# @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 `planMigrations` and `migrate` return, the refusals that had no name in the docs (`PENDING`, `STEP_FAILED`, `WRONG_OWNER` from `migrate`, `UNKNOWN_KIND`, a duplicate sequence id), what `upgrader.upgrade`, `upgrader.locate` and `upgradeStore` take and return, and rows for `refuseProblems` and `diffJson`.

  No behaviour changed.

  **What to change:** nothing.

## 0.1.6

### Patch Changes

- de2a757: DESIGN.md no longer reads as if moss's `initIntegrations` and `initSessions` were still there to call. Moss stopped exporting both (see the moss changeset): its integration tables are part of `MOSS_SEQUENCE`, and the sessions table is made where a deployment keeps moss's stored sessions (`session: 'sessions'`); `createServer` and `migrateTables` run 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: `evaluate` checks a config object once and only evaluates it after that; `prismTransform` is gone. **Breaking.**

  BREAKING — approved by moccadroid, 2026-10-09: replace `prismTransform` with `evaluate` (`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 wrapped `evaluate`, 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 `compile` would; 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 now `evaluate(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 holding `undefined`, a function, a `Date` or a non-finite number is refused with `E_TYPE`).
  - An invalid config is a `PrismError` (`E_SCHEMA`) naming the part that is wrong, however deep the config. `prismTransform` threw zod's own error, and a `RangeError` for a config nested far past the limit.

  `evaluateSafe` takes the same arguments.

  **`compile` and `execute`** — for a config that is stored. Unchanged.

  **`prismTransform` is removed**, from `@niscorp/prism` and from `@niscorp/prism/migrations`. What it added over `evaluate` was a check that the source is plain JSON; that check is now `check: '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 `$prism` binding called `evaluate` on every use and so paid the check each time: an endpoint's request and response under moss, and a `$prism` value 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 name `evaluate` where they named `prismTransform`.

  **What to change**
  - `import { prismTransform } from '@niscorp/prism'` or `'@niscorp/prism/migrations'` → `import { evaluate } from '@niscorp/prism'`, and `transform: prismTransform` → `transform: evaluate`. Every app made by `create-nisc` has this in `src/dev/strata.ts`; the app's typecheck and `npm run strata` both 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: hand `evaluate` the 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 `prismTransform` refusing a source that is not plain JSON checks it itself, or passes `{ check: 'always' }`.

## 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 under `import`.

  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`, `/upgrade` or `/node` is an `instanceof` the `StrataError` that `@niscorp/strata` exports; one from `prismTransform` of `@niscorp/prism/migrations` is an `instanceof PrismError`; one from `<Nova.Layout>` is an `instanceof NovaError` and of its own class. None of them was.
  - `upgradeStore` names the row it refused — `integration_actions (integration_id="…", action_id="…"): …`, with the upgrader's error as its `cause` — 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/components` renders under `<Nova.Shell>`. It threw `useShell 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 `instanceof` failed, a match on the whole text of an `upgradeStore` refusal — 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.

- 2b5fd83: `migrate` takes a `guard` and a `dryRun`.

  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 with `DOES_NOT_FIT`, and nothing was applied. `dryRun` does 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. `StrataErrorCode` gains one member, `DOES_NOT_FIT`.

## 0.1.3

### Patch Changes

- 18182d7: README corrections. `sqlSteps` cuts 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. The `compareSnapshot` example 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.

  `upgrade` handed back the upgrader's own stamp, and `upgradeStore` wrote it over the row's. A row stamped `{ "acme.forms": 0, "acme.prices": 1 }`, upgraded by code that was given `acme.forms` only, was written back stamped `{ "acme.forms": 1 }`: its record of `acme.prices` was gone. Code that has that grammar then read the row as never migrated and ran `acme.prices/1` over 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, `upgrade` returns `upgrader.stamp` itself, as before. `upgrader.stamp`, `behind`, the lock file and every ledger checksum are untouched.

  `TOO_NEW` is, 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`'s `AGENTS.md` said "on any grammar"; they now say "on any grammar the code was given".

  **What to change:** nothing.

- bab7b2c: `compareSnapshot` reads 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 `required` list, an `enum` — and that included a tuple's `prefixItems`, where position is the grammar. `z.tuple([z.string(), z.number()])` changed to `z.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, answer `same` as 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_OWNER` refusal from `createUpgrader` says what to do when the sequence is a grammar: declare its `documents`.

  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, and `createUpgrader` refuses 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`) and `details` are as they were.

## 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:

  ```ts
  import prism from '@niscorp/prism/package.json' with { type: 'json' };
  prism.version; // the version that is installed, not the range that asked for it
  ```

  Until now the `exports` map hid it, and the only way to the version was a path into `node_modules`. `check:packages` installs 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 `query` and `transaction` are called on the pool, and a transaction's `query` on 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** — `migrate` and `upgradeStore` over a `PGlite` threw `Cannot read properties of undefined (reading '_checkReady')`; `status` and `readLedger` did too, once a ledger existed. A pool whose `transaction` hands a checked-out `pg` client through as the transaction failed in `migrate` the same way.
  - **moss** — `createTideStore` took `query` off 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** — `createPostgresAdapter` with a read limit set (`limitReads`) threw on a pool whose `transaction` is a method.

  A `PGlite` now works as a pool as it is, and so does a `pg` wrapper that passes its client through. A pool built from closures (`createPglitePool`, a wrapper that builds its own `query`) behaves as before.

  **What to change:** nothing.

## 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.
