@docstack/client 0.1.8 → 0.2.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/README.md +350 -122
- package/lib/core/index.d.ts +3 -1
- package/lib/core/stack.d.ts +147 -0
- package/lib/core/sync/index.d.ts +17 -2
- package/lib/core/transaction-engine/errors.d.ts +57 -0
- package/lib/core/transaction-engine/handle.d.ts +165 -0
- package/lib/core/transaction-engine/index.d.ts +82 -0
- package/lib/core/transaction-engine/overlay.d.ts +66 -0
- package/lib/core/transaction-engine/stage.d.ts +50 -0
- package/lib/core/transaction-engine/sweep.d.ts +26 -0
- package/lib/index.d.ts +12 -2
- package/lib/index.js +4083 -267
- package/lib/index.umd.js +4094 -269
- package/lib/utils/index.d.ts +4 -2
- package/package.json +2 -1
package/lib/core/stack.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { JobEngine } from "./job-engine/index.js";
|
|
|
9
9
|
import { JobScheduler } from "./job-engine/scheduler.js";
|
|
10
10
|
import { PolicyEngine } from "./policy-engine/index.js";
|
|
11
11
|
import { CryptoEngine } from "./crypto-engine/index.js";
|
|
12
|
+
import { TransactionEngine, TransactionHandle, TransactionStage, TransactionCommitReport } from "./transaction-engine/index.js";
|
|
12
13
|
import type { ContentExport, ContentExportOptions, ContentImportOptions, ContentImportReport } from "./content-transfer.js";
|
|
13
14
|
export declare const BASE_SCHEMA: ClassModel["schema"];
|
|
14
15
|
export declare const CLASS_SCHEMA: ClassModel["schema"];
|
|
@@ -168,6 +169,11 @@ declare class ClientStack extends Stack {
|
|
|
168
169
|
* Handles key derivation (PBKDF2) and AES-GCM encryption.
|
|
169
170
|
*/
|
|
170
171
|
cryptoEngine: CryptoEngine;
|
|
172
|
+
/**
|
|
173
|
+
* Named write transactions (ADR-0039). Dormant unless the stack was opened with
|
|
174
|
+
* `transactions: true`; see {@link beginTransaction}.
|
|
175
|
+
*/
|
|
176
|
+
transactionEngine: TransactionEngine;
|
|
171
177
|
schemaVersion: string | undefined;
|
|
172
178
|
/**
|
|
173
179
|
* The current authenticated user session, if any.
|
|
@@ -521,7 +527,64 @@ declare class ClientStack extends Stack {
|
|
|
521
527
|
*
|
|
522
528
|
* @param patches - Patches not yet present in this stack.
|
|
523
529
|
*/
|
|
530
|
+
/**
|
|
531
|
+
* The highest consumer patch version this device has applied, from the patch
|
|
532
|
+
* ledger - `null` when no consumer patch has ever applied (or they are all
|
|
533
|
+
* deferred, which for the schema gate is the same thing: the schema those
|
|
534
|
+
* patches install is not here yet). The sync layer folds this into what it
|
|
535
|
+
* publishes and compares, so consumer-schema skew between devices refuses at
|
|
536
|
+
* the gate instead of pulling documents this device's schema cannot describe
|
|
537
|
+
* (ADR-0040).
|
|
538
|
+
*/
|
|
539
|
+
getConsumerSchemaVersion(): Promise<string | null>;
|
|
524
540
|
private applyConsumerPatches;
|
|
541
|
+
/**
|
|
542
|
+
* The stack a patch job executes against (ADR-0044): reads see the chain
|
|
543
|
+
* transaction's overlay, writes stage into it - so a migration's data
|
|
544
|
+
* transformation lands in the same commit as the model it prepares, or not at
|
|
545
|
+
* all. While the stack is locked, class-aware reads of an encrypting class
|
|
546
|
+
* THROW instead of serving the null convention: a migration wants the refusal
|
|
547
|
+
* (a `requiresKey: false` job that was declared wrongly must fail loudly, and
|
|
548
|
+
* the chain converts that failure to a deferral). Raw reads (`db.find`) bypass
|
|
549
|
+
* the class-aware path by design and stay the author's responsibility.
|
|
550
|
+
*/
|
|
551
|
+
private createPatchJobStack;
|
|
552
|
+
/**
|
|
553
|
+
* Runs one of a patch's one-shot jobs against the transaction facade. The run
|
|
554
|
+
* receipt is a `~JobRun` with NO `jobId` - patch jobs are deliberately never
|
|
555
|
+
* persisted as `~Job` documents, so there is no row to point at (`~sys-0.0.17`
|
|
556
|
+
* made the foreign key optional for exactly this) - carrying the patch
|
|
557
|
+
* identity in `runtimeArgs`. It writes DIRECTLY, win or lose: a failed
|
|
558
|
+
* migration's receipt is the troubleshooting trail and must survive the
|
|
559
|
+
* discard that protects everything else (ADR-0044).
|
|
560
|
+
*/
|
|
561
|
+
private runPatchJob;
|
|
562
|
+
/**
|
|
563
|
+
* The ADR-0042 protocol: the whole pending chain stages through one internal
|
|
564
|
+
* transaction - patch N+1 hydrates against the classes patch N staged, so the
|
|
565
|
+
* ADR-0038 merge composes in memory before anything is real - propagation is
|
|
566
|
+
* validated dry with nothing kept, and one commit lands every staged doc (class
|
|
567
|
+
* models and data documents alike, since the mixed extension) as one batch
|
|
568
|
+
* through the unchanged pipeline, where real propagation runs. The ledger
|
|
569
|
+
* (ADR-0041) arms only after that commit; any refusal beforehand persists
|
|
570
|
+
* nothing and names the patch at fault.
|
|
571
|
+
*/
|
|
572
|
+
private applyConsumerPatchChain;
|
|
573
|
+
/**
|
|
574
|
+
* Stages one patch's documents into the chain transaction. Hydration reads
|
|
575
|
+
* through the transaction's overlay, so an `_rev: "auto"` document merges onto
|
|
576
|
+
* what an earlier patch staged - or onto committed state when the chain has not
|
|
577
|
+
* touched it.
|
|
578
|
+
*/
|
|
579
|
+
private stagePatch;
|
|
580
|
+
/**
|
|
581
|
+
* ADR-0042 §3 - propagation, validated dry: for every class the chain staged
|
|
582
|
+
* over a committed predecessor, run the schema delta across the class's
|
|
583
|
+
* committed documents and keep nothing. The point is the refusal - a document
|
|
584
|
+
* that cannot satisfy the new model fails here, before the first write, naming
|
|
585
|
+
* the patch, the document and the attribute.
|
|
586
|
+
*/
|
|
587
|
+
private validateChainPropagation;
|
|
525
588
|
/**
|
|
526
589
|
* Decides whether applying a patch would write an encrypted attribute.
|
|
527
590
|
*
|
|
@@ -532,6 +595,9 @@ declare class ClientStack extends Stack {
|
|
|
532
595
|
* simulate schema evolution ahead of time.
|
|
533
596
|
*
|
|
534
597
|
* @param patch - The patch about to be applied.
|
|
598
|
+
* @param stagedSchema - Chain staging only (ADR-0042): resolves a class the
|
|
599
|
+
* current transaction already staged, so patch N+1 defers exactly as it would
|
|
600
|
+
* have when patch N had committed.
|
|
535
601
|
* @returns `true` if any document in it belongs to a class with encrypted attributes.
|
|
536
602
|
*/
|
|
537
603
|
private patchNeedsDocumentKey;
|
|
@@ -598,6 +664,34 @@ declare class ClientStack extends Stack {
|
|
|
598
664
|
getLastDocId(): Promise<number>;
|
|
599
665
|
getSystem(): Promise<SystemDoc>;
|
|
600
666
|
private loadPatches;
|
|
667
|
+
/**
|
|
668
|
+
* Finds a patch's ledger entry, raw - ledger documents are read outside the
|
|
669
|
+
* `active: true` visibility convention because `active` carries the ledger's own
|
|
670
|
+
* meaning here: `true` is applied, `false` is deferred, absent is a legacy entry
|
|
671
|
+
* from before the flag (treated as applied). See ADR-0041.
|
|
672
|
+
*/
|
|
673
|
+
private findPatchLedgerEntry;
|
|
674
|
+
/**
|
|
675
|
+
* Records a successful application: the ledger entry arms with `active: true` -
|
|
676
|
+
* flipping the deferral entry in place when one exists, so a replayed patch does
|
|
677
|
+
* not duplicate its record.
|
|
678
|
+
*/
|
|
679
|
+
private recordPatchApplication;
|
|
680
|
+
/**
|
|
681
|
+
* Records a deferral: the patch is known but dormant (`active: false`), waiting
|
|
682
|
+
* on the document key. The entry is what makes a deferred device honest at the
|
|
683
|
+
* sync gate - {@link getConsumerSchemaVersion} does not count it.
|
|
684
|
+
*/
|
|
685
|
+
private recordPatchDeferral;
|
|
686
|
+
/**
|
|
687
|
+
* The ADR-0038 half of patch hydration: `schema` does not ride the shallow
|
|
688
|
+
* merge, which would replace it wholesale - it merges attribute by attribute.
|
|
689
|
+
* A patch states only the attributes it changes, an absent attribute stays as
|
|
690
|
+
* stored, and an explicit `null` entry drops the attribute - from the model
|
|
691
|
+
* here, and from the documents when the write propagates. Shared by
|
|
692
|
+
* {@link applyPatch} and the chain staging of ADR-0042 so the two cannot drift.
|
|
693
|
+
*/
|
|
694
|
+
private static mergePatchSchema;
|
|
601
695
|
applyPatch: (patch: Patch) => Promise<string>;
|
|
602
696
|
private applyPatches;
|
|
603
697
|
checkSystem(): Promise<void>;
|
|
@@ -801,6 +895,27 @@ declare class ClientStack extends Stack {
|
|
|
801
895
|
* Removes event listeners and terminates background workers.
|
|
802
896
|
*/
|
|
803
897
|
close: () => void;
|
|
898
|
+
/**
|
|
899
|
+
* Opens a named write transaction (ADR-0039). Requires the stack to have been
|
|
900
|
+
* opened with `transactions: true`.
|
|
901
|
+
*
|
|
902
|
+
* Writes through the handle validate at the call site and stage in memory;
|
|
903
|
+
* reads through it see the staged state overlaid on committed state. Nothing
|
|
904
|
+
* reaches the database - or replication, or any other reader - until
|
|
905
|
+
* {@link commit}. `stack.db` stays live and unchanged next to open transactions:
|
|
906
|
+
* direct writes land immediately, and only touch a transaction by making its
|
|
907
|
+
* commit refuse when they advance a staged document's revision.
|
|
908
|
+
*/
|
|
909
|
+
beginTransaction: () => TransactionHandle;
|
|
910
|
+
/**
|
|
911
|
+
* Flushes a transaction's staged writes as one batch through the authoring
|
|
912
|
+
* pipeline. On refusal - validation, or a document changed underneath - nothing
|
|
913
|
+
* is persisted and the transaction stays open. The report says what landed and
|
|
914
|
+
* on what storage guarantee (`adapter.atomicBatch`).
|
|
915
|
+
*/
|
|
916
|
+
commit: (t: TransactionHandle | string) => Promise<TransactionCommitReport>;
|
|
917
|
+
/** Drops a transaction's staged writes. Idempotent. */
|
|
918
|
+
discardTransaction: (t: TransactionHandle | string) => void;
|
|
804
919
|
/**
|
|
805
920
|
* Retrieves a Class instance by name.
|
|
806
921
|
* Results are cached for 15 minutes to improve performance.
|
|
@@ -900,6 +1015,26 @@ declare class ClientStack extends Stack {
|
|
|
900
1015
|
[key: string]: any;
|
|
901
1016
|
docs: T[];
|
|
902
1017
|
}>;
|
|
1018
|
+
/**
|
|
1019
|
+
* {@link findDocuments} with an optional transaction stage overlaid - the shared
|
|
1020
|
+
* implementation, so a transaction's reads and ordinary reads run the identical
|
|
1021
|
+
* pipeline (policy, decryption, field visibility) and cannot drift (ADR-0039).
|
|
1022
|
+
*
|
|
1023
|
+
* With a stage: the database's index cannot see staged documents, so the
|
|
1024
|
+
* committed query runs unwindowed, staged ids mask their committed rows, staged
|
|
1025
|
+
* matches join the set, and sort/skip/limit apply after the merge. A selector
|
|
1026
|
+
* over a class the stage never touched skips all of that.
|
|
1027
|
+
*
|
|
1028
|
+
* @internal
|
|
1029
|
+
*/
|
|
1030
|
+
findDocumentsForView: <T extends Document | RelationDocument = Document>(stage: TransactionStage | undefined, selector: {
|
|
1031
|
+
[key: string]: any;
|
|
1032
|
+
}, fields?: string[], skip?: number, limit?: number, sort?: {
|
|
1033
|
+
[field: string]: "asc" | "desc";
|
|
1034
|
+
}[]) => Promise<{
|
|
1035
|
+
[key: string]: any;
|
|
1036
|
+
docs: T[];
|
|
1037
|
+
}>;
|
|
903
1038
|
/**
|
|
904
1039
|
* Runs raw fetched documents through the read pipeline: per-document policy
|
|
905
1040
|
* check, decryption, and field visibility. Shared by {@link findDocuments} and
|
|
@@ -1153,6 +1288,18 @@ declare class ClientStack extends Stack {
|
|
|
1153
1288
|
rows: any;
|
|
1154
1289
|
ast: (SelectAST | UnionAST)[];
|
|
1155
1290
|
}>;
|
|
1291
|
+
/**
|
|
1292
|
+
* {@link query}'s implementation, with the executor's data source as a
|
|
1293
|
+
* parameter: the executor reaches documents only through stack APIs, so a
|
|
1294
|
+
* transaction hands in a facade that routes them at its overlay while everything
|
|
1295
|
+
* else - parsing, binding, planning - stays exactly this code (ADR-0039).
|
|
1296
|
+
*
|
|
1297
|
+
* @internal
|
|
1298
|
+
*/
|
|
1299
|
+
runQuery: (sql: string, params: any[], execStack: ClientStack) => Promise<{
|
|
1300
|
+
rows: any;
|
|
1301
|
+
ast: (SelectAST | UnionAST)[];
|
|
1302
|
+
}>;
|
|
1156
1303
|
/**
|
|
1157
1304
|
* Executes a SQL query as an async stream of rows.
|
|
1158
1305
|
*
|
package/lib/core/sync/index.d.ts
CHANGED
|
@@ -142,7 +142,9 @@ export declare class SyncSchemaMismatchError extends Error {
|
|
|
142
142
|
readonly localVersion: string | undefined;
|
|
143
143
|
/** The schema version the remote was last written with. */
|
|
144
144
|
readonly remoteVersion: string;
|
|
145
|
-
|
|
145
|
+
/** Which half of the gate refused: the system schema, or the application's consumer patches. */
|
|
146
|
+
readonly scope: "system" | "consumer";
|
|
147
|
+
constructor(stack: string, localVersion: string | undefined, remoteVersion: string, scope?: "system" | "consumer");
|
|
146
148
|
}
|
|
147
149
|
/**
|
|
148
150
|
* The document DocStack keeps on a remote to record which schema wrote it.
|
|
@@ -158,6 +160,12 @@ export interface SyncMetaDoc {
|
|
|
158
160
|
_rev?: string;
|
|
159
161
|
/** Highest schema version any device has pushed to this remote. */
|
|
160
162
|
schemaVersion?: string;
|
|
163
|
+
/**
|
|
164
|
+
* Highest *consumer* patch version any device has pushed. The system version
|
|
165
|
+
* alone cannot see consumer-schema skew - two devices on the same build always
|
|
166
|
+
* agree on it, whatever their application patches are doing (ADR-0040).
|
|
167
|
+
*/
|
|
168
|
+
consumerSchemaVersion?: string;
|
|
161
169
|
/** Application version of the device that last wrote it, for diagnostics. */
|
|
162
170
|
appVersion?: string;
|
|
163
171
|
/** When it was last written. */
|
|
@@ -173,6 +181,13 @@ export interface SyncMetaDoc {
|
|
|
173
181
|
* @returns The recorded version, or `null` for a remote nobody has written yet.
|
|
174
182
|
*/
|
|
175
183
|
export declare const readRemoteSchemaVersion: (remote: PouchDB.Database) => Promise<string | null>;
|
|
184
|
+
/**
|
|
185
|
+
* Reads the highest consumer patch version recorded on a remote.
|
|
186
|
+
*
|
|
187
|
+
* `null` for a remote nobody has written, or one written only by builds that
|
|
188
|
+
* predate the consumer half of the gate.
|
|
189
|
+
*/
|
|
190
|
+
export declare const readRemoteConsumerSchemaVersion: (remote: PouchDB.Database) => Promise<string | null>;
|
|
176
191
|
/**
|
|
177
192
|
* Records this device's schema version on a remote, if it is the newest seen.
|
|
178
193
|
*
|
|
@@ -180,7 +195,7 @@ export declare const readRemoteSchemaVersion: (remote: PouchDB.Database) => Prom
|
|
|
180
195
|
* @param schemaVersion - The local schema version; a missing value writes nothing.
|
|
181
196
|
* @param appVersion - The local application version, stored for diagnostics.
|
|
182
197
|
*/
|
|
183
|
-
export declare const publishSchemaVersion: (remote: PouchDB.Database, schemaVersion: string | undefined, appVersion?: string) => Promise<void>;
|
|
198
|
+
export declare const publishSchemaVersion: (remote: PouchDB.Database, schemaVersion: string | undefined, appVersion?: string, consumerSchemaVersion?: string | null) => Promise<void>;
|
|
184
199
|
/**
|
|
185
200
|
* One stack's replication: its lifecycle, its filter, and its convergence state.
|
|
186
201
|
*
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Errors of the transaction engine (ADR-0039).
|
|
3
|
+
*
|
|
4
|
+
* Every one of them leaves the database untouched: a transaction failure is a refusal,
|
|
5
|
+
* never a partial application. The one exception is named where it happens -
|
|
6
|
+
* a commit on a non-atomic adapter can land a subset, and that outcome is reported as
|
|
7
|
+
* a `partial` status on the handle, not thrown as one of these.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
/** Raised by `beginTransaction()` on a stack opened without `transactions: true`. */
|
|
12
|
+
export declare class TransactionsDisabledError extends Error {
|
|
13
|
+
name: string;
|
|
14
|
+
constructor(stackName: string);
|
|
15
|
+
}
|
|
16
|
+
/** Raised when a handle is used in a state that cannot accept the operation. */
|
|
17
|
+
export declare class TransactionStateError extends Error {
|
|
18
|
+
name: string;
|
|
19
|
+
constructor(transactionId: string, status: string, operation: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Raised when the validation sweep refuses a document - at stage time (the write is
|
|
23
|
+
* not staged) or at commit time (nothing is written, the transaction stays open).
|
|
24
|
+
*/
|
|
25
|
+
export declare class TransactionValidationError extends Error {
|
|
26
|
+
name: string;
|
|
27
|
+
/** The document that failed. */
|
|
28
|
+
readonly docId: string | undefined;
|
|
29
|
+
constructor(message: string, docId?: string);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Raised by commit when a staged document's base revision no longer matches the
|
|
33
|
+
* stored winner - a direct write, another transaction's commit, or replication moved
|
|
34
|
+
* it. Nothing is written; the transaction stays open for re-staging or discard.
|
|
35
|
+
*/
|
|
36
|
+
export declare class TransactionConflictError extends Error {
|
|
37
|
+
name: string;
|
|
38
|
+
readonly conflicts: {
|
|
39
|
+
id: string;
|
|
40
|
+
baseRev: string | undefined;
|
|
41
|
+
currentRev: string | undefined;
|
|
42
|
+
}[];
|
|
43
|
+
constructor(conflicts: {
|
|
44
|
+
id: string;
|
|
45
|
+
baseRev: string | undefined;
|
|
46
|
+
currentRev: string | undefined;
|
|
47
|
+
}[]);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Raised at stage time for documents transactions cannot carry: class models (their
|
|
51
|
+
* write propagates to other documents mid-pipeline and cannot be staged or rolled
|
|
52
|
+
* back - ADR-0039), `_local/` device state, and design documents.
|
|
53
|
+
*/
|
|
54
|
+
export declare class TransactionUnsupportedDocError extends Error {
|
|
55
|
+
name: string;
|
|
56
|
+
constructor(docId: string, reason: string);
|
|
57
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Document } from "@docstack/shared";
|
|
2
|
+
import type ClientStack from "../stack.js";
|
|
3
|
+
import { TransactionStage, StagedEntry } from "./stage.js";
|
|
4
|
+
import { MangoSort } from "./overlay.js";
|
|
5
|
+
import type { TransactionEngine, TransactionCommitReport } from "./index.js";
|
|
6
|
+
export type TransactionStatus = "open" | "committed" | "discarded" | "partial";
|
|
7
|
+
/**
|
|
8
|
+
* One transaction: a private write journal plus a read view that overlays it on
|
|
9
|
+
* committed state (ADR-0039).
|
|
10
|
+
*
|
|
11
|
+
* Writes through the handle are validated at the call site (the sweep - failing
|
|
12
|
+
* stages nothing) and stage in memory; nothing reaches the database until
|
|
13
|
+
* {@link commit}, which flushes the journal as one batch through the stack's full
|
|
14
|
+
* authoring pipeline. Reads through the handle see the journal; `stack.db`, other
|
|
15
|
+
* handles, replication and live subscriptions see only committed state.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const t = stack.beginTransaction();
|
|
20
|
+
* await t.createDoc(null, "Task", { title: "write-up" });
|
|
21
|
+
* await t.db.put({ ...(await t.db.get("Task-77")), done: true });
|
|
22
|
+
* const drafted = await t.findDocuments({ "~class": { $eq: "Task" } });
|
|
23
|
+
* const report = await t.commit(); // or stack.commit(t)
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare class TransactionHandle {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
/** @internal */
|
|
29
|
+
readonly stage: TransactionStage;
|
|
30
|
+
/** @internal - ids this handle minted, counted into the id counter after commit. */
|
|
31
|
+
readonly mintedIds: Set<string>;
|
|
32
|
+
/**
|
|
33
|
+
* True for handles DocStack mints for its own machinery (patch application,
|
|
34
|
+
* ADR-0042). Internal handles may stage class models; public ones refuse them.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
readonly internal: boolean;
|
|
38
|
+
private statusValue;
|
|
39
|
+
private readonly stack;
|
|
40
|
+
private readonly engine;
|
|
41
|
+
/** The db-like surface: staged writes, overlaid reads. */
|
|
42
|
+
readonly db: TransactionDb;
|
|
43
|
+
/** @internal */
|
|
44
|
+
constructor(stack: ClientStack, engine: TransactionEngine, id: string, internal?: boolean);
|
|
45
|
+
get status(): TransactionStatus;
|
|
46
|
+
/** @internal */
|
|
47
|
+
setStatus(status: TransactionStatus): void;
|
|
48
|
+
stagedCount(): number;
|
|
49
|
+
/** @internal */
|
|
50
|
+
assertWritable(operation: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Stages a write. The sweep runs first: a document that fails validation, policy,
|
|
53
|
+
* or the locked-stack check is not staged and the journal is untouched.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
stageWrite(doc: Document, op?: "write" | "delete"): Promise<StagedEntry>;
|
|
57
|
+
/**
|
|
58
|
+
* Creates or updates a document in the transaction - `stack.createDoc`'s UX with
|
|
59
|
+
* a staged destination: `docId: null` mints an id, an existing id merges params
|
|
60
|
+
* over the overlay-visible document.
|
|
61
|
+
*/
|
|
62
|
+
createDoc(docId: string | null, type: string, params: {
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
}): Promise<Document>;
|
|
65
|
+
/** Batch counterpart of {@link createDoc}; validated sequentially, fail-fast. */
|
|
66
|
+
createDocs(docs: {
|
|
67
|
+
docId: string | null;
|
|
68
|
+
params: {
|
|
69
|
+
[key: string]: any;
|
|
70
|
+
};
|
|
71
|
+
}[], type: string): Promise<Document[]>;
|
|
72
|
+
/** Soft-deletes in the transaction: the overlay stops showing the document under the default `active: true`. */
|
|
73
|
+
deleteDocument(docId: string): Promise<boolean>;
|
|
74
|
+
/** The stack's polished read, against this transaction's view. */
|
|
75
|
+
findDocuments<T extends Document = Document>(selector: {
|
|
76
|
+
[key: string]: any;
|
|
77
|
+
}, fields?: string[], skip?: number, limit?: number, sort?: MangoSort): Promise<{
|
|
78
|
+
[key: string]: any;
|
|
79
|
+
docs: T[];
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* SQL against this transaction's view. The executor reaches data only through
|
|
83
|
+
* stack APIs, so a facade routes them at the overlay; LIMIT/OFFSET pushdown and
|
|
84
|
+
* sort indexes are disabled while staged - staged documents exist in no index,
|
|
85
|
+
* so windows and orderings must be computed after the merge.
|
|
86
|
+
*/
|
|
87
|
+
query(sql: string, ...params: any[]): Promise<{
|
|
88
|
+
rows: any;
|
|
89
|
+
ast: (import("@docstack/shared").SelectAST | import("@docstack/shared").UnionAST)[];
|
|
90
|
+
}>;
|
|
91
|
+
/** Flushes the journal - sugar for `stack.commit(t)`. */
|
|
92
|
+
commit(): Promise<TransactionCommitReport>;
|
|
93
|
+
/** Drops the journal - sugar for `stack.discardTransaction(t)`. */
|
|
94
|
+
discard(): void;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The handle's db-like surface. Writes stage; reads overlay. Not a Proxy over the
|
|
98
|
+
* guarded db on purpose: this object *has no* adapter methods or escape hatches to
|
|
99
|
+
* forward, so staging cannot become a fourth door around the authoring path.
|
|
100
|
+
*/
|
|
101
|
+
export declare class TransactionDb {
|
|
102
|
+
private readonly stack;
|
|
103
|
+
private readonly handle;
|
|
104
|
+
/** @internal */
|
|
105
|
+
constructor(stack: ClientStack, handle: TransactionHandle);
|
|
106
|
+
get(docId: string, options?: any): Promise<Document>;
|
|
107
|
+
bulkGet(request: {
|
|
108
|
+
docs: {
|
|
109
|
+
id: string;
|
|
110
|
+
rev?: string;
|
|
111
|
+
}[];
|
|
112
|
+
}): Promise<{
|
|
113
|
+
results: any[];
|
|
114
|
+
}>;
|
|
115
|
+
/**
|
|
116
|
+
* Raw-style Mango find over the transaction's view. Like `stack.db.find`, this
|
|
117
|
+
* skips the read pipeline (no policy filter, no decryption of committed rows);
|
|
118
|
+
* `findDocuments` on the handle is the polished read.
|
|
119
|
+
*/
|
|
120
|
+
find(query: {
|
|
121
|
+
selector: {
|
|
122
|
+
[key: string]: any;
|
|
123
|
+
};
|
|
124
|
+
fields?: string[];
|
|
125
|
+
skip?: number;
|
|
126
|
+
limit?: number;
|
|
127
|
+
sort?: any;
|
|
128
|
+
}): Promise<{
|
|
129
|
+
docs: Document[];
|
|
130
|
+
}>;
|
|
131
|
+
put(doc: Document, options?: any): Promise<{
|
|
132
|
+
ok: true;
|
|
133
|
+
id: string;
|
|
134
|
+
rev?: string;
|
|
135
|
+
staged: true;
|
|
136
|
+
}>;
|
|
137
|
+
post(doc: Document): Promise<{
|
|
138
|
+
ok: true;
|
|
139
|
+
id: string;
|
|
140
|
+
rev?: string;
|
|
141
|
+
staged: true;
|
|
142
|
+
}>;
|
|
143
|
+
/** Hard removal, staged: the commit writes `_deleted: true`. Soft deletion is `handle.deleteDocument`. */
|
|
144
|
+
remove(doc: {
|
|
145
|
+
_id: string;
|
|
146
|
+
_rev?: string;
|
|
147
|
+
} | string, rev?: string): Promise<{
|
|
148
|
+
ok: true;
|
|
149
|
+
id: string;
|
|
150
|
+
staged: true;
|
|
151
|
+
}>;
|
|
152
|
+
/**
|
|
153
|
+
* Stages a batch. Validated sequentially - the first refusal unwinds every entry
|
|
154
|
+
* this call staged, so a failing batch stages nothing. Documents stage before
|
|
155
|
+
* relations, mirroring the commit batch, so a relation and its endpoint can
|
|
156
|
+
* arrive in one array in any order.
|
|
157
|
+
*/
|
|
158
|
+
bulkDocs(docs: Document[] | {
|
|
159
|
+
docs: Document[];
|
|
160
|
+
}, options?: any): Promise<{
|
|
161
|
+
ok: true;
|
|
162
|
+
id: string;
|
|
163
|
+
staged: true;
|
|
164
|
+
}[]>;
|
|
165
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type ClientStack from "../stack.js";
|
|
2
|
+
import { TransactionHandle } from "./handle.js";
|
|
3
|
+
export { TransactionHandle, TransactionDb } from "./handle.js";
|
|
4
|
+
export type { TransactionStatus } from "./handle.js";
|
|
5
|
+
export { TransactionStage } from "./stage.js";
|
|
6
|
+
export type { StagedEntry, StagedOp } from "./stage.js";
|
|
7
|
+
export { stageCoversSelector, mergeStageIntoResults, widenProjection } from "./overlay.js";
|
|
8
|
+
export { classFromStage } from "./sweep.js";
|
|
9
|
+
export { TransactionsDisabledError, TransactionStateError, TransactionValidationError, TransactionConflictError, TransactionUnsupportedDocError, } from "./errors.js";
|
|
10
|
+
/** What one commit did, and on what guarantee. */
|
|
11
|
+
export type TransactionCommitReport = {
|
|
12
|
+
transactionId: string;
|
|
13
|
+
/** Documents that landed, with their new revisions. */
|
|
14
|
+
written: {
|
|
15
|
+
id: string;
|
|
16
|
+
rev: string;
|
|
17
|
+
}[];
|
|
18
|
+
/** Documents that did not - possible only on adapters where `atomicBatch` is false. */
|
|
19
|
+
failed: {
|
|
20
|
+
id: string;
|
|
21
|
+
error: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
}[];
|
|
24
|
+
/** Journal size at the moment commit ran. */
|
|
25
|
+
stagedCount: number;
|
|
26
|
+
durationMs: number;
|
|
27
|
+
/**
|
|
28
|
+
* The storage adapter's honest guarantee for this commit: `atomicBatch: true`
|
|
29
|
+
* means the batch landed (or failed) as one storage transaction; `false` means
|
|
30
|
+
* per-document results, mitigated by the rev pre-flight but not eliminated.
|
|
31
|
+
*/
|
|
32
|
+
adapter: {
|
|
33
|
+
name: string;
|
|
34
|
+
atomicBatch: boolean;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Named write transactions for one stack (ADR-0039).
|
|
39
|
+
*
|
|
40
|
+
* Enabled per stack by `transactions: true` in its configuration - the flag only
|
|
41
|
+
* unlocks {@link begin}; direct writes stay immediate, and the framework's own
|
|
42
|
+
* writers (scheduler, jobs, sync) always write directly. The stage lives above the
|
|
43
|
+
* plugin: nothing a transaction does touches the database until commit, and commit
|
|
44
|
+
* is exactly one `stack.db.bulkDocs` through the full authoring pipeline.
|
|
45
|
+
*/
|
|
46
|
+
export declare class TransactionEngine {
|
|
47
|
+
private readonly stack;
|
|
48
|
+
private readonly enabled;
|
|
49
|
+
private readonly handles;
|
|
50
|
+
/** Commits serialize here so one commit's rev pre-flight cannot be invalidated by another's write. */
|
|
51
|
+
private commitChain;
|
|
52
|
+
constructor(stack: ClientStack, enabled: boolean);
|
|
53
|
+
isEnabled(): boolean;
|
|
54
|
+
/** How many transactions are currently open (or partial). */
|
|
55
|
+
openCount(): number;
|
|
56
|
+
begin(): TransactionHandle;
|
|
57
|
+
/**
|
|
58
|
+
* Opens a transaction for DocStack's own machinery - patch application
|
|
59
|
+
* (ADR-0042). Independent of the `transactions: true` config gate (the flag
|
|
60
|
+
* governs the consumer feature, not the framework's internals) and permitted to
|
|
61
|
+
* stage class models: an internal handle claims staged validation and a single
|
|
62
|
+
* class-write batch, never propagation atomicity.
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
beginInternal(): TransactionHandle;
|
|
66
|
+
private resolve;
|
|
67
|
+
/**
|
|
68
|
+
* Drops a transaction's journal. Idempotent, and a no-op on a handle already in
|
|
69
|
+
* a terminal state - discarding what is already gone is not an error.
|
|
70
|
+
*/
|
|
71
|
+
discard(t: TransactionHandle | string): void;
|
|
72
|
+
/** Discards every open transaction - what `close()` and `reset()` do. */
|
|
73
|
+
discardAll(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Flushes a transaction's journal as one batch through the stack's authoring
|
|
76
|
+
* pipeline. Refusals - validation, or a staged document whose base revision
|
|
77
|
+
* moved - throw with nothing persisted and the transaction still open.
|
|
78
|
+
*/
|
|
79
|
+
commit(t: TransactionHandle | string): Promise<TransactionCommitReport>;
|
|
80
|
+
private commitNow;
|
|
81
|
+
private adapterInfo;
|
|
82
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Document } from "@docstack/shared";
|
|
2
|
+
import { TransactionStage } from "./stage.js";
|
|
3
|
+
/** The sort shape `findDocuments` accepts. */
|
|
4
|
+
export type MangoSort = {
|
|
5
|
+
[field: string]: "asc" | "desc";
|
|
6
|
+
}[] | string[];
|
|
7
|
+
/**
|
|
8
|
+
* True when a query with this selector could see documents this stage holds.
|
|
9
|
+
*
|
|
10
|
+
* Derived from the selector's `~class` / `~domain` constraint against the stage's
|
|
11
|
+
* partitions; a selector naming no class is answered conservatively. This is the
|
|
12
|
+
* per-query fast path: a find over a class the transaction never touched runs
|
|
13
|
+
* exactly as it would outside the transaction.
|
|
14
|
+
*/
|
|
15
|
+
export declare const stageCoversSelector: (stage: TransactionStage, selector: {
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Fields the widened committed query (and the staged docs) must carry beyond the
|
|
20
|
+
* caller's projection, so masking, sorting and the read pipeline can work; the
|
|
21
|
+
* extras are stripped again after the merge.
|
|
22
|
+
*/
|
|
23
|
+
export declare const widenProjection: (fields: string[] | undefined, sort: MangoSort | undefined) => {
|
|
24
|
+
queryFields: string[] | undefined;
|
|
25
|
+
extras: string[];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Merges a stage into a committed query result: every staged id masks its committed
|
|
29
|
+
* row (superseded and deleted alike), staged writes matching the selector join the
|
|
30
|
+
* set, then sort, window and projection apply in memory - the database's index can
|
|
31
|
+
* never see a staged document, so the window has to be computed after the union.
|
|
32
|
+
*
|
|
33
|
+
* `committedDocs` must come from a query WITHOUT skip/limit (the mask changes what
|
|
34
|
+
* the window contains) and carrying `widenProjection`'s fields.
|
|
35
|
+
*/
|
|
36
|
+
export declare const mergeStageIntoResults: (stage: TransactionStage, selector: {
|
|
37
|
+
[key: string]: any;
|
|
38
|
+
}, committedDocs: Document[], options?: {
|
|
39
|
+
sort?: MangoSort;
|
|
40
|
+
skip?: number;
|
|
41
|
+
limit?: number;
|
|
42
|
+
fields?: string[];
|
|
43
|
+
extras?: string[];
|
|
44
|
+
}) => Document[];
|
|
45
|
+
/** A PouchDB-shaped `not_found`, so overlay reads refuse like the database does. */
|
|
46
|
+
export declare const notFoundError: (id: string) => any;
|
|
47
|
+
/**
|
|
48
|
+
* Read-your-writes for a point read: a staged delete is a 404, a staged write is the
|
|
49
|
+
* authored plaintext (its `_rev` is the base revision - the revision the commit will
|
|
50
|
+
* replace), anything else is the stack's ordinary decrypting read.
|
|
51
|
+
*/
|
|
52
|
+
export declare const overlayGet: (stack: {
|
|
53
|
+
db: any;
|
|
54
|
+
}, stage: TransactionStage, id: string, options?: any) => Promise<Document>;
|
|
55
|
+
/** `bulkGet` counterpart of {@link overlayGet}, preserving request order. */
|
|
56
|
+
export declare const overlayBulkGet: (stack: {
|
|
57
|
+
db: any;
|
|
58
|
+
}, stage: TransactionStage, request: {
|
|
59
|
+
docs: {
|
|
60
|
+
id: string;
|
|
61
|
+
rev?: string;
|
|
62
|
+
}[];
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
}) => Promise<{
|
|
65
|
+
results: any[];
|
|
66
|
+
}>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Document } from "@docstack/shared";
|
|
2
|
+
/** What one staged entry means for its document id. */
|
|
3
|
+
export type StagedOp = "write" | "delete";
|
|
4
|
+
export interface StagedEntry {
|
|
5
|
+
/** The authored document, plaintext, cloned at stage time. */
|
|
6
|
+
doc: Document;
|
|
7
|
+
/** The winning revision the entry was staged against; absent for a new document. */
|
|
8
|
+
baseRev?: string;
|
|
9
|
+
op: StagedOp;
|
|
10
|
+
/** True when the id did not exist (in stage or store) when first staged. */
|
|
11
|
+
isNew: boolean;
|
|
12
|
+
stagedAt: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The write journal of one transaction: authored documents keyed by id, in memory.
|
|
16
|
+
*
|
|
17
|
+
* Also partitioned by class (`~class`, or `~domain` for relations) so a read can ask
|
|
18
|
+
* "could this stage affect a query over class X" in O(1) - the overlay only pays the
|
|
19
|
+
* merge for queries whose class the transaction actually touched (ADR-0039).
|
|
20
|
+
*/
|
|
21
|
+
export declare class TransactionStage {
|
|
22
|
+
private entries;
|
|
23
|
+
private partitions;
|
|
24
|
+
private partitionKeys;
|
|
25
|
+
/**
|
|
26
|
+
* Stages an entry. Re-staging an id replaces the document but keeps the original
|
|
27
|
+
* `baseRev` and `isNew` - the conflict check is against the world as it was when
|
|
28
|
+
* the transaction first touched the id, not against its own previous draft.
|
|
29
|
+
*/
|
|
30
|
+
set(id: string, entry: StagedEntry): void;
|
|
31
|
+
get(id: string): StagedEntry | undefined;
|
|
32
|
+
has(id: string): boolean;
|
|
33
|
+
get size(): number;
|
|
34
|
+
ids(): string[];
|
|
35
|
+
/** Entries in stage order (insertion order of first staging). */
|
|
36
|
+
values(): StagedEntry[];
|
|
37
|
+
hasPartition(name: string): boolean;
|
|
38
|
+
/** Keeps only the given ids - what a partial commit leaves behind. */
|
|
39
|
+
retain(ids: Set<string>): void;
|
|
40
|
+
/**
|
|
41
|
+
* A point-in-time copy of the journal, for {@link restore}. Used by the patch
|
|
42
|
+
* chain (ADR-0044) to unwind exactly one patch's staging - a pre-apply job's
|
|
43
|
+
* writes included - when a locked refusal converts that patch to a deferral
|
|
44
|
+
* while the already-staged prefix goes on to commit.
|
|
45
|
+
*/
|
|
46
|
+
snapshot(): Map<string, StagedEntry>;
|
|
47
|
+
restore(snapshot: Map<string, StagedEntry>): void;
|
|
48
|
+
remove(id: string): void;
|
|
49
|
+
clear(): void;
|
|
50
|
+
}
|