@docstack/client 0.1.8 → 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/LICENSE +0 -0
- package/README.md +373 -132
- package/lib/core/attribute.d.ts +0 -0
- package/lib/core/class.d.ts +0 -0
- package/lib/core/content-transfer.d.ts +0 -0
- package/lib/core/crypto-engine/index.d.ts +63 -5
- package/lib/core/crypto-engine/utils.d.ts +10 -2
- package/lib/core/datamodel/index.d.ts +0 -0
- package/lib/core/domain.d.ts +0 -0
- package/lib/core/guarded-db.d.ts +0 -0
- package/lib/core/index.d.ts +4 -2
- package/lib/core/job-engine/index.d.ts +0 -0
- package/lib/core/job-engine/schedule.d.ts +0 -0
- package/lib/core/job-engine/scheduler.d.ts +0 -0
- package/lib/core/query-engine/accumulators.d.ts +0 -0
- package/lib/core/query-engine/classes.d.ts +0 -0
- package/lib/core/query-engine/evaluator.d.ts +0 -0
- package/lib/core/query-engine/executor.d.ts +0 -0
- package/lib/core/query-engine/index.d.ts +0 -0
- package/lib/core/query-engine/parser.d.ts +0 -0
- package/lib/core/query-engine/planner.d.ts +0 -0
- package/lib/core/stack.d.ts +223 -8
- package/lib/core/sync/class-filter.d.ts +0 -0
- package/lib/core/sync/filter-identity.d.ts +0 -0
- package/lib/core/sync/index.d.ts +17 -2
- package/lib/core/sync/internal-docs.d.ts +0 -0
- package/lib/core/sync/tenants.d.ts +0 -0
- package/lib/core/test-utils/docstack.d.ts +0 -0
- 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 +25 -0
- package/lib/core/trigger/index.d.ts +0 -0
- package/lib/index.d.ts +12 -2
- package/lib/index.js +4710 -733
- package/lib/index.umd.js +5259 -623
- package/lib/index2.js +641 -0
- package/lib/plugins/pouchdb.d.ts +16 -1
- package/lib/utils/crypto/index.d.ts +0 -0
- package/lib/utils/index.d.ts +4 -2
- package/lib/utils/logger/index.d.ts +0 -0
- package/lib/utils/logger/transport.d.ts +0 -0
- package/lib/workers/dataModel.d.ts +0 -0
- package/package.json +3 -1
- package/lib/core/policy-engine/index.d.ts +0 -132
|
@@ -42,6 +42,18 @@ export declare class CryptoEngine {
|
|
|
42
42
|
* interrupted, and resumed.
|
|
43
43
|
*/
|
|
44
44
|
private retiredKeys;
|
|
45
|
+
/**
|
|
46
|
+
* Scope CEKs admitted by {@link admitScopeKey}, by key id (ADR-0045).
|
|
47
|
+
*
|
|
48
|
+
* The keyring the single document key generalizes into: reads dispatch by a
|
|
49
|
+
* payload's `kid` across the legacy key, retired keys, and these; writes
|
|
50
|
+
* into a scope-labeled document select through {@link scopeWriteKeys}. A
|
|
51
|
+
* scope whose CEK is absent here is simply LOCKED - its payloads stay
|
|
52
|
+
* sealed, which is the access decision.
|
|
53
|
+
*/
|
|
54
|
+
private scopeKeys;
|
|
55
|
+
/** The read-write entry per scope id - the CEK that seals new writes. */
|
|
56
|
+
private scopeWriteKeys;
|
|
45
57
|
private readonly logger;
|
|
46
58
|
/** Reference to the parent stack. */
|
|
47
59
|
private readonly stack;
|
|
@@ -106,6 +118,37 @@ export declare class CryptoEngine {
|
|
|
106
118
|
* @returns Key identifiers; empty when no key is held.
|
|
107
119
|
*/
|
|
108
120
|
getReadableKeyIds(): string[];
|
|
121
|
+
/**
|
|
122
|
+
* Admits a scope's CEK into the keyring after its canary verified
|
|
123
|
+
* (spec 02 §2.1 - admission is the caller's `verifyScopeCek` first, this
|
|
124
|
+
* second). The winning version of a scope enters read-write and becomes
|
|
125
|
+
* the key new writes into the scope seal under; older versions enter
|
|
126
|
+
* read-only, the retired-keys discipline applied per scope.
|
|
127
|
+
*
|
|
128
|
+
* @returns The admitted key's id.
|
|
129
|
+
*/
|
|
130
|
+
admitScopeKey(scopeId: string, cekHex: string, version: number, mode: "read-write" | "read-only"): Promise<string>;
|
|
131
|
+
/**
|
|
132
|
+
* Tests a candidate CEK against a scope's canary WITHOUT admitting it -
|
|
133
|
+
* the ADR-0018 admission discipline per scope: a corrupted or
|
|
134
|
+
* rotated-away ciphertext is an error at unlock, not garbage later. The
|
|
135
|
+
* marker's AAD binds it to the scope and key it was minted for.
|
|
136
|
+
*/
|
|
137
|
+
verifyScopeCek(scopeId: string, cekHex: string, marker: unknown): Promise<boolean>;
|
|
138
|
+
/** Whether new writes into this scope can seal - a read-write CEK is held. */
|
|
139
|
+
isScopeWritable(scopeId: string): boolean;
|
|
140
|
+
/** Scope ids holding a read-write CEK. */
|
|
141
|
+
unlockedScopeIds(): string[];
|
|
142
|
+
/** The key id new writes into a scope seal under, when the scope is open. */
|
|
143
|
+
getScopeWriteKeyId(scopeId: string): string | undefined;
|
|
144
|
+
/** The scope a held key id belongs to, if it is a scope key. */
|
|
145
|
+
scopeOfKid(kid: string): string | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* Drops every admitted scope CEK - the session's material is gone, the
|
|
148
|
+
* scopes are locked again. Mirrors what clearing the document key does for
|
|
149
|
+
* the legacy path.
|
|
150
|
+
*/
|
|
151
|
+
dropScopeKeys(): void;
|
|
109
152
|
/**
|
|
110
153
|
* Generates a cryptographically secure random string.
|
|
111
154
|
* Useful for generating salts or nonces.
|
|
@@ -160,15 +203,23 @@ export declare class CryptoEngine {
|
|
|
160
203
|
unwrapAndStoreDocumentKey(wrappedDocumentKey?: string | null, derivedKey?: string | null): Promise<string>;
|
|
161
204
|
private getCryptoKey;
|
|
162
205
|
/**
|
|
163
|
-
* Chooses the
|
|
206
|
+
* Chooses the keyring entry that can open a payload.
|
|
164
207
|
*
|
|
165
208
|
* A payload names its key, so an old field found mid-re-key is decrypted with the key
|
|
166
209
|
* it was actually written under instead of failing against the current one. Payloads
|
|
167
|
-
* from before identifiers existed name nothing, and are tried against the
|
|
168
|
-
*
|
|
210
|
+
* from before identifiers existed name nothing, and are tried against the LEGACY
|
|
211
|
+
* document key only (spec 02 §3): under multiple keys, falling back to "whatever is
|
|
212
|
+
* current" would silently mis-route - a scope key never answers for an unnamed
|
|
213
|
+
* payload.
|
|
169
214
|
*/
|
|
170
|
-
private
|
|
215
|
+
private resolveEntryFor;
|
|
171
216
|
private encryptValue;
|
|
217
|
+
/**
|
|
218
|
+
* @param label - The document's `~scope` at read time. A scope-sealed
|
|
219
|
+
* payload authenticates against it (AAD, spec 02 §2.3 rule 3): a tampered
|
|
220
|
+
* or stripped label fails the GCM authentication and the payload stays
|
|
221
|
+
* sealed - the mismatch is detected, never silently honored.
|
|
222
|
+
*/
|
|
172
223
|
private decryptValue;
|
|
173
224
|
/**
|
|
174
225
|
* Identifies which document keys contain encrypted data.
|
|
@@ -187,7 +238,14 @@ export declare class CryptoEngine {
|
|
|
187
238
|
* @param document - The document to encrypt
|
|
188
239
|
* @param classObj - The class defining which fields to encrypt
|
|
189
240
|
*/
|
|
190
|
-
|
|
241
|
+
/**
|
|
242
|
+
* @param scopeId - The document's resolved scope label. When present, the
|
|
243
|
+
* scope's read-write CEK seals every attribute (stamped with its `kid`,
|
|
244
|
+
* bound to the label via AAD); the caller has already refused the write if
|
|
245
|
+
* the scope is not open. Absent, the legacy document key path applies
|
|
246
|
+
* unchanged.
|
|
247
|
+
*/
|
|
248
|
+
encryptDocument(document: Document, classObj: Class, scopeId?: string): Promise<void>;
|
|
191
249
|
/**
|
|
192
250
|
* Decrypts encrypted fields in a document after retrieval.
|
|
193
251
|
* Modifies the document in place.
|
|
@@ -44,8 +44,16 @@ export declare const isEncryptedPayload: (value: unknown) => value is EncryptedP
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
export declare const deriveKeyId: (hexKey: string) => Promise<string>;
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The additional-authenticated-data string binding a scope-sealed payload to
|
|
49
|
+
* its label (spec 02 §2.3 rule 3): decryption derives it from the DOCUMENT's
|
|
50
|
+
* `~scope` at read time, so a tampered or stripped label does not merely look
|
|
51
|
+
* inconsistent - the GCM authentication fails and the payload stays sealed.
|
|
52
|
+
* Legacy-key payloads pass no AAD, keeping pre-scope ciphertext readable.
|
|
53
|
+
*/
|
|
54
|
+
export declare const scopeAad: (scopeId: string, kid: string) => string;
|
|
55
|
+
export declare const encryptWithAesGcm: (plaintext: string, key: CryptoKey, kid?: string, aad?: string) => Promise<EncryptedPayload>;
|
|
56
|
+
export declare const decryptWithAesGcm: (payload: EncryptedPayload, key: CryptoKey, aad?: string) => Promise<string>;
|
|
49
57
|
export declare const wrapDocumentKey: (documentKey: string, derivedKeyHex: string) => Promise<string>;
|
|
50
58
|
export declare const unwrapDocumentKey: (wrappedDocumentKey: string, cryptoKey: CryptoKey, derivedKeyHex: string) => Promise<string>;
|
|
51
59
|
/**
|
|
File without changes
|
package/lib/core/domain.d.ts
CHANGED
|
File without changes
|
package/lib/core/guarded-db.d.ts
CHANGED
|
File without changes
|
package/lib/core/index.d.ts
CHANGED
|
@@ -304,7 +304,7 @@ export { JOB_SCHEDULE_DOC_ID } from "./job-engine/scheduler.js";
|
|
|
304
304
|
export type { SchedulerOptions, SchedulerHost, JobScheduleState, TickReport, SkipReason, } from "./job-engine/scheduler.js";
|
|
305
305
|
export { parseSchedule, nextOccurrence, MIN_PERIOD_MS } from "./job-engine/schedule.js";
|
|
306
306
|
export type { ParsedSchedule } from "./job-engine/schedule.js";
|
|
307
|
-
export { StackSyncHandle, DocStackSyncHandle, SyncSchemaMismatchError, SYNC_META_DOC_ID, readRemoteSchemaVersion, publishSchemaVersion, createReplicationFilter, isInternalDoc, resolveInternalClasses, createClassFilter, hasClassRules, DATA_MODEL_CLASSES, withFilterIdentity, describeFilter, INTERNAL_DOC_IDS, INTERNAL_DOC_ID_PREFIXES, INTERNAL_DOC_CLASSES, OPTIONAL_INTERNAL_DOC_CLASSES, deriveTenantScope, classTenants, } from "./sync/index.js";
|
|
307
|
+
export { StackSyncHandle, DocStackSyncHandle, SyncSchemaMismatchError, SYNC_META_DOC_ID, readRemoteSchemaVersion, readRemoteConsumerSchemaVersion, publishSchemaVersion, createReplicationFilter, isInternalDoc, resolveInternalClasses, createClassFilter, hasClassRules, DATA_MODEL_CLASSES, withFilterIdentity, describeFilter, INTERNAL_DOC_IDS, INTERNAL_DOC_ID_PREFIXES, INTERNAL_DOC_CLASSES, OPTIONAL_INTERNAL_DOC_CLASSES, deriveTenantScope, classTenants, } from "./sync/index.js";
|
|
308
308
|
export type { SyncDirection, SyncState, SyncStatus, StackSyncOptions, DocStackSyncOptions, RemoteResolver, SyncMetaDoc, InternalDocFilterOptions, ClassFilterOptions, TenantScope, } from "./sync/index.js";
|
|
309
309
|
export type { ClassBuildOptions } from "./class.js";
|
|
310
310
|
export { CONTENT_EXPORT_FORMAT, META_CLASSES, isContentClassName, isContentDocument, isContentRelation, stripTransientFields, } from "./content-transfer.js";
|
|
@@ -312,7 +312,9 @@ export type { ContentExport, ContentExportOptions, ContentImportOptions, Content
|
|
|
312
312
|
export { SYSTEM_SEEDED_DOC_IDS } from "./datamodel/index.js";
|
|
313
313
|
export { collectQueryClasses } from "./query-engine/index.js";
|
|
314
314
|
export { StackWriteGuardError } from "./guarded-db.js";
|
|
315
|
-
export { StackLockedError } from "../plugins/pouchdb.js";
|
|
315
|
+
export { StackLockedError, StackScopeMismatchError } from "../plugins/pouchdb.js";
|
|
316
|
+
export { TransactionEngine, TransactionHandle, TransactionDb, TransactionsDisabledError, TransactionStateError, TransactionValidationError, TransactionConflictError, TransactionUnsupportedDocError, } from "./transaction-engine/index.js";
|
|
317
|
+
export type { TransactionCommitReport, TransactionStatus } from "./transaction-engine/index.js";
|
|
316
318
|
/**
|
|
317
319
|
* Key-identity helpers, for applications that re-key a database.
|
|
318
320
|
*
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/core/stack.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import Class from "./class.js";
|
|
2
2
|
import Domain from "./domain.js";
|
|
3
3
|
import { Stack, StackOptions, AuthSessionProof, ClientCredentials, CachedClass, ClassModelPropagationStart, ClassModelPropagationComplete, CachedDomain, DomainModel, ChangesSubscription } from "@docstack/shared";
|
|
4
|
-
import { SystemDoc, Patch, ClassModel, Document, RelationDocument } from "@docstack/shared";
|
|
4
|
+
import { SystemDoc, Patch, ClassModel, Document, RelationDocument, AccessScopeModel } from "@docstack/shared";
|
|
5
5
|
import { StackSyncHandle } from "./sync/index.js";
|
|
6
6
|
import type { StackSyncOptions, SyncStatus } from "./sync/index.js";
|
|
7
7
|
import type { SelectAST, UnionAST } from "./query-engine/index.js";
|
|
8
8
|
import { JobEngine } from "./job-engine/index.js";
|
|
9
9
|
import { JobScheduler } from "./job-engine/scheduler.js";
|
|
10
|
-
import { PolicyEngine } from "./policy-engine/index.js";
|
|
11
10
|
import { CryptoEngine } from "./crypto-engine/index.js";
|
|
11
|
+
import { TransactionEngine, TransactionHandle, TransactionStage, TransactionCommitReport } from "./transaction-engine/index.js";
|
|
12
12
|
import type { ContentExport, ContentExportOptions, ContentImportOptions, ContentImportReport } from "./content-transfer.js";
|
|
13
13
|
export declare const BASE_SCHEMA: ClassModel["schema"];
|
|
14
14
|
export declare const CLASS_SCHEMA: ClassModel["schema"];
|
|
@@ -162,12 +162,25 @@ declare class ClientStack extends Stack {
|
|
|
162
162
|
* Engine for enforcing read/write access control policies.
|
|
163
163
|
* Policies are evaluated based on user session and document content.
|
|
164
164
|
*/
|
|
165
|
-
|
|
165
|
+
/**
|
|
166
|
+
* The access-scope registry: every `~AccessScope` document, grouped by
|
|
167
|
+
* `scopeId` (ADR-0045). Built from the database, independent of which CEKs
|
|
168
|
+
* the keyring actually holds - the registry knows a scope's key ids even
|
|
169
|
+
* when the session cannot open them, which is what the label↔kid mismatch
|
|
170
|
+
* guard needs (spec 02 §2.3 rule 2).
|
|
171
|
+
*/
|
|
172
|
+
private accessScopeRegistry;
|
|
173
|
+
private accessScopeRegistryDirty;
|
|
166
174
|
/**
|
|
167
175
|
* Engine for field-level encryption and decryption.
|
|
168
176
|
* Handles key derivation (PBKDF2) and AES-GCM encryption.
|
|
169
177
|
*/
|
|
170
178
|
cryptoEngine: CryptoEngine;
|
|
179
|
+
/**
|
|
180
|
+
* Named write transactions (ADR-0039). Dormant unless the stack was opened with
|
|
181
|
+
* `transactions: true`; see {@link beginTransaction}.
|
|
182
|
+
*/
|
|
183
|
+
transactionEngine: TransactionEngine;
|
|
171
184
|
schemaVersion: string | undefined;
|
|
172
185
|
/**
|
|
173
186
|
* The current authenticated user session, if any.
|
|
@@ -319,6 +332,56 @@ declare class ClientStack extends Stack {
|
|
|
319
332
|
* ```
|
|
320
333
|
*/
|
|
321
334
|
unlock(documentKey: string): Promise<this>;
|
|
335
|
+
/**
|
|
336
|
+
* The access-scope registry, loaded from `~AccessScope` documents and
|
|
337
|
+
* refreshed whenever one is written (ADR-0045). Raw read: scope docs are
|
|
338
|
+
* the machinery that DECIDES readability - they cannot sit behind it.
|
|
339
|
+
*/
|
|
340
|
+
private getAccessScopeRegistry;
|
|
341
|
+
/**
|
|
342
|
+
* The scope a document's write seals under: its own `~scope` label, else
|
|
343
|
+
* its class's `defaultScope` (spec 02 §2.2 - the document's value wins).
|
|
344
|
+
*/
|
|
345
|
+
resolveScopeLabel(doc: unknown, classModel?: {
|
|
346
|
+
defaultScope?: string;
|
|
347
|
+
} | null): string | undefined;
|
|
348
|
+
/** Every key id belonging to a scope, across rotation versions - or null for an unknown scope. */
|
|
349
|
+
getAccessScopeKids(scopeId: string): Promise<Set<string> | null>;
|
|
350
|
+
/** Whether a declared scope's CEK is absent from the keyring - its content is sealed. */
|
|
351
|
+
isScopeLocked(scopeId: string): boolean;
|
|
352
|
+
/** Declared scopes whose CEK the keyring lacks. Loaded scopes only - call after open. */
|
|
353
|
+
lockedScopeIds(): Promise<string[]>;
|
|
354
|
+
/**
|
|
355
|
+
* Attempts every declared access scope with the session's attribute key
|
|
356
|
+
* (ADR-0045): the ABE decryption either yields a scope's CEK - verified
|
|
357
|
+
* against the scope's canary, then admitted to the keyring - or fails,
|
|
358
|
+
* and the scope stays locked. There is no gate to ask; this IS the access
|
|
359
|
+
* decision. Idempotent: a later call with better material unlocks more.
|
|
360
|
+
* Deferred patches that were waiting on a scope replay after.
|
|
361
|
+
*/
|
|
362
|
+
unlockScopes(attributeKey: string): Promise<{
|
|
363
|
+
unlocked: string[];
|
|
364
|
+
locked: string[];
|
|
365
|
+
}>;
|
|
366
|
+
/**
|
|
367
|
+
* AUTHORITY-side helper: assembles a complete `~AccessScope` document from
|
|
368
|
+
* a fresh (or supplied) CEK - ABE-sealing it under the policy, stamping the
|
|
369
|
+
* kid, minting the per-scope canary. Runs wherever the application controls
|
|
370
|
+
* (its server, an admin ceremony, tests); it needs the authority PUBLIC key
|
|
371
|
+
* only, never the master secret. The document is returned, not written -
|
|
372
|
+
* publishing it (and distributing attribute keys) is the consumer's act.
|
|
373
|
+
*/
|
|
374
|
+
static buildAccessScope(input: {
|
|
375
|
+
scopeId: string;
|
|
376
|
+
policyString: string;
|
|
377
|
+
/** The authority public key (`@docstack/abe` setup().pk). */
|
|
378
|
+
pk: string;
|
|
379
|
+
/** 32-byte CEK as hex; minted when absent. */
|
|
380
|
+
cekHex?: string;
|
|
381
|
+
version?: number;
|
|
382
|
+
}): Promise<AccessScopeModel & {
|
|
383
|
+
_id: string;
|
|
384
|
+
}>;
|
|
322
385
|
/**
|
|
323
386
|
* Encrypts bootstrap documents that were seeded before this stack had a key.
|
|
324
387
|
*
|
|
@@ -481,7 +544,6 @@ declare class ClientStack extends Stack {
|
|
|
481
544
|
* ```
|
|
482
545
|
*/
|
|
483
546
|
importContent: (payload: ContentExport, options?: ContentImportOptions) => Promise<ContentImportReport>;
|
|
484
|
-
private ensureDefaultPolicyForClass;
|
|
485
547
|
/**
|
|
486
548
|
* Creates and initializes a new ClientStack instance.
|
|
487
549
|
* This is the primary way to instantiate a stack - the constructor is private.
|
|
@@ -521,7 +583,64 @@ declare class ClientStack extends Stack {
|
|
|
521
583
|
*
|
|
522
584
|
* @param patches - Patches not yet present in this stack.
|
|
523
585
|
*/
|
|
586
|
+
/**
|
|
587
|
+
* The highest consumer patch version this device has applied, from the patch
|
|
588
|
+
* ledger - `null` when no consumer patch has ever applied (or they are all
|
|
589
|
+
* deferred, which for the schema gate is the same thing: the schema those
|
|
590
|
+
* patches install is not here yet). The sync layer folds this into what it
|
|
591
|
+
* publishes and compares, so consumer-schema skew between devices refuses at
|
|
592
|
+
* the gate instead of pulling documents this device's schema cannot describe
|
|
593
|
+
* (ADR-0040).
|
|
594
|
+
*/
|
|
595
|
+
getConsumerSchemaVersion(): Promise<string | null>;
|
|
524
596
|
private applyConsumerPatches;
|
|
597
|
+
/**
|
|
598
|
+
* The stack a patch job executes against (ADR-0044): reads see the chain
|
|
599
|
+
* transaction's overlay, writes stage into it - so a migration's data
|
|
600
|
+
* transformation lands in the same commit as the model it prepares, or not at
|
|
601
|
+
* all. While the stack is locked, class-aware reads of an encrypting class
|
|
602
|
+
* THROW instead of serving the null convention: a migration wants the refusal
|
|
603
|
+
* (a `requiresKey: false` job that was declared wrongly must fail loudly, and
|
|
604
|
+
* the chain converts that failure to a deferral). Raw reads (`db.find`) bypass
|
|
605
|
+
* the class-aware path by design and stay the author's responsibility.
|
|
606
|
+
*/
|
|
607
|
+
private createPatchJobStack;
|
|
608
|
+
/**
|
|
609
|
+
* Runs one of a patch's one-shot jobs against the transaction facade. The run
|
|
610
|
+
* receipt is a `~JobRun` with NO `jobId` - patch jobs are deliberately never
|
|
611
|
+
* persisted as `~Job` documents, so there is no row to point at (`~sys-0.0.17`
|
|
612
|
+
* made the foreign key optional for exactly this) - carrying the patch
|
|
613
|
+
* identity in `runtimeArgs`. It writes DIRECTLY, win or lose: a failed
|
|
614
|
+
* migration's receipt is the troubleshooting trail and must survive the
|
|
615
|
+
* discard that protects everything else (ADR-0044).
|
|
616
|
+
*/
|
|
617
|
+
private runPatchJob;
|
|
618
|
+
/**
|
|
619
|
+
* The ADR-0042 protocol: the whole pending chain stages through one internal
|
|
620
|
+
* transaction - patch N+1 hydrates against the classes patch N staged, so the
|
|
621
|
+
* ADR-0038 merge composes in memory before anything is real - propagation is
|
|
622
|
+
* validated dry with nothing kept, and one commit lands every staged doc (class
|
|
623
|
+
* models and data documents alike, since the mixed extension) as one batch
|
|
624
|
+
* through the unchanged pipeline, where real propagation runs. The ledger
|
|
625
|
+
* (ADR-0041) arms only after that commit; any refusal beforehand persists
|
|
626
|
+
* nothing and names the patch at fault.
|
|
627
|
+
*/
|
|
628
|
+
private applyConsumerPatchChain;
|
|
629
|
+
/**
|
|
630
|
+
* Stages one patch's documents into the chain transaction. Hydration reads
|
|
631
|
+
* through the transaction's overlay, so an `_rev: "auto"` document merges onto
|
|
632
|
+
* what an earlier patch staged - or onto committed state when the chain has not
|
|
633
|
+
* touched it.
|
|
634
|
+
*/
|
|
635
|
+
private stagePatch;
|
|
636
|
+
/**
|
|
637
|
+
* ADR-0042 §3 - propagation, validated dry: for every class the chain staged
|
|
638
|
+
* over a committed predecessor, run the schema delta across the class's
|
|
639
|
+
* committed documents and keep nothing. The point is the refusal - a document
|
|
640
|
+
* that cannot satisfy the new model fails here, before the first write, naming
|
|
641
|
+
* the patch, the document and the attribute.
|
|
642
|
+
*/
|
|
643
|
+
private validateChainPropagation;
|
|
525
644
|
/**
|
|
526
645
|
* Decides whether applying a patch would write an encrypted attribute.
|
|
527
646
|
*
|
|
@@ -532,8 +651,20 @@ declare class ClientStack extends Stack {
|
|
|
532
651
|
* simulate schema evolution ahead of time.
|
|
533
652
|
*
|
|
534
653
|
* @param patch - The patch about to be applied.
|
|
654
|
+
* @param stagedSchema - Chain staging only (ADR-0042): resolves a class the
|
|
655
|
+
* current transaction already staged, so patch N+1 defers exactly as it would
|
|
656
|
+
* have when patch N had committed.
|
|
535
657
|
* @returns `true` if any document in it belongs to a class with encrypted attributes.
|
|
536
658
|
*/
|
|
659
|
+
/**
|
|
660
|
+
* The deferral barrier, generalized per scope (spec 02 §5): a patch is
|
|
661
|
+
* blocked when the legacy half applies (stack locked and the patch needs
|
|
662
|
+
* the document key) OR any document it carries writes into a declared
|
|
663
|
+
* scope whose CEK the keyring lacks - sealing under the wrong key is never
|
|
664
|
+
* a fallback, so the patch waits for `unlockScopes` exactly as key-needing
|
|
665
|
+
* patches wait for `unlock`.
|
|
666
|
+
*/
|
|
667
|
+
private patchBlockedByLock;
|
|
537
668
|
private patchNeedsDocumentKey;
|
|
538
669
|
/**
|
|
539
670
|
* Authenticates a user and establishes a session.
|
|
@@ -598,6 +729,34 @@ declare class ClientStack extends Stack {
|
|
|
598
729
|
getLastDocId(): Promise<number>;
|
|
599
730
|
getSystem(): Promise<SystemDoc>;
|
|
600
731
|
private loadPatches;
|
|
732
|
+
/**
|
|
733
|
+
* Finds a patch's ledger entry, raw - ledger documents are read outside the
|
|
734
|
+
* `active: true` visibility convention because `active` carries the ledger's own
|
|
735
|
+
* meaning here: `true` is applied, `false` is deferred, absent is a legacy entry
|
|
736
|
+
* from before the flag (treated as applied). See ADR-0041.
|
|
737
|
+
*/
|
|
738
|
+
private findPatchLedgerEntry;
|
|
739
|
+
/**
|
|
740
|
+
* Records a successful application: the ledger entry arms with `active: true` -
|
|
741
|
+
* flipping the deferral entry in place when one exists, so a replayed patch does
|
|
742
|
+
* not duplicate its record.
|
|
743
|
+
*/
|
|
744
|
+
private recordPatchApplication;
|
|
745
|
+
/**
|
|
746
|
+
* Records a deferral: the patch is known but dormant (`active: false`), waiting
|
|
747
|
+
* on the document key. The entry is what makes a deferred device honest at the
|
|
748
|
+
* sync gate - {@link getConsumerSchemaVersion} does not count it.
|
|
749
|
+
*/
|
|
750
|
+
private recordPatchDeferral;
|
|
751
|
+
/**
|
|
752
|
+
* The ADR-0038 half of patch hydration: `schema` does not ride the shallow
|
|
753
|
+
* merge, which would replace it wholesale - it merges attribute by attribute.
|
|
754
|
+
* A patch states only the attributes it changes, an absent attribute stays as
|
|
755
|
+
* stored, and an explicit `null` entry drops the attribute - from the model
|
|
756
|
+
* here, and from the documents when the write propagates. Shared by
|
|
757
|
+
* {@link applyPatch} and the chain staging of ADR-0042 so the two cannot drift.
|
|
758
|
+
*/
|
|
759
|
+
private static mergePatchSchema;
|
|
601
760
|
applyPatch: (patch: Patch) => Promise<string>;
|
|
602
761
|
private applyPatches;
|
|
603
762
|
checkSystem(): Promise<void>;
|
|
@@ -711,10 +870,13 @@ declare class ClientStack extends Stack {
|
|
|
711
870
|
/**
|
|
712
871
|
* Whether a database-level `limit` returns the same rows as limiting in memory.
|
|
713
872
|
*
|
|
714
|
-
* `findDocuments`
|
|
715
|
-
*
|
|
716
|
-
*
|
|
717
|
-
*
|
|
873
|
+
* `findDocuments` drops a document whose visible fields are all sealed - a
|
|
874
|
+
* locked legacy key, or a scope the keyring cannot open. A limit applied
|
|
875
|
+
* before that filter would under-fill. So pushdown is allowed exactly when
|
|
876
|
+
* no row of this class can drop: the class has no encrypted attributes, or
|
|
877
|
+
* every key that might seal one is held (the legacy key, and every declared
|
|
878
|
+
* scope - a document may carry any label). The query engine asks this
|
|
879
|
+
* before pushing a SQL LIMIT into the fetch.
|
|
718
880
|
*
|
|
719
881
|
* @param className - The class being queried.
|
|
720
882
|
*/
|
|
@@ -801,6 +963,27 @@ declare class ClientStack extends Stack {
|
|
|
801
963
|
* Removes event listeners and terminates background workers.
|
|
802
964
|
*/
|
|
803
965
|
close: () => void;
|
|
966
|
+
/**
|
|
967
|
+
* Opens a named write transaction (ADR-0039). Requires the stack to have been
|
|
968
|
+
* opened with `transactions: true`.
|
|
969
|
+
*
|
|
970
|
+
* Writes through the handle validate at the call site and stage in memory;
|
|
971
|
+
* reads through it see the staged state overlaid on committed state. Nothing
|
|
972
|
+
* reaches the database - or replication, or any other reader - until
|
|
973
|
+
* {@link commit}. `stack.db` stays live and unchanged next to open transactions:
|
|
974
|
+
* direct writes land immediately, and only touch a transaction by making its
|
|
975
|
+
* commit refuse when they advance a staged document's revision.
|
|
976
|
+
*/
|
|
977
|
+
beginTransaction: () => TransactionHandle;
|
|
978
|
+
/**
|
|
979
|
+
* Flushes a transaction's staged writes as one batch through the authoring
|
|
980
|
+
* pipeline. On refusal - validation, or a document changed underneath - nothing
|
|
981
|
+
* is persisted and the transaction stays open. The report says what landed and
|
|
982
|
+
* on what storage guarantee (`adapter.atomicBatch`).
|
|
983
|
+
*/
|
|
984
|
+
commit: (t: TransactionHandle | string) => Promise<TransactionCommitReport>;
|
|
985
|
+
/** Drops a transaction's staged writes. Idempotent. */
|
|
986
|
+
discardTransaction: (t: TransactionHandle | string) => void;
|
|
804
987
|
/**
|
|
805
988
|
* Retrieves a Class instance by name.
|
|
806
989
|
* Results are cached for 15 minutes to improve performance.
|
|
@@ -900,6 +1083,26 @@ declare class ClientStack extends Stack {
|
|
|
900
1083
|
[key: string]: any;
|
|
901
1084
|
docs: T[];
|
|
902
1085
|
}>;
|
|
1086
|
+
/**
|
|
1087
|
+
* {@link findDocuments} with an optional transaction stage overlaid - the shared
|
|
1088
|
+
* implementation, so a transaction's reads and ordinary reads run the identical
|
|
1089
|
+
* pipeline (policy, decryption, field visibility) and cannot drift (ADR-0039).
|
|
1090
|
+
*
|
|
1091
|
+
* With a stage: the database's index cannot see staged documents, so the
|
|
1092
|
+
* committed query runs unwindowed, staged ids mask their committed rows, staged
|
|
1093
|
+
* matches join the set, and sort/skip/limit apply after the merge. A selector
|
|
1094
|
+
* over a class the stage never touched skips all of that.
|
|
1095
|
+
*
|
|
1096
|
+
* @internal
|
|
1097
|
+
*/
|
|
1098
|
+
findDocumentsForView: <T extends Document | RelationDocument = Document>(stage: TransactionStage | undefined, selector: {
|
|
1099
|
+
[key: string]: any;
|
|
1100
|
+
}, fields?: string[], skip?: number, limit?: number, sort?: {
|
|
1101
|
+
[field: string]: "asc" | "desc";
|
|
1102
|
+
}[]) => Promise<{
|
|
1103
|
+
[key: string]: any;
|
|
1104
|
+
docs: T[];
|
|
1105
|
+
}>;
|
|
903
1106
|
/**
|
|
904
1107
|
* Runs raw fetched documents through the read pipeline: per-document policy
|
|
905
1108
|
* check, decryption, and field visibility. Shared by {@link findDocuments} and
|
|
@@ -1153,6 +1356,18 @@ declare class ClientStack extends Stack {
|
|
|
1153
1356
|
rows: any;
|
|
1154
1357
|
ast: (SelectAST | UnionAST)[];
|
|
1155
1358
|
}>;
|
|
1359
|
+
/**
|
|
1360
|
+
* {@link query}'s implementation, with the executor's data source as a
|
|
1361
|
+
* parameter: the executor reaches documents only through stack APIs, so a
|
|
1362
|
+
* transaction hands in a facade that routes them at its overlay while everything
|
|
1363
|
+
* else - parsing, binding, planning - stays exactly this code (ADR-0039).
|
|
1364
|
+
*
|
|
1365
|
+
* @internal
|
|
1366
|
+
*/
|
|
1367
|
+
runQuery: (sql: string, params: any[], execStack: ClientStack) => Promise<{
|
|
1368
|
+
rows: any;
|
|
1369
|
+
ast: (SelectAST | UnionAST)[];
|
|
1370
|
+
}>;
|
|
1156
1371
|
/**
|
|
1157
1372
|
* Executes a SQL query as an async stream of rows.
|
|
1158
1373
|
*
|
|
File without changes
|
|
File without changes
|
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
|
*
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -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
|
+
}
|