@noy-db/hub 0.2.0-pre.30 → 0.2.0-pre.31
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/dist/adapter/index.d.ts +9 -0
- package/dist/adapter/index.js +14 -0
- package/dist/attestation/index.d.ts +1 -1
- package/dist/attestation/index.js +3 -3
- package/dist/blobs/index.d.ts +3 -3
- package/dist/bundle/index.d.ts +3 -3
- package/dist/bundle/index.js +1 -1
- package/dist/{chunk-Y6YUWZTS.js → chunk-L5HHBOMS.js} +2 -2
- package/dist/{chunk-BVKLJBJJ.js → chunk-W5NVNJDX.js} +2 -1
- package/dist/{chunk-BVKLJBJJ.js.map → chunk-W5NVNJDX.js.map} +1 -1
- package/dist/consent/index.d.ts +2 -2
- package/dist/{decrypt-partition-Mhjh6nnG.d.ts → decrypt-partition-CXS5KopB.d.ts} +1 -1
- package/dist/derivations/index.d.ts +3 -3
- package/dist/{dev-unlock-DJ3IhgY9.d.ts → dev-unlock-CnHTTVea.d.ts} +1 -1
- package/dist/guards/index.d.ts +3 -3
- package/dist/{hash-DLwkYf1d.d.ts → hash-oc5paYl0.d.ts} +1 -1
- package/dist/history/index.d.ts +3 -3
- package/dist/i18n/index.d.ts +2 -2
- package/dist/{index-CMRIx_zx.d.ts → index-BmhGztUi.d.ts} +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +2 -2
- package/dist/kernel/index.d.ts +2 -2
- package/dist/materialized-views/index.d.ts +3 -3
- package/dist/{mime-magic-CacDBwYp.d.ts → mime-magic-JeLKHRng.d.ts} +1 -1
- package/dist/{noydb-5MIBD77B.js → noydb-NAU2DTFP.js} +2 -2
- package/dist/noydb-NAU2DTFP.js.map +1 -0
- package/dist/overlay-views/index.d.ts +3 -3
- package/dist/periods/index.d.ts +2 -2
- package/dist/session/index.d.ts +3 -3
- package/dist/shadow/index.d.ts +2 -2
- package/dist/snapshots/index.d.ts +2 -2
- package/dist/store/index.d.ts +2 -2
- package/dist/sync/index.d.ts +1 -1
- package/dist/team/index.d.ts +2 -2
- package/dist/{transition-guard-J04-jime.d.ts → transition-guard-Dy3NioQr.d.ts} +1 -1
- package/dist/tx/index.d.ts +2 -2
- package/dist/{with-materialized-view-DNfzC5lH.d.ts → with-materialized-view-DIVeez0W.d.ts} +1 -1
- package/dist/{with-overlayed-view-qk3lbhOd.d.ts → with-overlayed-view-DVZrc5Hb.d.ts} +1 -1
- package/dist/{with-rollup-Dd3_ZG4b.d.ts → with-rollup-CbU-O4wP.d.ts} +1 -1
- package/package.json +62 -221
- package/dist/aggregate/index.cjs +0 -1144
- package/dist/aggregate/index.cjs.map +0 -1
- package/dist/aggregate/index.d.cts +0 -39
- package/dist/attestation/index.cjs +0 -305
- package/dist/attestation/index.cjs.map +0 -1
- package/dist/attestation/index.d.cts +0 -54
- package/dist/blobs/index.cjs +0 -1967
- package/dist/blobs/index.cjs.map +0 -1
- package/dist/blobs/index.d.cts +0 -48
- package/dist/bundle/index.cjs +0 -41045
- package/dist/bundle/index.cjs.map +0 -1
- package/dist/bundle/index.d.cts +0 -187
- package/dist/consent/index.cjs +0 -204
- package/dist/consent/index.cjs.map +0 -1
- package/dist/consent/index.d.cts +0 -27
- package/dist/crdt/index.cjs +0 -152
- package/dist/crdt/index.cjs.map +0 -1
- package/dist/crdt/index.d.cts +0 -30
- package/dist/decrypt-partition-C0iDpbSd.d.cts +0 -558
- package/dist/derivations/index.cjs +0 -469
- package/dist/derivations/index.cjs.map +0 -1
- package/dist/derivations/index.d.cts +0 -74
- package/dist/dev-unlock-0Z6yxfS7.d.cts +0 -263
- package/dist/discriminant-BN9REW3o.d.cts +0 -60
- package/dist/errors-BAWO5Z5a.d.cts +0 -1500
- package/dist/forget/index.cjs +0 -43
- package/dist/forget/index.cjs.map +0 -1
- package/dist/forget/index.d.cts +0 -1
- package/dist/guards/index.cjs +0 -455
- package/dist/guards/index.cjs.map +0 -1
- package/dist/guards/index.d.cts +0 -39
- package/dist/hash-Db8ncXGr.d.cts +0 -63
- package/dist/history/index.cjs +0 -1245
- package/dist/history/index.cjs.map +0 -1
- package/dist/history/index.d.cts +0 -65
- package/dist/i18n/index.cjs +0 -1280
- package/dist/i18n/index.cjs.map +0 -1
- package/dist/i18n/index.d.cts +0 -41
- package/dist/index-BMmajblo.d.cts +0 -362
- package/dist/index-C1SC1EPe.d.cts +0 -1325
- package/dist/index-xJEPkvMb.d.cts +0 -93
- package/dist/index.cjs +0 -48100
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1050
- package/dist/indexing/index.cjs +0 -803
- package/dist/indexing/index.cjs.map +0 -1
- package/dist/indexing/index.d.cts +0 -36
- package/dist/kernel/index.cjs +0 -751
- package/dist/kernel/index.cjs.map +0 -1
- package/dist/kernel/index.d.cts +0 -11
- package/dist/lazy-builder-eYZzLEL1.d.cts +0 -304
- package/dist/materialized-views/index.cjs +0 -1518
- package/dist/materialized-views/index.cjs.map +0 -1
- package/dist/materialized-views/index.d.cts +0 -187
- package/dist/mime-magic-sdMzsfVK.d.cts +0 -103
- package/dist/overlay-views/index.cjs +0 -399
- package/dist/overlay-views/index.cjs.map +0 -1
- package/dist/overlay-views/index.d.cts +0 -102
- package/dist/periods/index.cjs +0 -1041
- package/dist/periods/index.cjs.map +0 -1
- package/dist/periods/index.d.cts +0 -24
- package/dist/predicate-BmhBSPCH.d.cts +0 -267
- package/dist/query/index.cjs +0 -3480
- package/dist/query/index.cjs.map +0 -1
- package/dist/query/index.d.cts +0 -4
- package/dist/sealed-record/index.cjs +0 -139
- package/dist/sealed-record/index.cjs.map +0 -1
- package/dist/sealed-record/index.d.cts +0 -123
- package/dist/session/index.cjs +0 -495
- package/dist/session/index.cjs.map +0 -1
- package/dist/session/index.d.cts +0 -48
- package/dist/shadow/index.cjs +0 -133
- package/dist/shadow/index.cjs.map +0 -1
- package/dist/shadow/index.d.cts +0 -19
- package/dist/snapshots/index.cjs +0 -937
- package/dist/snapshots/index.cjs.map +0 -1
- package/dist/snapshots/index.d.cts +0 -30
- package/dist/store/index.cjs +0 -1091
- package/dist/store/index.cjs.map +0 -1
- package/dist/store/index.d.cts +0 -501
- package/dist/strategy-BSxFXGzb.d.cts +0 -110
- package/dist/strategy-mJExw4LQ.d.cts +0 -1069
- package/dist/sync/index.cjs +0 -1062
- package/dist/sync/index.cjs.map +0 -1
- package/dist/sync/index.d.cts +0 -45
- package/dist/team/index.cjs +0 -2805
- package/dist/team/index.cjs.map +0 -1
- package/dist/team/index.d.cts +0 -120
- package/dist/transition-guard-tWZIsaXe.d.cts +0 -165
- package/dist/tx/index.cjs +0 -614
- package/dist/tx/index.cjs.map +0 -1
- package/dist/tx/index.d.cts +0 -39
- package/dist/types-CdVfiQgt.d.cts +0 -15275
- package/dist/ulid-DRH25k3y.d.cts +0 -66
- package/dist/util/index.cjs +0 -237
- package/dist/util/index.cjs.map +0 -1
- package/dist/util/index.d.cts +0 -79
- package/dist/with-materialized-view-DfgPcvi9.d.cts +0 -27
- package/dist/with-overlayed-view-Cvfkx1lg.d.cts +0 -13
- package/dist/with-rollup-nIxo7h9z.d.cts +0 -47
- /package/dist/{noydb-5MIBD77B.js.map → adapter/index.js.map} +0 -0
- /package/dist/{chunk-Y6YUWZTS.js.map → chunk-L5HHBOMS.js.map} +0 -0
- /package/dist/{types-DHn9lEP0.d.ts → index-DHn9lEP0.d.ts} +0 -0
package/dist/team/index.d.cts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { aW as NoydbStore, aU as UnlockedKeyring } from '../types-CdVfiQgt.cjs';
|
|
2
|
-
export { be as BundleRecipient, cj as EnrollAuthenticatorOptions, ck as EnrollAuthenticatorWrappingDEKsOptions, cl as EnrollAuthenticatorWrappingKEKOptions, d1 as ListUsersOptions, ds as PaperRecoveryEntry, dA as PresenceHandle, dV as RecoverPassphraseInput, dW as RecoverPassphraseResult, dX as RecoverUserOptions, dY as RecoveryProof, e4 as RotatePassphraseInput, en as SlotRewrapCeremony, eo as SlotRewrapContext, ew as SyncEngine, eE as SyncTransaction, eU as UpdateAuthenticatorOptions, fe as WrappedDeksBlob, fm as buildRecipientKeyringFile, fn as burnPaperRecoveryEntry, gl as changeSecret, gm as createOwnerKeyring, fr as deriveMagicLinkContentKey, fs as enrollAuthenticator, gn as ensureCollectionDEK, fv as evaluateExportCapability, fw as evaluateImportCapability, fy as findAuthenticator, go as grant, fz as hasExportCapability, fA as hasImportCapability, fD as isMagicLinkGrantExpired, fJ as listMagicLinkGrants, fK as listUsers, fL as listUsersWithEnvelopes, gp as loadKeyring, fO as loadPaperRecoveryEntries, fR as magicLinkGrantRecordId, fS as mintPaperRecoveryEntry, fU as mintWrappedDeksBlob, gq as persistKeyring, fX as readMagicLinkGrantRecord, fG as recoverPassphrase, fY as recoverUser, f_ as removeAuthenticator, gr as revoke, g3 as revokeMagicLinkGrant, fH as rotatePassphrase, g5 as savePaperRecoveryEntries, g9 as unwrapDeksFromBlob, ga as unwrapDeksFromPaperEntry, gc as unwrapMagicLinkGrant, gs as updateAuthenticator, gt as updateKeyringIdentity, gk as writeMagicLinkGrant } from '../types-CdVfiQgt.cjs';
|
|
3
|
-
import '../lazy-builder-eYZzLEL1.cjs';
|
|
4
|
-
import '../predicate-BmhBSPCH.cjs';
|
|
5
|
-
import '../strategy-mJExw4LQ.cjs';
|
|
6
|
-
import '../errors-BAWO5Z5a.cjs';
|
|
7
|
-
import '../strategy-BSxFXGzb.cjs';
|
|
8
|
-
import '../index-BMmajblo.cjs';
|
|
9
|
-
import '../index-C1SC1EPe.cjs';
|
|
10
|
-
import '@noy-db/attestation';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* _sync_credentials reserved collection —
|
|
14
|
-
*
|
|
15
|
-
* Stores per-adapter OAuth tokens (and any other long-lived sync secrets) as
|
|
16
|
-
* encrypted records inside the vault itself. Tokens are wrapped with the
|
|
17
|
-
* compartment's own DEK, live on disk as ciphertext like any other record, and
|
|
18
|
-
* are accessed only through the dedicated API in this module — never via
|
|
19
|
-
* `vault.collection('_sync_credentials')`.
|
|
20
|
-
*
|
|
21
|
-
* Design decisions
|
|
22
|
-
* ────────────────
|
|
23
|
-
*
|
|
24
|
-
* **Why a reserved collection, not a separate store?**
|
|
25
|
-
* The compartment's existing encryption stack (AES-256-GCM + collection DEK)
|
|
26
|
-
* is exactly the right primitive for protecting OAuth tokens at rest. Using a
|
|
27
|
-
* separate store would require a new encryption surface, new adapter calls,
|
|
28
|
-
* and a new backup/restore path — all of which already exist for collections.
|
|
29
|
-
*
|
|
30
|
-
* **Why not exposed as a regular collection?**
|
|
31
|
-
* The same reason `_keyring` and `_ledger` aren't: they have invariants that
|
|
32
|
-
* must be enforced (naming scheme, no cross-user leakage, no schema
|
|
33
|
-
* validation, no history/ledger writes for privacy). Routing through a
|
|
34
|
-
* dedicated API enforces those invariants.
|
|
35
|
-
*
|
|
36
|
-
* **Token lifecycle:**
|
|
37
|
-
* - `putCredential(vault, adapterId, token)` — store or overwrite
|
|
38
|
-
* - `getCredential(vault, adapterId)` — load and decrypt
|
|
39
|
-
* - `deleteCredential(vault, adapterId)` — remove
|
|
40
|
-
* - `listCredentials(vault)` — enumerate adapter IDs (not tokens)
|
|
41
|
-
*
|
|
42
|
-
* The `adapterId` is the record ID within the `_sync_credentials` collection.
|
|
43
|
-
* It should be a stable, human-readable identifier for the adapter instance
|
|
44
|
-
* (e.g. `'google-drive'`, `'dropbox'`, `'s3-prod'`).
|
|
45
|
-
*
|
|
46
|
-
* **ACL:** only `owner` and `admin` roles can read/write sync credentials.
|
|
47
|
-
* Operators, viewers, and clients cannot call this API. The check is made
|
|
48
|
-
* against the caller's keyring role at call time.
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
/** The reserved collection name. Never collides with user collections. */
|
|
52
|
-
declare const SYNC_CREDENTIALS_COLLECTION = "_sync_credentials";
|
|
53
|
-
/**
|
|
54
|
-
* An OAuth/auth token stored in `_sync_credentials`.
|
|
55
|
-
*
|
|
56
|
-
* Fields mirror the OAuth2 token response shape. `customData` is an escape
|
|
57
|
-
* hatch for adapter-specific secrets (API keys, connection strings, etc.)
|
|
58
|
-
* that don't fit the OAuth2 shape.
|
|
59
|
-
*/
|
|
60
|
-
interface SyncCredential {
|
|
61
|
-
/** Stable identifier for the adapter instance (e.g. 'google-drive'). */
|
|
62
|
-
readonly adapterId: string;
|
|
63
|
-
/** OAuth token type, usually 'Bearer'. */
|
|
64
|
-
readonly tokenType: string;
|
|
65
|
-
/** The access token. Expires at `expiresAt` if set. */
|
|
66
|
-
readonly accessToken: string;
|
|
67
|
-
/** Long-lived refresh token for renewing the access token. */
|
|
68
|
-
readonly refreshToken?: string;
|
|
69
|
-
/** ISO timestamp when `accessToken` expires. Absent means "no expiry". */
|
|
70
|
-
readonly expiresAt?: string;
|
|
71
|
-
/** Space-separated OAuth scopes. */
|
|
72
|
-
readonly scopes?: string;
|
|
73
|
-
/** Adapter-specific opaque data (API keys, endpoints, etc.). */
|
|
74
|
-
readonly customData?: Record<string, string>;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Store or overwrite a sync credential for the given adapter.
|
|
78
|
-
*
|
|
79
|
-
* The credential is encrypted with the `_sync_credentials` collection DEK
|
|
80
|
-
* (auto-generated on first use). The record ID is the `adapterId`.
|
|
81
|
-
*
|
|
82
|
-
* Requires owner or admin role.
|
|
83
|
-
*/
|
|
84
|
-
declare function putCredential(adapter: NoydbStore, vault: string, keyring: UnlockedKeyring, credential: SyncCredential): Promise<void>;
|
|
85
|
-
/**
|
|
86
|
-
* Load and decrypt a sync credential for the given adapter ID.
|
|
87
|
-
*
|
|
88
|
-
* Returns `null` if no credential exists for this adapter.
|
|
89
|
-
* Requires owner or admin role.
|
|
90
|
-
*/
|
|
91
|
-
declare function getCredential(adapter: NoydbStore, vault: string, keyring: UnlockedKeyring, adapterId: string): Promise<SyncCredential | null>;
|
|
92
|
-
/**
|
|
93
|
-
* Delete a sync credential by adapter ID.
|
|
94
|
-
*
|
|
95
|
-
* No-op if the credential doesn't exist. Requires owner or admin role.
|
|
96
|
-
*/
|
|
97
|
-
declare function deleteCredential(adapter: NoydbStore, vault: string, keyring: UnlockedKeyring, adapterId: string): Promise<void>;
|
|
98
|
-
/**
|
|
99
|
-
* List all adapter IDs that have stored credentials.
|
|
100
|
-
*
|
|
101
|
-
* Returns only the IDs, never the credential payloads. Useful for
|
|
102
|
-
* displaying "connected adapters" in UI without decrypting tokens.
|
|
103
|
-
* Requires owner or admin role.
|
|
104
|
-
*/
|
|
105
|
-
declare function listCredentials(adapter: NoydbStore, vault: string, keyring: UnlockedKeyring): Promise<string[]>;
|
|
106
|
-
/**
|
|
107
|
-
* Check whether a credential exists and whether its access token has expired.
|
|
108
|
-
*
|
|
109
|
-
* Returns `{ exists: false }` if no credential is stored, or
|
|
110
|
-
* `{ exists: true, expired: boolean }` based on the `expiresAt` field.
|
|
111
|
-
* Requires owner or admin role.
|
|
112
|
-
*/
|
|
113
|
-
declare function credentialStatus(adapter: NoydbStore, vault: string, keyring: UnlockedKeyring, adapterId: string): Promise<{
|
|
114
|
-
exists: false;
|
|
115
|
-
} | {
|
|
116
|
-
exists: true;
|
|
117
|
-
expired: boolean;
|
|
118
|
-
}>;
|
|
119
|
-
|
|
120
|
-
export { SYNC_CREDENTIALS_COLLECTION, type SyncCredential, UnlockedKeyring, credentialStatus, deleteCredential, getCredential, listCredentials, putCredential };
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { an as GuardStrategy, ar as GuardStrategyHandle, ao as GuardChange, ap as GuardContext } from './types-CdVfiQgt.cjs';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Register a guard for a collection. Guards run on every `put()` /
|
|
5
|
-
* `delete()` for the named collection (after permissions, before
|
|
6
|
-
* encryption) and may:
|
|
7
|
-
*
|
|
8
|
-
* - `check` — block writes by throwing (typically `RecordLockedError`)
|
|
9
|
-
* - `frozenFields` — freeze specific fields once a condition is true
|
|
10
|
-
* - `amendment` — declare an authorized-override path with invariant
|
|
11
|
-
*
|
|
12
|
-
* Pass the returned handle to `createNoydb({ strategies: [...] })`.
|
|
13
|
-
*
|
|
14
|
-
* @see docs/superpowers/specs/2026-05-18-guards-design.md
|
|
15
|
-
*/
|
|
16
|
-
declare function withGuard<T extends Record<string, unknown>>(strategy: GuardStrategy<T>): GuardStrategyHandle<T>;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* `immutableGuard` — declarative WORM / append-only sugar over the guard
|
|
20
|
-
* subsystem.
|
|
21
|
-
*
|
|
22
|
-
* Issued fiscal documents (invoices, DDTs) must be immutable after issue.
|
|
23
|
-
* That is expressible today with a hand-rolled `withGuard` (block on
|
|
24
|
-
* `check`/`onDelete`, allow an admin `amendment`), but the boilerplate is
|
|
25
|
-
* repetitive and easy to get subtly wrong. `immutableGuard` generates
|
|
26
|
-
* exactly that guard from a declarative config, reusing the guard
|
|
27
|
-
* machinery wholesale — `check`/`onDelete` rejection, the ledgered
|
|
28
|
-
* `amendment` override, and composition with `periods`/`history`.
|
|
29
|
-
*
|
|
30
|
-
* ```ts
|
|
31
|
-
* createNoydb({ guardStrategies: [
|
|
32
|
-
* immutableGuard({
|
|
33
|
-
* collection: 'invoices',
|
|
34
|
-
* after: (r) => r.status === 'issued', // immutable once issued
|
|
35
|
-
* }),
|
|
36
|
-
* ] })
|
|
37
|
-
* ```
|
|
38
|
-
*
|
|
39
|
-
* A record is mutable until `after(record)` holds; from then on, updates
|
|
40
|
-
* and deletes throw `RecordLockedError` unless performed inside an
|
|
41
|
-
* `amendment` transaction by an authorized role (the override is
|
|
42
|
-
* ledgered by the guard amendment mechanism). `appendOnly: true` is
|
|
43
|
-
* shorthand for `after: () => true` — immutable from creation.
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
interface ImmutableGuardConfig<T extends Record<string, unknown>> {
|
|
47
|
-
/** The collection to make WORM. */
|
|
48
|
-
collection: string;
|
|
49
|
-
/**
|
|
50
|
-
* A record becomes immutable once this predicate holds. Evaluated on
|
|
51
|
-
* the *existing* (already-persisted) record, so the write that first
|
|
52
|
-
* makes it true is still allowed; subsequent writes are blocked.
|
|
53
|
-
* Mutually exclusive with `appendOnly`.
|
|
54
|
-
*/
|
|
55
|
-
after?: (record: T) => boolean;
|
|
56
|
-
/** Shorthand for `after: () => true` — immutable from creation. */
|
|
57
|
-
appendOnly?: boolean;
|
|
58
|
-
/** Roles permitted to override via an amendment transaction. Default `['admin', 'owner']`. */
|
|
59
|
-
amendmentRoles?: ReadonlyArray<'admin' | 'owner'>;
|
|
60
|
-
/**
|
|
61
|
-
* Optional set-level invariant run over the amendment change-set after
|
|
62
|
-
* the writes execute. Signature matches `GuardStrategy.amendment.invariant`
|
|
63
|
-
* exactly: it receives every {before, after} pair touching this
|
|
64
|
-
* collection in the amendment plus the guard context; throwing reverts
|
|
65
|
-
* the whole amendment and surfaces as `InvariantError`.
|
|
66
|
-
*
|
|
67
|
-
* Use this to keep a constraint inviolable EVEN under amendment — e.g.
|
|
68
|
-
* forbid deleting an issued document by re-throwing on any
|
|
69
|
-
* `before !== null && after === null` change, or assert a cross-record
|
|
70
|
-
* sum is preserved. When omitted the amendment is unconditionally
|
|
71
|
-
* allowed (the amendment itself is the sanctioned, ledgered override) —
|
|
72
|
-
* this is the backward-compatible default.
|
|
73
|
-
*/
|
|
74
|
-
amendmentInvariant?: (changes: ReadonlyArray<GuardChange<T>>, ctx: GuardContext<T>) => Promise<void> | void;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Build an immutability guard. Pass the returned handle to
|
|
78
|
-
* `createNoydb({ guardStrategies: [...] })`.
|
|
79
|
-
*/
|
|
80
|
-
declare function immutableGuard<T extends Record<string, unknown>>(config: ImmutableGuardConfig<T>): GuardStrategyHandle<T>;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* `transitionGuard` — declarative state-machine sugar over the guard
|
|
84
|
-
* subsystem.
|
|
85
|
-
*
|
|
86
|
-
* Any record with a lifecycle field (invoice `status`, order state,
|
|
87
|
-
* ticket workflow, subscription phase) needs transition validation: a
|
|
88
|
-
* write may only move the field along a declared arc. That is expressible
|
|
89
|
-
* with a hand-rolled `withGuard({ check })`, but every consumer
|
|
90
|
-
* re-implements the same graph lookup + error. `transitionGuard`
|
|
91
|
-
* generates exactly that guard from a state graph, reusing the guard
|
|
92
|
-
* machinery wholesale — `check` rejection, the ledgered `amendment`
|
|
93
|
-
* override, and composition with `periods`/`history`.
|
|
94
|
-
*
|
|
95
|
-
* It generalizes {@link immutableGuard}: WORM is the special case "every
|
|
96
|
-
* state has no outgoing arcs", i.e. `transitions` mapping each state to `[]`.
|
|
97
|
-
*
|
|
98
|
-
* ```ts
|
|
99
|
-
* createNoydb({ guardStrategies: [
|
|
100
|
-
* transitionGuard<Sale>({
|
|
101
|
-
* collection: 'sales', field: 'status',
|
|
102
|
-
* transitions: { // absence of an arc = forbidden
|
|
103
|
-
* draft: ['to_verify', 'cancelled'],
|
|
104
|
-
* to_verify: ['proforma', 'draft', 'cancelled'],
|
|
105
|
-
* proforma: ['invoiced', 'cancelled'],
|
|
106
|
-
* invoiced: ['paid'], paid: [], cancelled: [],
|
|
107
|
-
* },
|
|
108
|
-
* initial: ['draft', 'to_verify'], // allowed status on insert
|
|
109
|
-
* }),
|
|
110
|
-
* ] })
|
|
111
|
-
* ```
|
|
112
|
-
*
|
|
113
|
-
* Semantics:
|
|
114
|
-
* - **Insert** (`ctx.existing === null`): `incoming[field]` must be in
|
|
115
|
-
* `initial`. When `initial` is omitted, any value is allowed on insert.
|
|
116
|
-
* - **Update**: the arc `(existing[field] → incoming[field])` must be
|
|
117
|
-
* listed in `transitions[from]`, else `IllegalTransitionError`. A
|
|
118
|
-
* same-value write (`from === to`) is allowed when `allowIdempotent`
|
|
119
|
-
* (default `true`) — so writes that touch other fields without moving
|
|
120
|
-
* state pass.
|
|
121
|
-
* - **Override**: inside an `amendment` transaction by an authorized role
|
|
122
|
-
* the check is skipped and the change is ledgered (mirrors every guard).
|
|
123
|
-
*
|
|
124
|
-
* The status graph is caller-supplied data — no UI, no domain logic.
|
|
125
|
-
*/
|
|
126
|
-
|
|
127
|
-
interface TransitionGuardConfig<T extends Record<string, unknown>> {
|
|
128
|
-
/** The collection whose state field is governed. */
|
|
129
|
-
collection: string;
|
|
130
|
-
/** The state field on the record (e.g. `'status'`). */
|
|
131
|
-
field: keyof T & string;
|
|
132
|
-
/**
|
|
133
|
-
* The transition graph: each state maps to the states it may move to.
|
|
134
|
-
* A state absent from the map (or mapped to `[]`) is terminal — no
|
|
135
|
-
* outgoing arc, so any non-idempotent write from it is rejected.
|
|
136
|
-
*/
|
|
137
|
-
transitions: Readonly<Record<string, readonly string[]>>;
|
|
138
|
-
/**
|
|
139
|
-
* States allowed as the initial value on insert (`existing === null`).
|
|
140
|
-
* Omit to allow any value on insert.
|
|
141
|
-
*/
|
|
142
|
-
initial?: readonly string[];
|
|
143
|
-
/**
|
|
144
|
-
* Allow a same-value write (`from === to`) on update. Default `true` —
|
|
145
|
-
* lets a put that changes other fields, but not the state, through.
|
|
146
|
-
*/
|
|
147
|
-
allowIdempotent?: boolean;
|
|
148
|
-
/** Roles permitted to override via an amendment transaction. Default `['admin', 'owner']`. */
|
|
149
|
-
amendmentRoles?: ReadonlyArray<'admin' | 'owner'>;
|
|
150
|
-
/**
|
|
151
|
-
* Optional set-level invariant run over the amendment change-set after
|
|
152
|
-
* the writes execute. Signature matches `GuardStrategy.amendment.invariant`
|
|
153
|
-
* exactly. When omitted the amendment is unconditionally allowed (the
|
|
154
|
-
* amendment itself is the sanctioned, ledgered override) — the
|
|
155
|
-
* backward-compatible default. Mirrors {@link immutableGuard}.
|
|
156
|
-
*/
|
|
157
|
-
amendmentInvariant?: (changes: ReadonlyArray<GuardChange<T>>, ctx: GuardContext<T>) => Promise<void> | void;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Build a state-machine transition guard. Pass the returned handle to
|
|
161
|
-
* `createNoydb({ guardStrategies: [...] })`.
|
|
162
|
-
*/
|
|
163
|
-
declare function transitionGuard<T extends Record<string, unknown>>(config: TransitionGuardConfig<T>): GuardStrategyHandle<T>;
|
|
164
|
-
|
|
165
|
-
export { type ImmutableGuardConfig as I, type TransitionGuardConfig as T, immutableGuard as i, transitionGuard as t, withGuard as w };
|