@ai-matrx/records 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +95 -0
- package/LICENSE +21 -0
- package/README.md +118 -0
- package/dist/core/index.cjs +3003 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +652 -0
- package/dist/core/index.d.ts +652 -0
- package/dist/core/index.js +2970 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +1481 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2723 -0
- package/dist/index.d.ts +2723 -0
- package/dist/index.js +1458 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +2745 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +3238 -0
- package/dist/react/index.d.ts +3238 -0
- package/dist/react/index.js +2728 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +112 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Changelog — @ai-matrx/records
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
**The anonymous lane, wired to the doors that actually exist.** This package
|
|
6
|
+
named a door `custom.anon_record_write`, which the store never had — so
|
|
7
|
+
`FormRunner` told every person that a public form's link was "pending" for a
|
|
8
|
+
lane that had already shipped. W4-ANON landed as SEVEN doors, and they are all
|
|
9
|
+
here now: `anonTokenIssue` / `anonTokenVerify` / `anonTokenRevoke` (an embed
|
|
10
|
+
token carries ONE decision, read or write, and an exact origin list), `anonWrite`
|
|
11
|
+
(the stranger's write — the TOKEN supplies the organization, and what it makes is
|
|
12
|
+
a quarantined submission, never a record), `anonCapture` (the SIGNED-IN offline
|
|
13
|
+
path, deduplicated on a key the client mints) and `anonPublish`. A screen calling
|
|
14
|
+
a live feature pending is the same silent failure as one calling a missing
|
|
15
|
+
feature live.
|
|
16
|
+
|
|
17
|
+
**Work.** `workStates`, `workTemplateRefusal` / `workTemplateDeclare` /
|
|
18
|
+
`workTemplateInstantiate` / `workTemplateShape` / `workInstantiationShape`,
|
|
19
|
+
`workWhoseTurn`, `workHasAssignment`, `workAssignmentFields`,
|
|
20
|
+
`workTakeAssignment`, `workTransitionRefusal`, and the slots:
|
|
21
|
+
`workSlotsDeclare`, `workSlotHold`, `workSlotRelease`, `workSlotHolds`,
|
|
22
|
+
`workSlotExpire`. One hold per slot is a UNIQUE INDEX in the database, so a
|
|
23
|
+
second hold comes back as `already_exists` carrying the index that refused it.
|
|
24
|
+
|
|
25
|
+
**The trust doors, and they are `iam`'s, not the store's.** `isExternalPrincipal`,
|
|
26
|
+
`externalPrincipalCard`, `externalPrincipalReach`, `publishBindingCreate`,
|
|
27
|
+
`publishBindingRevoke`, `resolvePublishBinding`, `worldPublishGapNotice`,
|
|
28
|
+
`publishToWorld`. They are named by hand in `core/doors.ts` rather than
|
|
29
|
+
generated, because `pnpm records:generate` reads `custom` and only `custom` — a
|
|
30
|
+
census of it says nothing true about `iam`. `RecordsDataSource.rpc` gained an
|
|
31
|
+
optional third argument, `{ schema }`, which a supabase-js host routes to
|
|
32
|
+
`client.schema(schema).rpc(...)`; every existing call is unchanged.
|
|
33
|
+
|
|
34
|
+
New types: `WorkState`, `WorkGraph`, `WorkInstantiation`, `WorkTurn`,
|
|
35
|
+
`WorkSlotHold`, `WorkAssignmentField`, `AnonTokenBinding`,
|
|
36
|
+
`ExternalPrincipalCard`, `PublishBinding`, `ResolvedPublishBinding`.
|
|
37
|
+
|
|
38
|
+
**Two vocabularies this package had wrong, found by writing a Field the store
|
|
39
|
+
refused.** `FIELD_SENSITIVITIES` read `standard · sensitive · secret`; the store
|
|
40
|
+
has always wanted `public · internal · confidential · restricted`, which is also
|
|
41
|
+
what the knob `custom/field_sensitivity_levels` keys off — so the two could
|
|
42
|
+
never have agreed. And `Field.context_policy` was typed as `Delivery`
|
|
43
|
+
(`inline · on_demand · auto`), which is a different question: delivery is how
|
|
44
|
+
much of a merge field's value rides into a turn, while FLD-12's context policy
|
|
45
|
+
is whether an agent may see a Field's values at all, in the store's own words
|
|
46
|
+
`include · summarize · exclude · on_request`. Both are corrected, and
|
|
47
|
+
`ContextPolicy` is now its own exported type. Neither was generated, which is
|
|
48
|
+
why neither was caught by `pnpm check:store-registry`.
|
|
49
|
+
|
|
50
|
+
`src/store.generated.ts` regenerated from the live store.
|
|
51
|
+
|
|
52
|
+
## 0.2.0
|
|
53
|
+
|
|
54
|
+
**Reads go through the read door.** `recordRead` calls `custom.read_record` and
|
|
55
|
+
`list` calls `custom.read_records` instead of selecting `custom.record`, so the
|
|
56
|
+
store decides which rows a reader may see and which fields of them: a masked
|
|
57
|
+
field comes back present-and-null with the store's own reason in `hidden`
|
|
58
|
+
(`_hidden`, unfolded for you). `query` materialises through the same doors, and
|
|
59
|
+
its as-of reads now call `custom.query_record_as_of` / `custom.query_table_as_of`
|
|
60
|
+
— both clocks — instead of filtering `updated_at` itself and refusing the world
|
|
61
|
+
clock by name.
|
|
62
|
+
|
|
63
|
+
Shapes that changed with it: `list` and `query` answer `ReadRow[]`
|
|
64
|
+
(`{ id, document, level, hidden }`) rather than raw store rows, `RecordRead` is
|
|
65
|
+
`{ record_id, document, hidden, computed }`, and `useRecords` lost
|
|
66
|
+
`includeDeleted` — the read door reads live records only, and there is no door
|
|
67
|
+
that answers otherwise.
|
|
68
|
+
|
|
69
|
+
New refusal class: `stale_read` (`40001`), which is `custom.has_visibility_at`
|
|
70
|
+
refusing to answer from a snapshot older than a write the reader has already
|
|
71
|
+
observed. It is the one refusal worth retrying.
|
|
72
|
+
|
|
73
|
+
**A store defect this found and fixed** (`db/migrations/campaign/vis25_the_owner_arm_on_the_read_door.sql`,
|
|
74
|
+
applied to the main database): `custom.has_visibility` had no VIS-25 owner arm,
|
|
75
|
+
so the record you had just written was yours to `custom.read_records` and not
|
|
76
|
+
yours to `custom.read_record` — the list offered the row and the door slammed.
|
|
77
|
+
|
|
78
|
+
## 0.1.1
|
|
79
|
+
|
|
80
|
+
First working release: the types root, the headless `/core` client over the
|
|
81
|
+
store's doors, the `/react` binding, the generated store registry
|
|
82
|
+
(`pnpm records:generate` / `check:store-registry`) and the real-door suite that
|
|
83
|
+
runs against the live rehearsal branch inside an always-rolled-back transaction.
|
|
84
|
+
|
|
85
|
+
Two defects the suite found in the mirror and fixed:
|
|
86
|
+
|
|
87
|
+
- The document-size prediction measured `JSON.stringify` bytes; the store
|
|
88
|
+
measures the JSONB text, which puts a space after every `:` and every `,`. On
|
|
89
|
+
an 18-key document the two sentences disagreed by 35 bytes. The mirror now
|
|
90
|
+
renders the way Postgres does.
|
|
91
|
+
- The mirror predicted refusals for `ver`, `actor`, `on_behalf_of` and `at`.
|
|
92
|
+
The store never raises them: `custom.stamp_value_envelopes` and
|
|
93
|
+
`custom.value_versions` overwrite all four with the truth BEFORE
|
|
94
|
+
`custom.value_envelope_refusal` looks. Those four predictions are gone —
|
|
95
|
+
a sentence the store never says is a defect in the mirror.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AI Matrix Engine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @ai-matrx/records
|
|
2
|
+
|
|
3
|
+
THE headless client of the AI Matrx record store: the Table / Field / Rule /
|
|
4
|
+
Record / Relation / Home types, the closed vocabularies and refusal codes read
|
|
5
|
+
from the store ITSELF, the typed calls over the store's doors and nothing else,
|
|
6
|
+
the merge-field declaration, a validation mirror that predicts only what the
|
|
7
|
+
store's own triggers refuse, and a result envelope that never throws.
|
|
8
|
+
|
|
9
|
+
The package ships the types root, the headless `/core` and the React binding
|
|
10
|
+
`/react`. `@ai-matrx/records-ui` renders it; nothing else talks to the store.
|
|
11
|
+
|
|
12
|
+
**The one rule this package lives by:** the client mirrors the store's contract
|
|
13
|
+
and never re-implements a rule the store enforces. Where the store refuses, this
|
|
14
|
+
package surfaces the store's own sentence — never a translation of it.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import {
|
|
18
|
+
type Table, type Field, type StoredRecord, // the shapes
|
|
19
|
+
type ValueEnvelope, // ver · src · actor · on_behalf_of · at · absent · alternates · dated
|
|
20
|
+
PARITY_FIELD_TYPES, // the 13 field types, read from custom.parity_field_types()
|
|
21
|
+
ACTOR_VOCABULARY, // exactly: user, agent, system
|
|
22
|
+
ABSENCE_REASONS, // exactly: never asked, none, refused, conflicting
|
|
23
|
+
STORE_DOORS, STORE_REFUSAL_CODES, // GENERATED from pg_proc on the live store
|
|
24
|
+
} from "@ai-matrx/records";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## /core — the typed client over the doors
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createRecordsClient } from "@ai-matrx/records/core";
|
|
31
|
+
|
|
32
|
+
const records = createRecordsClient({
|
|
33
|
+
dataSource: supabase, // structural subset of SupabaseClient
|
|
34
|
+
organizationId: orgId, // the store is keyed (organization_id, id)
|
|
35
|
+
onError: (e) => report(e), // OPTIONAL — the default screams to console
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const written = await records.recordWrite({ table_id, data: { title: "Acme", owner: personId } });
|
|
39
|
+
const rows = await records.list({ table_id, limit: 50 });
|
|
40
|
+
const one = await records.recordRead({ record_id, withComputed: true });
|
|
41
|
+
const moved = await records.recordUpdate({ record_id, patch: { title: "Acme Inc" }, expectedVersion: 3 });
|
|
42
|
+
|
|
43
|
+
if (!moved.ok && moved.error.code === "stale_write") {
|
|
44
|
+
// The store's own words, plus the conflict field by field.
|
|
45
|
+
moved.error.message; // "Someone else changed this record while you were working on it. …"
|
|
46
|
+
staleWriteDetail(moved.error); // { expected_version, current_version, contested_fields }
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Nothing here throws, and nothing here invents an answer. A door the store does
|
|
51
|
+
not have yet is answered `door_absent`, naming the door AND the lane that owes
|
|
52
|
+
it — `metadataSearch`, `fieldPropose`, `tablePropose` and `mergeFieldResolve`
|
|
53
|
+
refuse today rather than returning an empty list that would read like "there is
|
|
54
|
+
nothing".
|
|
55
|
+
|
|
56
|
+
## The mirror — what the store is about to say, said half a second earlier
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { predictWriteRefusals } from "@ai-matrx/records/core";
|
|
60
|
+
|
|
61
|
+
const problems = predictWriteRefusals({ fields, document, recordType });
|
|
62
|
+
// [{ field_key: "amount", sqlstate: "23514", predicted_by: "custom.validate_values",
|
|
63
|
+
// message: "Amount has to be at least 1" }]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This is NOT validation — the store validates, inside the write door, and cannot
|
|
67
|
+
be bypassed. The mirror only says what the store is about to say, in the store's
|
|
68
|
+
exact sentence, and it says NOTHING where it cannot see (option membership,
|
|
69
|
+
relation targets, anything needing a query). Silence means "ask the store".
|
|
70
|
+
|
|
71
|
+
## /react — bind once, then hooks
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import {
|
|
75
|
+
RecordsProvider, useTables, useFields, useRecords, useRecord, useRecordMutation,
|
|
76
|
+
} from "@ai-matrx/records/react";
|
|
77
|
+
|
|
78
|
+
<RecordsProvider client={records}>{children}</RecordsProvider>;
|
|
79
|
+
|
|
80
|
+
const { tables } = useTables();
|
|
81
|
+
const { fields } = useFields(table_id);
|
|
82
|
+
const { rows, total, live, liveOffReason } = useRecords({ table_id, pageSize: 50 });
|
|
83
|
+
const { write, update, remove, conflict } = useRecordMutation();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`live` is never quietly false: when realtime is not bound, `liveOffReason` says
|
|
87
|
+
why in a sentence a screen can show.
|
|
88
|
+
|
|
89
|
+
## The generated half
|
|
90
|
+
|
|
91
|
+
`src/store.generated.ts` is written by `pnpm records:generate` from the live
|
|
92
|
+
store's own catalogue — the vocabularies from the store's vocabulary functions,
|
|
93
|
+
the field types from `custom.parity_field_types()`, the doors from `pg_proc`,
|
|
94
|
+
the kernel ids from the kernel rows, the tokens from `platform.entity_types`.
|
|
95
|
+
`pnpm check:store-registry` exits non-zero the moment this package and the store
|
|
96
|
+
disagree, which is how a door another lane added shows up as a red check rather
|
|
97
|
+
than as a runtime surprise.
|
|
98
|
+
|
|
99
|
+
## The suite
|
|
100
|
+
|
|
101
|
+
`pnpm test` runs against the live rehearsal branch. Every case is one
|
|
102
|
+
transaction that always ends in `rollback`: inside it the suite opens the
|
|
103
|
+
product switch, grants only what its own half needs, and sets
|
|
104
|
+
`request.jwt.claims` to the campaign's test identity (`test@test.com`) — so every
|
|
105
|
+
call goes through the real doors, the real triggers and the real guards, as a
|
|
106
|
+
real identity, and the branch is byte-identical afterwards (the last test
|
|
107
|
+
re-reads it from a fresh connection and proves it). The red half is the same
|
|
108
|
+
calls without that standing, asserted by the store's message text.
|
|
109
|
+
|
|
110
|
+
## Commands
|
|
111
|
+
|
|
112
|
+
| Command | What it does |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `pnpm records:generate` | Rewrite `src/store.generated.ts` from the live store |
|
|
115
|
+
| `pnpm check:store-registry` | Fail when this package and the store disagree |
|
|
116
|
+
| `pnpm test` | The real-door suite |
|
|
117
|
+
| `pnpm build` | tsup → `dist` (esm + cjs + d.ts), then the client boundary stamp |
|
|
118
|
+
| `pnpm check:package` | build · exports agreement · publint · tarball canary |
|