@niscorp/strata
0.1.7
Patch Changes
2e73b96: The README states what its API tables left to source comments.
It now gives the grammar of a sequence id and of a kind name, what
planMigrationsandmigratereturn, the refusals that had no name in the docs (PENDING,STEP_FAILED,WRONG_OWNERfrommigrate,UNKNOWN_KIND, a duplicate sequence id), whatupgrader.upgrade,upgrader.locateandupgradeStoretake and return, and rows forrefuseProblemsanddiffJson.No behaviour changed.
What to change: nothing.
0.1.6
Patch Changes
de2a757: DESIGN.md no longer reads as if moss's
initIntegrationsandinitSessionswere still there to call. Moss stopped exporting both (see the moss changeset): its integration tables are part ofMOSS_SEQUENCE, and the sessions table is made where a deployment keeps moss's stored sessions (session: 'sessions');createServerandmigrateTablesrun both. The paragraph that names it is the record of what moved when strata landed, and it now says what has changed since. No code changed.What to change: nothing.
0.1.5
Patch Changes
f834f3f:
evaluatechecks a config object once and only evaluates it after that;prismTransformis gone. Breaking.BREAKING — approved by moccadroid, 2026-10-09: replace
prismTransformwithevaluate(transform: evaluate), and pass limits as{ limits: … }.Prism had three ways to run a config, and the one a host was told to hand to its seam (
prismTransform) was the one that reported worst, while the hosts on a hot path each wrappedevaluate, which checked the config on every call. There are two now, with one job each.evaluate(config, source, options?)— for a config a program holds.- The first time it is handed a config object it checks it and keeps its tree; the second time it prepares the tree as
compilewould; from then on a call only evaluates. Four fields picked and renamed, the same config object again: 3.5 µs before, 0.4 µs now. A nested choice on two fields: 18 µs before, 0.7 µs now. A config that is a new object on every call costs what it did. - It is kept by the object: a config changed in place after its first call is not read again. Hand over a new object.
- Both arguments are
unknown, so it is what a host's transform seam takes:createTide({ transform: evaluate }),createUpgrader(grammars, { transform: evaluate }). - The third argument is
{ limits?, check? }. Limits were the third argument themselves:evaluate(config, source, { maxSteps: 100 })is nowevaluate(config, source, { limits: { maxSteps: 100 } }). check: 'always'is for writing a config, for tests and for tools: nothing is kept, the config is checked on every call, and the source must be plain JSON (a source holdingundefined, a function, aDateor a non-finite number is refused withE_TYPE).- An invalid config is a
PrismError(E_SCHEMA) naming the part that is wrong, however deep the config.prismTransformthrew zod's own error, and aRangeErrorfor a config nested far past the limit.
evaluateSafetakes the same arguments.compileandexecute— for a config that is stored. Unchanged.prismTransformis removed, from@niscorp/prismand from@niscorp/prism/migrations. What it added overevaluatewas a check that the source is plain JSON; that check is nowcheck: 'always', and a seam's source is taken as it is.moss, nova, vex, loom, nisc, create-nisc and the cli move with it. Each has Prism as a peer or a dependency, or has one of those that do, and a Prism at 0.3 is outside the range their last versions accept: an app takes them together. Nothing in vex, loom or the cli changed beyond that range.
moss's shell and nova's
$prismbinding calledevaluateon every use and so paid the check each time: an endpoint's request and response under moss, and a$prismvalue on each draw and for each row of a list, are 8 to 45 times faster for the configs measured. tide's and strata's documentation nameevaluatewhere they namedprismTransform.What to change
import { prismTransform } from '@niscorp/prism'or'@niscorp/prism/migrations'→import { evaluate } from '@niscorp/prism', andtransform: prismTransform→transform: evaluate. Every app made bycreate-nischas this insrc/dev/strata.ts; the app's typecheck andnpm run strataboth stop on that line until it is changed.- A third argument of limits becomes
{ limits: … }. - A wrapper that parsed the config before calling
evaluate(evaluate(ConfigSchema.parse(config), …)) makes a new object on every call and so is checked on every call: handevaluatethe config itself. - Code that changes a config object in place and evaluates it again must hand over a new object, or pass
{ check: 'always' }. - A host that relied on
prismTransformrefusing a source that is not plain JSON checks it itself, or passes{ check: 'always' }.
- The first time it is handed a config object it checks it and keeps its tree; the second time it prepares the tree as
0.1.4
Patch Changes
7f8d202: Under
require, the entry points of nova, prism and strata share one copy of their code, as they always have underimport.The CommonJS build was not split, so every entry point carried its own copy of each class and of each React context. The ESM build is byte for byte what it was. Under
require:- an error from
@niscorp/strata/postgres,/check,/upgradeor/nodeis aninstanceoftheStrataErrorthat@niscorp/strataexports; one fromprismTransformof@niscorp/prism/migrationsis aninstanceof PrismError; one from<Nova.Layout>is aninstanceof NovaErrorand of its own class. None of them was. upgradeStorenames the row it refused —integration_actions (integration_id="…", action_id="…"): …, with the upgrader's error as itscause— and so does a moss boot that meets a stored row it cannot read. Both gave the upgrader's sentence alone.- a kit registered from
@niscorp/nova/adapters/react/componentsrenders under<Nova.Shell>. It threwuseShell must be used inside <NovaShellProvider>.
What to change: nothing in an ESM app. A CommonJS app that had built on any of the above — a branch that ran because
instanceoffailed, a match on the whole text of anupgradeStorerefusal — now sees what an ESM app sees. An app that loads both builds of one package in one process still has two copies of it.- an error from
2b5fd83:
migratetakes aguardand adryRun.A host could run a table migration and could not ask anything of the result before it landed, or see what a run would do without doing it.
guard(tx)is asked inside the run's transaction, after the last pending step — when nothing was pending too. Every sentence it returns refuses the run withDOES_NOT_FIT, and nothing was applied.dryRundoes all of it, guard included, then rolls it back: the report says what would be applied, and it takes the locks a real run takes.What to change: nothing. A run given neither behaves exactly as before.
StrataErrorCodegains one member,DOES_NOT_FIT.
0.1.3
Patch Changes
18182d7: README corrections.
sqlStepscuts at each line that ends in;, full-line--comments excepted. It is not a SQL parser: a;that ends a line inside a string, a function body, a/* */comment or a trailing--comment cuts there too, and two statements on one line, or a;followed by a trailing comment, stay one step. ThecompareSnapshotexample shows the lines it prints (- required["id"],+ required["key"]). No code changed.What to change: nothing.
bd3184d: An upgrade keeps a document's stamp entries for grammars the upgrader was not given.
upgradehanded back the upgrader's own stamp, andupgradeStorewrote it over the row's. A row stamped{ "acme.forms": 0, "acme.prices": 1 }, upgraded by code that was givenacme.formsonly, was written back stamped{ "acme.forms": 1 }: its record ofacme.priceswas gone. Code that has that grammar then read the row as never migrated and ranacme.prices/1over it a second time — a price already in cents, multiplied again.The stamp now handed back, and written, is the upgrader's own plus the document's entries for any grammar it was not given, as they were. Nothing new is refused, and nothing else is written differently: for a document whose stamp names only the upgrader's grammars,
upgradereturnsupgrader.stampitself, as before.upgrader.stamp,behind, the lock file and every ledger checksum are untouched.TOO_NEWis, as it always was, about the grammars the code was given — a stamp entry for one it was not given is neither ahead nor behind. The README, DESIGN and rule 19 of@niscorp/nisc'sAGENTS.mdsaid "on any grammar"; they now say "on any grammar the code was given".What to change: nothing.
bab7b2c:
compareSnapshotreads a tuple by position: swapped positions are a change.Every array in a schema was compared as a set — right for a union's members, a
requiredlist, anenum— and that included a tuple'sprefixItems, where position is the grammar.z.tuple([z.string(), z.number()])changed toz.tuple([z.number(), z.string()])read as{ status: 'same' }while a document valid before (["a", 1]) was no longer valid, so the gate asked for no migration.A tuple's positions are now compared place by place: the swap above is
changed, with~ …prefixItems[0].type: "string" → "number", and a position added or removed is named (+ …prefixItems[1]). What sits inside a position is compared as before, a union there still as a set. A recorded snapshot is read as it was and none needs re-recording: nisc's own grammars, Prism's with its ten tuples among them, answersameas before.What to change: nothing, unless a grammar of yours had a tuple's positions reordered at a version that was already recorded. That was a change the gate missed; it now says so, and the answer is the migration the change owed — append it, then record the new version.
eef9cd9: The
WRONG_OWNERrefusal fromcreateUpgradersays what to do when the sequence is a grammar: declare itsdocuments.strata tells a grammar from a table sequence by what the sequence declares:
documents, or a document step. A kit that declares neither and has no migrations yet — or only empty markers, the migration an addition to a grammar is recorded with — reads as owning tables, andcreateUpgraderrefuses it. The refusal named one way out,migrate, which for a kit is the wrong one: it records the kit's markers in a database's ledger.Which sequences are refused has not changed, and neither has how a sequence is read. The message has a second sentence:
- before:
Documents are upgraded by grammar sequences; these own tables and belong to a database's ledger (`migrate`). - now:
Documents are upgraded by grammar sequences; these own tables and belong to a database's ledger (`migrate`). If one of them is a grammar, declare its `documents` — a sequence with no `documents` and no document step is read as owning tables.
The README states the rule, and its kit example declares
documents: {}.What to change: nothing in an app. Code or a stored expectation that compares this error's full message text needs the new text;
code(WRONG_OWNER) anddetailsare as they were.- before:
0.1.2
Patch Changes
fe30458: Every package exports its
package.json, so an app can say which version of a package it runs by reading it from the package:import prism from '@niscorp/prism/package.json' with { type: 'json' }; prism.version; // the version that is installed, not the range that asked for itUntil now the
exportsmap hid it, and the only way to the version was a path intonode_modules.check:packagesinstalls the tarballs and reads every package's version this way.What to change: nothing. An app that read a version by path can read it by name.
1e6d55d: A pool's
queryandtransactionare called on the pool, and a transaction'squeryon the transaction — never taken off the object first. A driver's own object has them as methods that need their receiver, and taken off they failed inside the driver, on a property nothing in nisc names:- strata —
migrateandupgradeStoreover aPGlitethrewCannot read properties of undefined (reading '_checkReady');statusandreadLedgerdid too, once a ledger existed. A pool whosetransactionhands a checked-outpgclient through as the transaction failed inmigratethe same way. - moss —
createTideStoretookqueryoff the transaction. Over a pool that hands its client through, every transaction of the store failed inside the driver, and tide recorded the run as deferred: nothing threw, and the effect never ran. - vex —
createPostgresAdapterwith a read limit set (limitReads) threw on a pool whosetransactionis a method.
A
PGlitenow works as a pool as it is, and so does apgwrapper that passes its client through. A pool built from closures (createPglitePool, a wrapper that builds its ownquery) behaves as before.What to change: nothing.
- strata —
0.1.1
Patch Changes
- b67a125: Documentation only: each package's README, reference and design docs checked against its source and corrected — install lines and peers, signatures, defaults, status codes, licenses (loom, signal: Apache-2.0), and API that existed but was not documented.