@g9tm/syncdata 1.3.1 → 1.5.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.
@@ -109,6 +109,8 @@ export declare const FILE_MODE_COPY = "copy";
109
109
  export declare const FILE_MODE_COLUMNS = "columns";
110
110
  /** A file table merged into a table that held rows or protected rows: changed rows only. */
111
111
  export declare const FILE_MODE_MERGE = "merge";
112
+ /** A file table inserted into a table holding only OTHER rows (a second site's first load): every row is new, no per-row diff. */
113
+ export declare const FILE_MODE_APPEND = "append";
112
114
  export declare class G9ApplyDiagnostics {
113
115
  transactions: number;
114
116
  transactionsWithTriggersSkipped: number;
@@ -198,6 +200,20 @@ export interface G9ChangeFeedEntry {
198
200
  deleted: boolean;
199
201
  localPending: boolean;
200
202
  }
203
+ /**
204
+ * Narrows a change-feed read to the keys a query of the app's own tables selects (`G9CChangeFeedKeyFilter`): a projection of one
205
+ * site no longer walks the entries of every other site. Deletions are always returned - their rows are gone, so no query can place
206
+ * them. `keySql` is one SELECT of one column, embedded as `pk IN (...)`; parameters are bound by name (`$name`, `@name`, `:name`)
207
+ * and `$g9t`, `$g9a`, `$g9l` belong to the read.
208
+ */
209
+ export interface G9ChangeFeedKeyFilter {
210
+ keySql: string;
211
+ parameters?: {
212
+ [name: string]: string | number | null;
213
+ };
214
+ }
215
+ /** Throws when the filter cannot be embedded safely (`G9CChangeFeedKeyFilter.Validate`). */
216
+ export declare function validateChangeFeedKeyFilter(filter: G9ChangeFeedKeyFilter): void;
201
217
  export interface G9SyncRejection {
202
218
  localTable: string;
203
219
  key: string;
@@ -430,6 +446,8 @@ export interface G9SyncClientStore {
430
446
  reevaluateRetained(signal?: AbortSignal): Promise<number>;
431
447
  getCommands(state: G9ELocalCommandState | null, limit: number, signal?: AbortSignal): Promise<G9LocalCommand[]>;
432
448
  readChangeFeed(localTable: string, afterSequence: number, limit: number, signal?: AbortSignal): Promise<G9ChangeFeedEntry[]>;
449
+ /** The feed narrowed to the keys `keyFilter` selects, plus every deletion; fewer than `limit` entries means the read is complete. */
450
+ readChangeFeedFiltered(localTable: string, afterSequence: number, limit: number, keyFilter: G9ChangeFeedKeyFilter, signal?: AbortSignal): Promise<G9ChangeFeedEntry[]>;
433
451
  getChangeSequence(signal?: AbortSignal): Promise<number>;
434
452
  getChangeFeedFloor(signal?: AbortSignal): Promise<number>;
435
453
  getRejections(signal?: AbortSignal): Promise<G9SyncRejection[]>;
@@ -25,6 +25,8 @@ export const FILE_MODE_COPY = 'copy';
25
25
  export const FILE_MODE_COLUMNS = 'columns';
26
26
  /** A file table merged into a table that held rows or protected rows: changed rows only. */
27
27
  export const FILE_MODE_MERGE = 'merge';
28
+ /** A file table inserted into a table holding only OTHER rows (a second site's first load): every row is new, no per-row diff. */
29
+ export const FILE_MODE_APPEND = 'append';
28
30
  export class G9ApplyDiagnostics {
29
31
  constructor() {
30
32
  this.transactions = 0;
@@ -177,6 +179,21 @@ export class G9ApplyReport {
177
179
  }
178
180
  }
179
181
  }
182
+ const RESERVED_FEED_PARAMETERS = ['$g9t', '$g9a', '$g9l'];
183
+ /** Throws when the filter cannot be embedded safely (`G9CChangeFeedKeyFilter.Validate`). */
184
+ export function validateChangeFeedKeyFilter(filter) {
185
+ var _a, _b;
186
+ const sql = ((_a = filter.keySql) !== null && _a !== void 0 ? _a : '').trim();
187
+ if (sql.slice(0, 6).toUpperCase() !== 'SELECT' || sql.indexOf(';') >= 0) {
188
+ throw new Error('A change-feed key filter is a single SELECT statement of one column.');
189
+ }
190
+ for (const name of Object.keys((_b = filter.parameters) !== null && _b !== void 0 ? _b : {})) {
191
+ if (name.length < 2 || '$@:'.indexOf(name[0]) < 0)
192
+ throw new Error(`Change-feed key filter parameter '${name}' must be named ($name, @name or :name).`);
193
+ if (RESERVED_FEED_PARAMETERS.indexOf(name.toLowerCase()) >= 0)
194
+ throw new Error(`Change-feed key filter parameter '${name}' is reserved by the read.`);
195
+ }
196
+ }
180
197
  // ---------------------------------------------------------------- local commands (ADR-0025)
181
198
  /** `G9ELocalCommandState`. */
182
199
  export const G9ELocalCommandState = {
@@ -1 +1 @@
1
- {"version":3,"file":"storeTypes.js","sourceRoot":"","sources":["../../src/client/storeTypes.ts"],"names":[],"mappings":"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","sourcesContent":["// Twins of G9SyncData.Client/Storage/*.cs: the client store contract, its records, local commands, revocation rules\n// and apply diagnostics. The engine is store-agnostic; `@g9tm/syncdata/sqlite` is the implementation.\nimport type { G9EMoveOutPolicy } from '../abstractions/enums.js';\nimport type { G9Long } from '../abstractions/long.js';\nimport type { G9SyncParameters } from '../abstractions/parameters.js';\nimport type {\n G9DtBackfillRequest,\n G9DtCommand,\n G9DtHelloResult,\n G9DtNegotiatedTable,\n G9DtPullContinuation,\n G9DtPullPlan,\n G9DtPushResult,\n G9DtSnapshotFileRequest,\n G9DtSubscriptionDefinition,\n G9DtSubscriptionRef,\n G9DtSyncChunk,\n G9DtTableColumns,\n G9DtTableDigest,\n} from '../protocol/dtos.js';\nimport type { G9SyncClientModel } from './model.js';\n\n// ---------------------------------------------------------------- server state and subscriptions\n\n/** What the client last learned from the server (`G9CClientServerState`). */\nexport interface G9ClientServerState {\n epoch: string | null;\n lastHelloUtcMs: number | null;\n /** Server clock minus device clock, ms. */\n serverClockOffsetMs: number;\n tables: G9DtNegotiatedTable[];\n definitions: G9DtSubscriptionDefinition[];\n}\n\n/** `G9ESubscriptionState`. */\nexport const G9ESubscriptionState = {\n Active: 0,\n NeedsReconcile: 1,\n Failed: 2,\n} as const;\nexport type G9ESubscriptionState = (typeof G9ESubscriptionState)[keyof typeof G9ESubscriptionState];\n\n/** A local subscription (`G9CSyncSubscriptionRecord`). Times are epoch milliseconds UTC. */\nexport interface G9SyncSubscriptionRecord {\n key: string;\n name: string;\n parameters: G9SyncParameters;\n /** Membership slot (bit) for persistent subscriptions; the shared ephemeral slot otherwise. */\n slot: number;\n ephemeral: boolean;\n ttlMs: number | null;\n cursor: string | null;\n /** The move-out policy the server last announced; kept so it still applies once access is gone. */\n moveOutPolicy: G9EMoveOutPolicy | null;\n definitionHash: string | null;\n authHash: string | null;\n state: G9ESubscriptionState;\n lastErrorCode: string | null;\n createdUtcMs: number;\n lastSuccessUtcMs: number | null;\n lastReconcileUtcMs: number | null;\n lastAccessUtcMs: number | null;\n}\n\n/** `G9CSyncSubscriptionRecord.ToReference()`. */\nexport function toSubscriptionRef(record: G9SyncSubscriptionRecord): G9DtSubscriptionRef {\n return {\n name: record.name,\n parameters: record.parameters.values,\n cursor: record.cursor,\n definitionHash: record.definitionHash,\n authHash: record.authHash,\n };\n}\n\n/** A partial update of a subscription record; absent members are left unchanged (`G9CSubscriptionUpdate`). */\nexport interface G9SubscriptionUpdate {\n cursor?: string | null;\n definitionHash?: string | null;\n authHash?: string | null;\n state?: G9ESubscriptionState | null;\n errorCode?: string | null;\n clearError?: boolean;\n succeeded?: boolean;\n reconciled?: boolean;\n accessed?: boolean;\n clearCursor?: boolean;\n}\n\n// ---------------------------------------------------------------- push and pull\n\n/** A push batch built from dirty rows (`G9CPushBatch`). */\nexport interface G9PushBatch {\n batchId: number;\n /** Highest local change sequence included; rows changed after it stay dirty after the acknowledgement. */\n maxSequence: number;\n frames: Uint8Array[];\n tables: G9DtTableColumns[];\n rowCount: number;\n /** True when the byte budget ended the batch early: more pending rows follow. */\n truncated: boolean;\n /** Rows rejected on the device while building the batch (G9S-REJ-TOO-LARGE). */\n rejectedLocally: number;\n /** Commands carried by this batch, in device order. */\n commands: G9DtCommand[];\n}\n\n/** A pull being staged, persisted across restarts (`G9CStagedPull`). */\nexport interface G9StagedPull {\n runId: string;\n plan: G9DtPullPlan;\n createdUtcMs: number;\n backfills: G9DtBackfillRequest[];\n /** What the world looked like when the plan was made (ADR-0046); a run only resumes a stage whose generation matches. */\n generation: string | null;\n /** The cursor each planned subscription had when the plan was made, by subscription key (ADR-0060). */\n plannedFromCursors: { [subscriptionKey: string]: string | null };\n /** The device clock when the stage began, assigned by the store. */\n stageSequence: number;\n}\n\n/** One committed slice of a staged pull (`G9CApplySlice`). */\nexport interface G9ApplySlice {\n report: G9ApplyReport;\n rowsApplied: number;\n rowsAppliedTotal: number;\n rowsPlanned: number;\n isFinal: boolean;\n}\n\n/** `G9CResumePoint`. */\nexport interface G9ResumePoint {\n continuation: G9DtPullContinuation | null;\n stagedRows: number;\n stagedBytes: number;\n complete: boolean;\n}\n\n// ---------------------------------------------------------------- reports\n\n/** Why a table large enough for index deferral kept its indexes live. */\nexport const REASON_TABLE_NOT_EMPTY = 'table-not-empty';\nconst MAX_FOREIGN_TRIGGER_NAMES = 10;\n\n/** How an apply ran, as opposed to what it changed (`G9CApplyDiagnostics`, ADR-0058). */\n/** A file table copied record for record into an empty table with identical columns (the transfer optimisation). */\nexport const FILE_MODE_COPY = 'copy';\n/** A file table copied into an empty table by column name. */\nexport const FILE_MODE_COLUMNS = 'columns';\n/** A file table merged into a table that held rows or protected rows: changed rows only. */\nexport const FILE_MODE_MERGE = 'merge';\n\nexport class G9ApplyDiagnostics {\n transactions = 0;\n transactionsWithTriggersSkipped = 0;\n readonly foreignTriggers: string[] = [];\n readonly deferredIndexes: { [table: string]: number } = {};\n readonly liveIndexReasons: { [table: string]: string } = {};\n readonly rowsByTable: { [table: string]: number } = {};\n readonly msByTable: { [table: string]: number } = {};\n indexRebuildMs = 0;\n elapsedMs = 0;\n /** Snapshot files copied in (ADR-0072). */\n snapshotFiles = 0;\n /** Rows that arrived through snapshot files. */\n fileRows = 0;\n /** Milliseconds spent copying snapshot files, metadata included. */\n fileCopyMs = 0;\n /** How each table came out of a snapshot file: `copy`, `columns` or `merge` (see FILE_MODE_*). */\n readonly fileTables: { [table: string]: string } = {};\n\n /** True when every transaction ran with trigger execution off, false when any ran with it on, null when nothing was applied. */\n get triggersSkipped(): boolean | null {\n return this.transactions === 0 ? null : this.transactionsWithTriggersSkipped === this.transactions;\n }\n\n get deferredIndexCount(): number {\n let total = 0;\n for (const key of Object.keys(this.deferredIndexes)) total += this.deferredIndexes[key] as number;\n return total;\n }\n\n merge(other: G9ApplyDiagnostics): void {\n this.transactions += other.transactions;\n this.transactionsWithTriggersSkipped += other.transactionsWithTriggersSkipped;\n this.indexRebuildMs += other.indexRebuildMs;\n this.elapsedMs += other.elapsedMs;\n this.snapshotFiles += other.snapshotFiles;\n this.fileRows += other.fileRows;\n this.fileCopyMs += other.fileCopyMs;\n for (const key of Object.keys(other.fileTables)) this.fileTables[key] = other.fileTables[key] as string;\n for (const name of other.foreignTriggers) {\n if (this.foreignTriggers.length >= MAX_FOREIGN_TRIGGER_NAMES) break;\n if (!this.foreignTriggers.some((n) => n.toLowerCase() === name.toLowerCase())) this.foreignTriggers.push(name);\n }\n addAll(this.deferredIndexes, other.deferredIndexes);\n for (const key of Object.keys(other.liveIndexReasons)) this.liveIndexReasons[key] = other.liveIndexReasons[key] as string;\n addAll(this.rowsByTable, other.rowsByTable);\n addAll(this.msByTable, other.msByTable);\n }\n\n /** One line for a log: what engaged, what did not and why, and the tables that took the time. */\n toString(): string {\n if (this.transactions === 0) return 'nothing applied';\n let text = `${this.transactions} transaction(s) in ${this.elapsedMs} ms; triggers `;\n text +=\n this.triggersSkipped === true\n ? 'skipped'\n : this.transactionsWithTriggersSkipped === 0\n ? 'LIVE'\n : `skipped in ${this.transactionsWithTriggersSkipped} of ${this.transactions}`;\n if (this.foreignTriggers.length > 0) text += ' (foreign: ' + this.foreignTriggers.join(', ') + ')';\n const deferred = Object.keys(this.deferredIndexes);\n if (deferred.length > 0) {\n text += `; ${this.deferredIndexCount} index(es) rebuilt after the load in ${this.indexRebuildMs} ms (` + deferred.map((t) => t + ':' + this.deferredIndexes[t]).join(', ') + ')';\n }\n const live = Object.keys(this.liveIndexReasons);\n if (live.length > 0) text += '; live indexes on ' + live.map((t) => t + ' (' + this.liveIndexReasons[t] + ')').join(', ');\n if (this.snapshotFiles > 0) {\n text +=\n `; ${this.snapshotFiles} snapshot file(s), ${this.fileRows} rows copied in ${this.fileCopyMs} ms (` +\n Object.keys(this.fileTables)\n .map((t) => t + ':' + this.fileTables[t])\n .join(', ') +\n ')';\n }\n const slowest = Object.keys(this.msByTable)\n .sort((a, b) => (this.msByTable[b] as number) - (this.msByTable[a] as number))\n .slice(0, 4);\n if (slowest.length > 0) {\n text +=\n '; ' +\n slowest\n .map((t) => {\n const ms = this.msByTable[t] as number;\n const rows = this.rowsByTable[t] ?? 0;\n const rate = ms > 0 ? Math.floor((rows * 1000) / ms) : rows;\n return `${t} ${rows} rows in ${ms} ms (${rate}/s)`;\n })\n .join(', ');\n }\n return text;\n }\n}\n\nfunction addAll(target: { [key: string]: number }, source: { [key: string]: number }): void {\n for (const key of Object.keys(source)) target[key] = (target[key] ?? 0) + (source[key] as number);\n}\n\n/** What an apply changed (`G9CApplyReport`). */\nexport class G9ApplyReport {\n upserts = 0;\n deletes = 0;\n moveOuts = 0;\n membershipAdded = 0;\n /** Rows skipped because they carry a pending local change. */\n skippedDirty = 0;\n accepted = 0;\n rejected = 0;\n conflicts = 0;\n commandsCompleted = 0;\n revokedDeleted = 0;\n revokedQuarantined = 0;\n revokedRetained = 0;\n /** Changed keys per local table (capped per table). */\n readonly changedKeys: { [localTable: string]: string[] } = {};\n /** Upserts whose row already held exactly the incoming values (ADR-0058). */\n unchangedRows = 0;\n readonly diagnostics = new G9ApplyDiagnostics();\n\n get total(): number {\n return this.upserts + this.deletes + this.moveOuts;\n }\n\n merge(other: G9ApplyReport): void {\n this.unchangedRows += other.unchangedRows;\n this.diagnostics.merge(other.diagnostics);\n this.upserts += other.upserts;\n this.deletes += other.deletes;\n this.moveOuts += other.moveOuts;\n this.membershipAdded += other.membershipAdded;\n this.skippedDirty += other.skippedDirty;\n this.accepted += other.accepted;\n this.rejected += other.rejected;\n this.conflicts += other.conflicts;\n this.commandsCompleted += other.commandsCompleted;\n this.revokedDeleted += other.revokedDeleted;\n this.revokedQuarantined += other.revokedQuarantined;\n this.revokedRetained += other.revokedRetained;\n for (const table of Object.keys(other.changedKeys)) {\n const list = this.changedKeys[table] ?? (this.changedKeys[table] = []);\n for (const key of other.changedKeys[table] as string[]) list.push(key);\n }\n }\n}\n\n/** A local row as seen by reconciliation (`G9CLocalRowEntry`). */\nexport interface G9LocalRowEntry {\n key: string;\n bucket: number;\n version: number;\n hash: G9Long;\n member: boolean;\n dirty: boolean;\n}\n\nexport interface G9PendingTable {\n localTable: string;\n upserts: number;\n deletes: number;\n rejected: number;\n}\n\nexport interface G9PendingSummary {\n totalRows: number;\n rejectedRows: number;\n /** Commands recorded on the device and not yet executed by the server. */\n pendingCommands: number;\n tables: G9PendingTable[];\n}\n\n/** One entry of the local change feed (`G9CChangeFeedEntry`). */\nexport interface G9ChangeFeedEntry {\n sequence: number;\n key: string;\n deleted: boolean;\n localPending: boolean;\n}\n\nexport interface G9SyncRejection {\n localTable: string;\n key: string;\n code: string;\n message: string | null;\n sqlState: string | null;\n constraint: string | null;\n column: string | null;\n details: string | null;\n atUtcMs: number;\n}\n\n/** A persisted summary of one run (`G9CSyncRunReport`); serialized with the .NET JSON names into g9_runs. */\nexport interface G9SyncRunReport {\n runId: string;\n startedUtcMs: number;\n durationMs: number;\n outcome: string;\n priority: string;\n errorCode: string | null;\n errorMessage: string | null;\n pushed: number;\n rejected: number;\n conflicts: number;\n downloaded: number;\n applied: number;\n downloadedBytes: number;\n subscriptions: string[];\n phaseMs: { [phase: string]: number };\n triggersSkipped: boolean | null;\n foreignTriggers: string[];\n applyTransactions: number;\n indexesDeferred: number;\n indexRebuildMs: number;\n liveIndexReasons: { [table: string]: string };\n unchangedRows: number;\n}\n\n// ---------------------------------------------------------------- local commands (ADR-0025)\n\n/** `G9ELocalCommandState`. */\nexport const G9ELocalCommandState = {\n Pending: 0,\n Ok: 1,\n Rejected: 2,\n Superseded: 3,\n} as const;\nexport type G9ELocalCommandState = (typeof G9ELocalCommandState)[keyof typeof G9ELocalCommandState];\n\n/** A command recorded on this device (`G9CLocalCommand`). */\nexport interface G9LocalCommand {\n commandId: string;\n sequence: number;\n name: string;\n version: number;\n payload: string;\n createdUtcMs: number;\n state: G9ELocalCommandState;\n code: string | null;\n message: string | null;\n result: string | null;\n completedUtcMs: number | null;\n}\n\n/** What to record (`G9CCommandRequest`). */\nexport interface G9CommandRequest {\n name: string;\n version?: number;\n /** JSON payload for the server's handler. */\n payload: string;\n}\n\n/** A value an app binds through {@link G9LocalCommandWriter}: the SQLite driver's value forms, plus booleans. */\nexport type G9LocalSqlArgument = null | number | string | boolean | ArrayBuffer | Uint8Array | { int64: string } | G9Long;\n\n/**\n * Writes a command's local effects in the transaction that records it (`IG9LocalCommandWriter`). Rows written here are\n * not uploaded as row changes; they are protected from pulls until the server's result arrives.\n */\nexport interface G9LocalCommandWriter {\n /** Executes SQL; parameters bind positionally as `$p0`, `$p1`, … */\n execute(sql: string, ...args: G9LocalSqlArgument[]): Promise<number>;\n scalar(sql: string, ...args: G9LocalSqlArgument[]): Promise<unknown>;\n}\n\n// ---------------------------------------------------------------- revocation (ADR-0026)\n\n/** `G9ERevocationReason`. */\nexport const G9ERevocationReason = {\n LeftScope: 0,\n Unsubscribed: 1,\n Expired: 2,\n AccessRevoked: 3,\n} as const;\nexport type G9ERevocationReason = (typeof G9ERevocationReason)[keyof typeof G9ERevocationReason];\n\n/** `G9ERevocationDisposition`. */\nexport const G9ERevocationDisposition = {\n Delete: 0,\n MarkRevoked: 1,\n Quarantine: 2,\n Retain: 3,\n} as const;\nexport type G9ERevocationDisposition = (typeof G9ERevocationDisposition)[keyof typeof G9ERevocationDisposition];\n\n/** A row no subscription holds any more, as a revocation rule sees it (`G9CRevokedRow`). */\nexport interface G9RevokedRow {\n localTable: string;\n key: string;\n reason: G9ERevocationReason;\n subscriptionKey: string | null;\n hasPendingChanges: boolean;\n isRejected: boolean;\n /** Column values (key included) as stored locally; empty when the row no longer exists locally. */\n values: { [column: string]: unknown };\n}\n\n/** Lets a custom rule change the row before its disposition applies. */\nexport interface G9RevokedRowEditor {\n set(column: string, value: unknown): void;\n}\n\nexport interface G9RevocationRule {\n readonly name: string;\n readonly condition: (row: G9RevokedRow) => boolean;\n readonly decide: (row: G9RevokedRow, editor: G9RevokedRowEditor) => G9ERevocationDisposition;\n readonly retainWhile: ((row: G9RevokedRow) => boolean) | null;\n}\n\n/** Ordered revocation rules (`G9CRevocationPolicy`): the first matching rule decides. */\nexport class G9RevocationPolicy {\n readonly rules: readonly G9RevocationRule[];\n\n private constructor(rules: readonly G9RevocationRule[]) {\n this.rules = rules;\n }\n\n /** No rules: delete clean rows, quarantine rows with pending changes. */\n static readonly default = new G9RevocationPolicy([]);\n\n static create(configure: (builder: G9RevocationPolicyBuilder) => void): G9RevocationPolicy {\n const builder = new G9RevocationPolicyBuilder();\n configure(builder);\n return new G9RevocationPolicy(builder.rules.slice());\n }\n\n decide(row: G9RevokedRow, editor: G9RevokedRowEditor): { rule: G9RevocationRule | null; disposition: G9ERevocationDisposition } {\n for (const rule of this.rules) {\n if (rule.condition(row)) return { rule, disposition: rule.decide(row, editor) };\n }\n return { rule: null, disposition: row.hasPendingChanges ? G9ERevocationDisposition.Quarantine : G9ERevocationDisposition.Delete };\n }\n\n find(name: string): G9RevocationRule | null {\n for (const rule of this.rules) if (rule.name === name) return rule;\n return null;\n }\n}\n\nexport class G9RevocationPolicyBuilder {\n readonly rules: G9RevocationRule[] = [];\n\n when(name: string, condition: (row: G9RevokedRow) => boolean): G9RevocationActionBuilder {\n if (typeof name !== 'string' || name.trim() === '') throw new TypeError('A rule needs a name.');\n if (this.rules.some((r) => r.name === name)) throw new RangeError(`Revocation rule '${name}' is declared twice.`);\n return new G9RevocationActionBuilder(this, name, condition);\n }\n\n /** Rows of one local table. */\n forTable(name: string, localTable: string, condition?: (row: G9RevokedRow) => boolean): G9RevocationActionBuilder {\n const table = localTable.toLowerCase();\n return this.when(name, (row) => row.localTable.toLowerCase() === table && (condition ? condition(row) : true));\n }\n}\n\nexport class G9RevocationActionBuilder {\n private readonly _owner: G9RevocationPolicyBuilder;\n private readonly _name: string;\n private readonly _condition: (row: G9RevokedRow) => boolean;\n\n constructor(owner: G9RevocationPolicyBuilder, name: string, condition: (row: G9RevokedRow) => boolean) {\n this._owner = owner;\n this._name = name;\n this._condition = condition;\n }\n\n delete(): G9RevocationPolicyBuilder {\n return this._add(() => G9ERevocationDisposition.Delete, null);\n }\n\n markRevoked(): G9RevocationPolicyBuilder {\n return this._add(() => G9ERevocationDisposition.MarkRevoked, null);\n }\n\n quarantine(): G9RevocationPolicyBuilder {\n return this._add(() => G9ERevocationDisposition.Quarantine, null);\n }\n\n retainWhile(retainWhile: (row: G9RevokedRow) => boolean): G9RevocationPolicyBuilder {\n return this._add(() => G9ERevocationDisposition.Retain, retainWhile);\n }\n\n custom(decide: (row: G9RevokedRow, editor: G9RevokedRowEditor) => G9ERevocationDisposition): G9RevocationPolicyBuilder {\n return this._add(decide, null);\n }\n\n private _add(\n decide: (row: G9RevokedRow, editor: G9RevokedRowEditor) => G9ERevocationDisposition,\n retainWhile: ((row: G9RevokedRow) => boolean) | null,\n ): G9RevocationPolicyBuilder {\n this._owner.rules.push({ name: this._name, condition: this._condition, decide, retainWhile });\n return this._owner;\n }\n}\n\n/** A row in the holding area (`G9CQuarantinedRow`). */\nexport interface G9QuarantinedRow {\n localTable: string;\n key: string;\n reason: G9ERevocationReason;\n rule: string | null;\n hadPendingChanges: boolean;\n values: { [column: string]: unknown };\n atUtcMs: number;\n}\n\n/** A row kept in the app's table after revocation (`G9CRetainedRow`). */\nexport interface G9RetainedRow {\n localTable: string;\n key: string;\n disposition: G9ERevocationDisposition;\n reason: G9ERevocationReason;\n rule: string | null;\n atUtcMs: number;\n}\n\n// ---------------------------------------------------------------- failures\n\n/** What went wrong inside a client store, in terms an application can act on (`G9ELocalStoreFailure`). */\nexport const G9ELocalStoreFailure = {\n Other: 0,\n Corrupt: 1,\n DiskFull: 2,\n Busy: 3,\n} as const;\nexport type G9ELocalStoreFailure = (typeof G9ELocalStoreFailure)[keyof typeof G9ELocalStoreFailure];\n\n/** Implemented by a store's errors so the engine can give a run's failure a stable code (`IG9SyncLocalStoreFailure`). */\nexport interface G9SyncLocalStoreFailure {\n readonly localStoreFailure: G9ELocalStoreFailure;\n}\n\nexport function isLocalStoreFailure(error: unknown): error is G9SyncLocalStoreFailure {\n return typeof error === 'object' && error !== null && typeof (error as { localStoreFailure?: unknown }).localStoreFailure === 'number';\n}\n\n// ---------------------------------------------------------------- the store contract\n\n/**\n * The client storage provider (`IG9SyncClientStore`): local tables, change capture, subscription membership,\n * staging, apply and metadata. Every method takes an optional AbortSignal (the CancellationToken twin).\n */\nexport interface G9SyncClientStore {\n initialize(model: G9SyncClientModel, signal?: AbortSignal): Promise<void>;\n getClientId(signal?: AbortSignal): Promise<string>;\n getServerState(signal?: AbortSignal): Promise<G9ClientServerState>;\n saveHello(hello: G9DtHelloResult, signal?: AbortSignal): Promise<void>;\n getPendingBackfills(signal?: AbortSignal): Promise<G9DtBackfillRequest[]>;\n\n getSubscriptions(signal?: AbortSignal): Promise<G9SyncSubscriptionRecord[]>;\n ensureSubscription(\n name: string,\n parameters: G9SyncParameters,\n ephemeral: boolean,\n ttlMs: number | null,\n adoptExistingRows: boolean,\n signal?: AbortSignal,\n ): Promise<G9SyncSubscriptionRecord>;\n removeSubscription(key: string, policy: G9EMoveOutPolicy, signal?: AbortSignal): Promise<void>;\n releaseSubscriptionRows(key: string, signal?: AbortSignal): Promise<G9ApplyReport>;\n updateSubscription(key: string, update: G9SubscriptionUpdate, signal?: AbortSignal): Promise<void>;\n collectExpiredEphemeral(utcNowMs: number, signal?: AbortSignal): Promise<number>;\n\n buildPushBatch(tables: readonly G9DtNegotiatedTable[], maxRows: number, maxBytes: number, signal?: AbortSignal): Promise<G9PushBatch | null>;\n forgetUploadedRows(localTable: string, keys: readonly string[] | null, signal?: AbortSignal): Promise<number>;\n applyPushResult(batch: G9PushBatch, result: G9DtPushResult, signal?: AbortSignal): Promise<G9ApplyReport>;\n\n getStagedPull(signal?: AbortSignal): Promise<G9StagedPull | null>;\n /** The newest staged pull planned under exactly this generation, or null (ADR-0060). */\n getStagedPullForGeneration(generation: string, signal?: AbortSignal): Promise<G9StagedPull | null>;\n beginStagedPull(pull: G9StagedPull, signal?: AbortSignal): Promise<void>;\n pruneStagedPulls(olderThanUtcMs: number, maxBytes: number, keepGeneration: string | null, signal?: AbortSignal): Promise<number>;\n discardAllStagedPulls(signal?: AbortSignal): Promise<number>;\n stageChunk(runId: string, chunk: G9DtSyncChunk, signal?: AbortSignal): Promise<void>;\n /**\n * The store's offer to receive large first downloads as SQLite snapshot files (ADR-0072), or null for rows only. A store that\n * makes an offer stages file segments (`chunk.file`) in `stageChunk` and copies the files in `applyStagedPull`.\n */\n describeSnapshotFiles?(negotiated: readonly G9DtNegotiatedTable[]): G9DtSnapshotFileRequest | null;\n getResumePoint(runId: string, signal?: AbortSignal): Promise<G9ResumePoint>;\n applyStagedPull(\n runId: string,\n rowsApplied: ((rows: number) => void) | null,\n slices: ((slice: G9ApplySlice) => void) | null,\n signal?: AbortSignal,\n ): Promise<G9ApplyReport>;\n discardStagedPull(runId: string, signal?: AbortSignal): Promise<void>;\n\n computeDigest(subscriptionKey: string, table: G9DtNegotiatedTable, signal?: AbortSignal): Promise<G9DtTableDigest>;\n readLocalEntries(\n subscriptionKey: string,\n table: G9DtNegotiatedTable,\n buckets: ReadonlySet<number>,\n serverKeys: ReadonlySet<string>,\n signal?: AbortSignal,\n ): Promise<G9LocalRowEntry[]>;\n applyReconcile(\n subscriptionKey: string,\n table: G9DtNegotiatedTable,\n fetchedFrames: readonly Uint8Array[],\n addMembership: readonly string[],\n moveOut: readonly string[],\n policy: G9EMoveOutPolicy,\n signal?: AbortSignal,\n ): Promise<G9ApplyReport>;\n\n getPendingSummary(signal?: AbortSignal): Promise<G9PendingSummary>;\n enqueueCommand(\n request: G9CommandRequest,\n applyLocally: ((writer: G9LocalCommandWriter, signal?: AbortSignal) => Promise<void>) | null,\n signal?: AbortSignal,\n ): Promise<G9LocalCommand>;\n getQuarantined(signal?: AbortSignal): Promise<G9QuarantinedRow[]>;\n restoreQuarantined(localTable: string, key: string, signal?: AbortSignal): Promise<number>;\n discardQuarantined(localTable: string | null, key: string | null, signal?: AbortSignal): Promise<number>;\n getRetained(signal?: AbortSignal): Promise<G9RetainedRow[]>;\n reevaluateRetained(signal?: AbortSignal): Promise<number>;\n getCommands(state: G9ELocalCommandState | null, limit: number, signal?: AbortSignal): Promise<G9LocalCommand[]>;\n readChangeFeed(localTable: string, afterSequence: number, limit: number, signal?: AbortSignal): Promise<G9ChangeFeedEntry[]>;\n getChangeSequence(signal?: AbortSignal): Promise<number>;\n getChangeFeedFloor(signal?: AbortSignal): Promise<number>;\n getRejections(signal?: AbortSignal): Promise<G9SyncRejection[]>;\n retryRejected(localTable: string | null, key: string | null, signal?: AbortSignal): Promise<number>;\n markDirty(localTable: string, keys: readonly string[], signal?: AbortSignal): Promise<number>;\n adoptExistingRows(signal?: AbortSignal): Promise<number>;\n saveRunReport(report: G9SyncRunReport, keep: number, signal?: AbortSignal): Promise<void>;\n getRecentRunReports(count: number, signal?: AbortSignal): Promise<G9SyncRunReport[]>;\n getPhaseStatistics(signal?: AbortSignal): Promise<{ [key: string]: number }>;\n recordPhaseStatistics(samples: { [key: string]: number }, signal?: AbortSignal): Promise<void>;\n purgeDeletedFeed(keep: number, signal?: AbortSignal): Promise<number>;\n dispose(): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"storeTypes.js","sourceRoot":"","sources":["../../src/client/storeTypes.ts"],"names":[],"mappings":"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","sourcesContent":["// Twins of G9SyncData.Client/Storage/*.cs: the client store contract, its records, local commands, revocation rules\n// and apply diagnostics. The engine is store-agnostic; `@g9tm/syncdata/sqlite` is the implementation.\nimport type { G9EMoveOutPolicy } from '../abstractions/enums.js';\nimport type { G9Long } from '../abstractions/long.js';\nimport type { G9SyncParameters } from '../abstractions/parameters.js';\nimport type {\n G9DtBackfillRequest,\n G9DtCommand,\n G9DtHelloResult,\n G9DtNegotiatedTable,\n G9DtPullContinuation,\n G9DtPullPlan,\n G9DtPushResult,\n G9DtSnapshotFileRequest,\n G9DtSubscriptionDefinition,\n G9DtSubscriptionRef,\n G9DtSyncChunk,\n G9DtTableColumns,\n G9DtTableDigest,\n} from '../protocol/dtos.js';\nimport type { G9SyncClientModel } from './model.js';\n\n// ---------------------------------------------------------------- server state and subscriptions\n\n/** What the client last learned from the server (`G9CClientServerState`). */\nexport interface G9ClientServerState {\n epoch: string | null;\n lastHelloUtcMs: number | null;\n /** Server clock minus device clock, ms. */\n serverClockOffsetMs: number;\n tables: G9DtNegotiatedTable[];\n definitions: G9DtSubscriptionDefinition[];\n}\n\n/** `G9ESubscriptionState`. */\nexport const G9ESubscriptionState = {\n Active: 0,\n NeedsReconcile: 1,\n Failed: 2,\n} as const;\nexport type G9ESubscriptionState = (typeof G9ESubscriptionState)[keyof typeof G9ESubscriptionState];\n\n/** A local subscription (`G9CSyncSubscriptionRecord`). Times are epoch milliseconds UTC. */\nexport interface G9SyncSubscriptionRecord {\n key: string;\n name: string;\n parameters: G9SyncParameters;\n /** Membership slot (bit) for persistent subscriptions; the shared ephemeral slot otherwise. */\n slot: number;\n ephemeral: boolean;\n ttlMs: number | null;\n cursor: string | null;\n /** The move-out policy the server last announced; kept so it still applies once access is gone. */\n moveOutPolicy: G9EMoveOutPolicy | null;\n definitionHash: string | null;\n authHash: string | null;\n state: G9ESubscriptionState;\n lastErrorCode: string | null;\n createdUtcMs: number;\n lastSuccessUtcMs: number | null;\n lastReconcileUtcMs: number | null;\n lastAccessUtcMs: number | null;\n}\n\n/** `G9CSyncSubscriptionRecord.ToReference()`. */\nexport function toSubscriptionRef(record: G9SyncSubscriptionRecord): G9DtSubscriptionRef {\n return {\n name: record.name,\n parameters: record.parameters.values,\n cursor: record.cursor,\n definitionHash: record.definitionHash,\n authHash: record.authHash,\n };\n}\n\n/** A partial update of a subscription record; absent members are left unchanged (`G9CSubscriptionUpdate`). */\nexport interface G9SubscriptionUpdate {\n cursor?: string | null;\n definitionHash?: string | null;\n authHash?: string | null;\n state?: G9ESubscriptionState | null;\n errorCode?: string | null;\n clearError?: boolean;\n succeeded?: boolean;\n reconciled?: boolean;\n accessed?: boolean;\n clearCursor?: boolean;\n}\n\n// ---------------------------------------------------------------- push and pull\n\n/** A push batch built from dirty rows (`G9CPushBatch`). */\nexport interface G9PushBatch {\n batchId: number;\n /** Highest local change sequence included; rows changed after it stay dirty after the acknowledgement. */\n maxSequence: number;\n frames: Uint8Array[];\n tables: G9DtTableColumns[];\n rowCount: number;\n /** True when the byte budget ended the batch early: more pending rows follow. */\n truncated: boolean;\n /** Rows rejected on the device while building the batch (G9S-REJ-TOO-LARGE). */\n rejectedLocally: number;\n /** Commands carried by this batch, in device order. */\n commands: G9DtCommand[];\n}\n\n/** A pull being staged, persisted across restarts (`G9CStagedPull`). */\nexport interface G9StagedPull {\n runId: string;\n plan: G9DtPullPlan;\n createdUtcMs: number;\n backfills: G9DtBackfillRequest[];\n /** What the world looked like when the plan was made (ADR-0046); a run only resumes a stage whose generation matches. */\n generation: string | null;\n /** The cursor each planned subscription had when the plan was made, by subscription key (ADR-0060). */\n plannedFromCursors: { [subscriptionKey: string]: string | null };\n /** The device clock when the stage began, assigned by the store. */\n stageSequence: number;\n}\n\n/** One committed slice of a staged pull (`G9CApplySlice`). */\nexport interface G9ApplySlice {\n report: G9ApplyReport;\n rowsApplied: number;\n rowsAppliedTotal: number;\n rowsPlanned: number;\n isFinal: boolean;\n}\n\n/** `G9CResumePoint`. */\nexport interface G9ResumePoint {\n continuation: G9DtPullContinuation | null;\n stagedRows: number;\n stagedBytes: number;\n complete: boolean;\n}\n\n// ---------------------------------------------------------------- reports\n\n/** Why a table large enough for index deferral kept its indexes live. */\nexport const REASON_TABLE_NOT_EMPTY = 'table-not-empty';\nconst MAX_FOREIGN_TRIGGER_NAMES = 10;\n\n/** How an apply ran, as opposed to what it changed (`G9CApplyDiagnostics`, ADR-0058). */\n/** A file table copied record for record into an empty table with identical columns (the transfer optimisation). */\nexport const FILE_MODE_COPY = 'copy';\n/** A file table copied into an empty table by column name. */\nexport const FILE_MODE_COLUMNS = 'columns';\n/** A file table merged into a table that held rows or protected rows: changed rows only. */\nexport const FILE_MODE_MERGE = 'merge';\n/** A file table inserted into a table holding only OTHER rows (a second site's first load): every row is new, no per-row diff. */\nexport const FILE_MODE_APPEND = 'append';\n\nexport class G9ApplyDiagnostics {\n transactions = 0;\n transactionsWithTriggersSkipped = 0;\n readonly foreignTriggers: string[] = [];\n readonly deferredIndexes: { [table: string]: number } = {};\n readonly liveIndexReasons: { [table: string]: string } = {};\n readonly rowsByTable: { [table: string]: number } = {};\n readonly msByTable: { [table: string]: number } = {};\n indexRebuildMs = 0;\n elapsedMs = 0;\n /** Snapshot files copied in (ADR-0072). */\n snapshotFiles = 0;\n /** Rows that arrived through snapshot files. */\n fileRows = 0;\n /** Milliseconds spent copying snapshot files, metadata included. */\n fileCopyMs = 0;\n /** How each table came out of a snapshot file: `copy`, `columns` or `merge` (see FILE_MODE_*). */\n readonly fileTables: { [table: string]: string } = {};\n\n /** True when every transaction ran with trigger execution off, false when any ran with it on, null when nothing was applied. */\n get triggersSkipped(): boolean | null {\n return this.transactions === 0 ? null : this.transactionsWithTriggersSkipped === this.transactions;\n }\n\n get deferredIndexCount(): number {\n let total = 0;\n for (const key of Object.keys(this.deferredIndexes)) total += this.deferredIndexes[key] as number;\n return total;\n }\n\n merge(other: G9ApplyDiagnostics): void {\n this.transactions += other.transactions;\n this.transactionsWithTriggersSkipped += other.transactionsWithTriggersSkipped;\n this.indexRebuildMs += other.indexRebuildMs;\n this.elapsedMs += other.elapsedMs;\n this.snapshotFiles += other.snapshotFiles;\n this.fileRows += other.fileRows;\n this.fileCopyMs += other.fileCopyMs;\n for (const key of Object.keys(other.fileTables)) this.fileTables[key] = other.fileTables[key] as string;\n for (const name of other.foreignTriggers) {\n if (this.foreignTriggers.length >= MAX_FOREIGN_TRIGGER_NAMES) break;\n if (!this.foreignTriggers.some((n) => n.toLowerCase() === name.toLowerCase())) this.foreignTriggers.push(name);\n }\n addAll(this.deferredIndexes, other.deferredIndexes);\n for (const key of Object.keys(other.liveIndexReasons)) this.liveIndexReasons[key] = other.liveIndexReasons[key] as string;\n addAll(this.rowsByTable, other.rowsByTable);\n addAll(this.msByTable, other.msByTable);\n }\n\n /** One line for a log: what engaged, what did not and why, and the tables that took the time. */\n toString(): string {\n if (this.transactions === 0) return 'nothing applied';\n let text = `${this.transactions} transaction(s) in ${this.elapsedMs} ms; triggers `;\n text +=\n this.triggersSkipped === true\n ? 'skipped'\n : this.transactionsWithTriggersSkipped === 0\n ? 'LIVE'\n : `skipped in ${this.transactionsWithTriggersSkipped} of ${this.transactions}`;\n if (this.foreignTriggers.length > 0) text += ' (foreign: ' + this.foreignTriggers.join(', ') + ')';\n const deferred = Object.keys(this.deferredIndexes);\n if (deferred.length > 0) {\n text += `; ${this.deferredIndexCount} index(es) rebuilt after the load in ${this.indexRebuildMs} ms (` + deferred.map((t) => t + ':' + this.deferredIndexes[t]).join(', ') + ')';\n }\n const live = Object.keys(this.liveIndexReasons);\n if (live.length > 0) text += '; live indexes on ' + live.map((t) => t + ' (' + this.liveIndexReasons[t] + ')').join(', ');\n if (this.snapshotFiles > 0) {\n text +=\n `; ${this.snapshotFiles} snapshot file(s), ${this.fileRows} rows copied in ${this.fileCopyMs} ms (` +\n Object.keys(this.fileTables)\n .map((t) => t + ':' + this.fileTables[t])\n .join(', ') +\n ')';\n }\n const slowest = Object.keys(this.msByTable)\n .sort((a, b) => (this.msByTable[b] as number) - (this.msByTable[a] as number))\n .slice(0, 4);\n if (slowest.length > 0) {\n text +=\n '; ' +\n slowest\n .map((t) => {\n const ms = this.msByTable[t] as number;\n const rows = this.rowsByTable[t] ?? 0;\n const rate = ms > 0 ? Math.floor((rows * 1000) / ms) : rows;\n return `${t} ${rows} rows in ${ms} ms (${rate}/s)`;\n })\n .join(', ');\n }\n return text;\n }\n}\n\nfunction addAll(target: { [key: string]: number }, source: { [key: string]: number }): void {\n for (const key of Object.keys(source)) target[key] = (target[key] ?? 0) + (source[key] as number);\n}\n\n/** What an apply changed (`G9CApplyReport`). */\nexport class G9ApplyReport {\n upserts = 0;\n deletes = 0;\n moveOuts = 0;\n membershipAdded = 0;\n /** Rows skipped because they carry a pending local change. */\n skippedDirty = 0;\n accepted = 0;\n rejected = 0;\n conflicts = 0;\n commandsCompleted = 0;\n revokedDeleted = 0;\n revokedQuarantined = 0;\n revokedRetained = 0;\n /** Changed keys per local table (capped per table). */\n readonly changedKeys: { [localTable: string]: string[] } = {};\n /** Upserts whose row already held exactly the incoming values (ADR-0058). */\n unchangedRows = 0;\n readonly diagnostics = new G9ApplyDiagnostics();\n\n get total(): number {\n return this.upserts + this.deletes + this.moveOuts;\n }\n\n merge(other: G9ApplyReport): void {\n this.unchangedRows += other.unchangedRows;\n this.diagnostics.merge(other.diagnostics);\n this.upserts += other.upserts;\n this.deletes += other.deletes;\n this.moveOuts += other.moveOuts;\n this.membershipAdded += other.membershipAdded;\n this.skippedDirty += other.skippedDirty;\n this.accepted += other.accepted;\n this.rejected += other.rejected;\n this.conflicts += other.conflicts;\n this.commandsCompleted += other.commandsCompleted;\n this.revokedDeleted += other.revokedDeleted;\n this.revokedQuarantined += other.revokedQuarantined;\n this.revokedRetained += other.revokedRetained;\n for (const table of Object.keys(other.changedKeys)) {\n const list = this.changedKeys[table] ?? (this.changedKeys[table] = []);\n for (const key of other.changedKeys[table] as string[]) list.push(key);\n }\n }\n}\n\n/** A local row as seen by reconciliation (`G9CLocalRowEntry`). */\nexport interface G9LocalRowEntry {\n key: string;\n bucket: number;\n version: number;\n hash: G9Long;\n member: boolean;\n dirty: boolean;\n}\n\nexport interface G9PendingTable {\n localTable: string;\n upserts: number;\n deletes: number;\n rejected: number;\n}\n\nexport interface G9PendingSummary {\n totalRows: number;\n rejectedRows: number;\n /** Commands recorded on the device and not yet executed by the server. */\n pendingCommands: number;\n tables: G9PendingTable[];\n}\n\n/** One entry of the local change feed (`G9CChangeFeedEntry`). */\nexport interface G9ChangeFeedEntry {\n sequence: number;\n key: string;\n deleted: boolean;\n localPending: boolean;\n}\n\n/**\n * Narrows a change-feed read to the keys a query of the app's own tables selects (`G9CChangeFeedKeyFilter`): a projection of one\n * site no longer walks the entries of every other site. Deletions are always returned - their rows are gone, so no query can place\n * them. `keySql` is one SELECT of one column, embedded as `pk IN (...)`; parameters are bound by name (`$name`, `@name`, `:name`)\n * and `$g9t`, `$g9a`, `$g9l` belong to the read.\n */\nexport interface G9ChangeFeedKeyFilter {\n keySql: string;\n parameters?: { [name: string]: string | number | null };\n}\n\nconst RESERVED_FEED_PARAMETERS = ['$g9t', '$g9a', '$g9l'];\n\n/** Throws when the filter cannot be embedded safely (`G9CChangeFeedKeyFilter.Validate`). */\nexport function validateChangeFeedKeyFilter(filter: G9ChangeFeedKeyFilter): void {\n const sql = (filter.keySql ?? '').trim();\n if (sql.slice(0, 6).toUpperCase() !== 'SELECT' || sql.indexOf(';') >= 0) {\n throw new Error('A change-feed key filter is a single SELECT statement of one column.');\n }\n for (const name of Object.keys(filter.parameters ?? {})) {\n if (name.length < 2 || '$@:'.indexOf(name[0] as string) < 0) throw new Error(`Change-feed key filter parameter '${name}' must be named ($name, @name or :name).`);\n if (RESERVED_FEED_PARAMETERS.indexOf(name.toLowerCase()) >= 0) throw new Error(`Change-feed key filter parameter '${name}' is reserved by the read.`);\n }\n}\n\nexport interface G9SyncRejection {\n localTable: string;\n key: string;\n code: string;\n message: string | null;\n sqlState: string | null;\n constraint: string | null;\n column: string | null;\n details: string | null;\n atUtcMs: number;\n}\n\n/** A persisted summary of one run (`G9CSyncRunReport`); serialized with the .NET JSON names into g9_runs. */\nexport interface G9SyncRunReport {\n runId: string;\n startedUtcMs: number;\n durationMs: number;\n outcome: string;\n priority: string;\n errorCode: string | null;\n errorMessage: string | null;\n pushed: number;\n rejected: number;\n conflicts: number;\n downloaded: number;\n applied: number;\n downloadedBytes: number;\n subscriptions: string[];\n phaseMs: { [phase: string]: number };\n triggersSkipped: boolean | null;\n foreignTriggers: string[];\n applyTransactions: number;\n indexesDeferred: number;\n indexRebuildMs: number;\n liveIndexReasons: { [table: string]: string };\n unchangedRows: number;\n}\n\n// ---------------------------------------------------------------- local commands (ADR-0025)\n\n/** `G9ELocalCommandState`. */\nexport const G9ELocalCommandState = {\n Pending: 0,\n Ok: 1,\n Rejected: 2,\n Superseded: 3,\n} as const;\nexport type G9ELocalCommandState = (typeof G9ELocalCommandState)[keyof typeof G9ELocalCommandState];\n\n/** A command recorded on this device (`G9CLocalCommand`). */\nexport interface G9LocalCommand {\n commandId: string;\n sequence: number;\n name: string;\n version: number;\n payload: string;\n createdUtcMs: number;\n state: G9ELocalCommandState;\n code: string | null;\n message: string | null;\n result: string | null;\n completedUtcMs: number | null;\n}\n\n/** What to record (`G9CCommandRequest`). */\nexport interface G9CommandRequest {\n name: string;\n version?: number;\n /** JSON payload for the server's handler. */\n payload: string;\n}\n\n/** A value an app binds through {@link G9LocalCommandWriter}: the SQLite driver's value forms, plus booleans. */\nexport type G9LocalSqlArgument = null | number | string | boolean | ArrayBuffer | Uint8Array | { int64: string } | G9Long;\n\n/**\n * Writes a command's local effects in the transaction that records it (`IG9LocalCommandWriter`). Rows written here are\n * not uploaded as row changes; they are protected from pulls until the server's result arrives.\n */\nexport interface G9LocalCommandWriter {\n /** Executes SQL; parameters bind positionally as `$p0`, `$p1`, … */\n execute(sql: string, ...args: G9LocalSqlArgument[]): Promise<number>;\n scalar(sql: string, ...args: G9LocalSqlArgument[]): Promise<unknown>;\n}\n\n// ---------------------------------------------------------------- revocation (ADR-0026)\n\n/** `G9ERevocationReason`. */\nexport const G9ERevocationReason = {\n LeftScope: 0,\n Unsubscribed: 1,\n Expired: 2,\n AccessRevoked: 3,\n} as const;\nexport type G9ERevocationReason = (typeof G9ERevocationReason)[keyof typeof G9ERevocationReason];\n\n/** `G9ERevocationDisposition`. */\nexport const G9ERevocationDisposition = {\n Delete: 0,\n MarkRevoked: 1,\n Quarantine: 2,\n Retain: 3,\n} as const;\nexport type G9ERevocationDisposition = (typeof G9ERevocationDisposition)[keyof typeof G9ERevocationDisposition];\n\n/** A row no subscription holds any more, as a revocation rule sees it (`G9CRevokedRow`). */\nexport interface G9RevokedRow {\n localTable: string;\n key: string;\n reason: G9ERevocationReason;\n subscriptionKey: string | null;\n hasPendingChanges: boolean;\n isRejected: boolean;\n /** Column values (key included) as stored locally; empty when the row no longer exists locally. */\n values: { [column: string]: unknown };\n}\n\n/** Lets a custom rule change the row before its disposition applies. */\nexport interface G9RevokedRowEditor {\n set(column: string, value: unknown): void;\n}\n\nexport interface G9RevocationRule {\n readonly name: string;\n readonly condition: (row: G9RevokedRow) => boolean;\n readonly decide: (row: G9RevokedRow, editor: G9RevokedRowEditor) => G9ERevocationDisposition;\n readonly retainWhile: ((row: G9RevokedRow) => boolean) | null;\n}\n\n/** Ordered revocation rules (`G9CRevocationPolicy`): the first matching rule decides. */\nexport class G9RevocationPolicy {\n readonly rules: readonly G9RevocationRule[];\n\n private constructor(rules: readonly G9RevocationRule[]) {\n this.rules = rules;\n }\n\n /** No rules: delete clean rows, quarantine rows with pending changes. */\n static readonly default = new G9RevocationPolicy([]);\n\n static create(configure: (builder: G9RevocationPolicyBuilder) => void): G9RevocationPolicy {\n const builder = new G9RevocationPolicyBuilder();\n configure(builder);\n return new G9RevocationPolicy(builder.rules.slice());\n }\n\n decide(row: G9RevokedRow, editor: G9RevokedRowEditor): { rule: G9RevocationRule | null; disposition: G9ERevocationDisposition } {\n for (const rule of this.rules) {\n if (rule.condition(row)) return { rule, disposition: rule.decide(row, editor) };\n }\n return { rule: null, disposition: row.hasPendingChanges ? G9ERevocationDisposition.Quarantine : G9ERevocationDisposition.Delete };\n }\n\n find(name: string): G9RevocationRule | null {\n for (const rule of this.rules) if (rule.name === name) return rule;\n return null;\n }\n}\n\nexport class G9RevocationPolicyBuilder {\n readonly rules: G9RevocationRule[] = [];\n\n when(name: string, condition: (row: G9RevokedRow) => boolean): G9RevocationActionBuilder {\n if (typeof name !== 'string' || name.trim() === '') throw new TypeError('A rule needs a name.');\n if (this.rules.some((r) => r.name === name)) throw new RangeError(`Revocation rule '${name}' is declared twice.`);\n return new G9RevocationActionBuilder(this, name, condition);\n }\n\n /** Rows of one local table. */\n forTable(name: string, localTable: string, condition?: (row: G9RevokedRow) => boolean): G9RevocationActionBuilder {\n const table = localTable.toLowerCase();\n return this.when(name, (row) => row.localTable.toLowerCase() === table && (condition ? condition(row) : true));\n }\n}\n\nexport class G9RevocationActionBuilder {\n private readonly _owner: G9RevocationPolicyBuilder;\n private readonly _name: string;\n private readonly _condition: (row: G9RevokedRow) => boolean;\n\n constructor(owner: G9RevocationPolicyBuilder, name: string, condition: (row: G9RevokedRow) => boolean) {\n this._owner = owner;\n this._name = name;\n this._condition = condition;\n }\n\n delete(): G9RevocationPolicyBuilder {\n return this._add(() => G9ERevocationDisposition.Delete, null);\n }\n\n markRevoked(): G9RevocationPolicyBuilder {\n return this._add(() => G9ERevocationDisposition.MarkRevoked, null);\n }\n\n quarantine(): G9RevocationPolicyBuilder {\n return this._add(() => G9ERevocationDisposition.Quarantine, null);\n }\n\n retainWhile(retainWhile: (row: G9RevokedRow) => boolean): G9RevocationPolicyBuilder {\n return this._add(() => G9ERevocationDisposition.Retain, retainWhile);\n }\n\n custom(decide: (row: G9RevokedRow, editor: G9RevokedRowEditor) => G9ERevocationDisposition): G9RevocationPolicyBuilder {\n return this._add(decide, null);\n }\n\n private _add(\n decide: (row: G9RevokedRow, editor: G9RevokedRowEditor) => G9ERevocationDisposition,\n retainWhile: ((row: G9RevokedRow) => boolean) | null,\n ): G9RevocationPolicyBuilder {\n this._owner.rules.push({ name: this._name, condition: this._condition, decide, retainWhile });\n return this._owner;\n }\n}\n\n/** A row in the holding area (`G9CQuarantinedRow`). */\nexport interface G9QuarantinedRow {\n localTable: string;\n key: string;\n reason: G9ERevocationReason;\n rule: string | null;\n hadPendingChanges: boolean;\n values: { [column: string]: unknown };\n atUtcMs: number;\n}\n\n/** A row kept in the app's table after revocation (`G9CRetainedRow`). */\nexport interface G9RetainedRow {\n localTable: string;\n key: string;\n disposition: G9ERevocationDisposition;\n reason: G9ERevocationReason;\n rule: string | null;\n atUtcMs: number;\n}\n\n// ---------------------------------------------------------------- failures\n\n/** What went wrong inside a client store, in terms an application can act on (`G9ELocalStoreFailure`). */\nexport const G9ELocalStoreFailure = {\n Other: 0,\n Corrupt: 1,\n DiskFull: 2,\n Busy: 3,\n} as const;\nexport type G9ELocalStoreFailure = (typeof G9ELocalStoreFailure)[keyof typeof G9ELocalStoreFailure];\n\n/** Implemented by a store's errors so the engine can give a run's failure a stable code (`IG9SyncLocalStoreFailure`). */\nexport interface G9SyncLocalStoreFailure {\n readonly localStoreFailure: G9ELocalStoreFailure;\n}\n\nexport function isLocalStoreFailure(error: unknown): error is G9SyncLocalStoreFailure {\n return typeof error === 'object' && error !== null && typeof (error as { localStoreFailure?: unknown }).localStoreFailure === 'number';\n}\n\n// ---------------------------------------------------------------- the store contract\n\n/**\n * The client storage provider (`IG9SyncClientStore`): local tables, change capture, subscription membership,\n * staging, apply and metadata. Every method takes an optional AbortSignal (the CancellationToken twin).\n */\nexport interface G9SyncClientStore {\n initialize(model: G9SyncClientModel, signal?: AbortSignal): Promise<void>;\n getClientId(signal?: AbortSignal): Promise<string>;\n getServerState(signal?: AbortSignal): Promise<G9ClientServerState>;\n saveHello(hello: G9DtHelloResult, signal?: AbortSignal): Promise<void>;\n getPendingBackfills(signal?: AbortSignal): Promise<G9DtBackfillRequest[]>;\n\n getSubscriptions(signal?: AbortSignal): Promise<G9SyncSubscriptionRecord[]>;\n ensureSubscription(\n name: string,\n parameters: G9SyncParameters,\n ephemeral: boolean,\n ttlMs: number | null,\n adoptExistingRows: boolean,\n signal?: AbortSignal,\n ): Promise<G9SyncSubscriptionRecord>;\n removeSubscription(key: string, policy: G9EMoveOutPolicy, signal?: AbortSignal): Promise<void>;\n releaseSubscriptionRows(key: string, signal?: AbortSignal): Promise<G9ApplyReport>;\n updateSubscription(key: string, update: G9SubscriptionUpdate, signal?: AbortSignal): Promise<void>;\n collectExpiredEphemeral(utcNowMs: number, signal?: AbortSignal): Promise<number>;\n\n buildPushBatch(tables: readonly G9DtNegotiatedTable[], maxRows: number, maxBytes: number, signal?: AbortSignal): Promise<G9PushBatch | null>;\n forgetUploadedRows(localTable: string, keys: readonly string[] | null, signal?: AbortSignal): Promise<number>;\n applyPushResult(batch: G9PushBatch, result: G9DtPushResult, signal?: AbortSignal): Promise<G9ApplyReport>;\n\n getStagedPull(signal?: AbortSignal): Promise<G9StagedPull | null>;\n /** The newest staged pull planned under exactly this generation, or null (ADR-0060). */\n getStagedPullForGeneration(generation: string, signal?: AbortSignal): Promise<G9StagedPull | null>;\n beginStagedPull(pull: G9StagedPull, signal?: AbortSignal): Promise<void>;\n pruneStagedPulls(olderThanUtcMs: number, maxBytes: number, keepGeneration: string | null, signal?: AbortSignal): Promise<number>;\n discardAllStagedPulls(signal?: AbortSignal): Promise<number>;\n stageChunk(runId: string, chunk: G9DtSyncChunk, signal?: AbortSignal): Promise<void>;\n /**\n * The store's offer to receive large first downloads as SQLite snapshot files (ADR-0072), or null for rows only. A store that\n * makes an offer stages file segments (`chunk.file`) in `stageChunk` and copies the files in `applyStagedPull`.\n */\n describeSnapshotFiles?(negotiated: readonly G9DtNegotiatedTable[]): G9DtSnapshotFileRequest | null;\n getResumePoint(runId: string, signal?: AbortSignal): Promise<G9ResumePoint>;\n applyStagedPull(\n runId: string,\n rowsApplied: ((rows: number) => void) | null,\n slices: ((slice: G9ApplySlice) => void) | null,\n signal?: AbortSignal,\n ): Promise<G9ApplyReport>;\n discardStagedPull(runId: string, signal?: AbortSignal): Promise<void>;\n\n computeDigest(subscriptionKey: string, table: G9DtNegotiatedTable, signal?: AbortSignal): Promise<G9DtTableDigest>;\n readLocalEntries(\n subscriptionKey: string,\n table: G9DtNegotiatedTable,\n buckets: ReadonlySet<number>,\n serverKeys: ReadonlySet<string>,\n signal?: AbortSignal,\n ): Promise<G9LocalRowEntry[]>;\n applyReconcile(\n subscriptionKey: string,\n table: G9DtNegotiatedTable,\n fetchedFrames: readonly Uint8Array[],\n addMembership: readonly string[],\n moveOut: readonly string[],\n policy: G9EMoveOutPolicy,\n signal?: AbortSignal,\n ): Promise<G9ApplyReport>;\n\n getPendingSummary(signal?: AbortSignal): Promise<G9PendingSummary>;\n enqueueCommand(\n request: G9CommandRequest,\n applyLocally: ((writer: G9LocalCommandWriter, signal?: AbortSignal) => Promise<void>) | null,\n signal?: AbortSignal,\n ): Promise<G9LocalCommand>;\n getQuarantined(signal?: AbortSignal): Promise<G9QuarantinedRow[]>;\n restoreQuarantined(localTable: string, key: string, signal?: AbortSignal): Promise<number>;\n discardQuarantined(localTable: string | null, key: string | null, signal?: AbortSignal): Promise<number>;\n getRetained(signal?: AbortSignal): Promise<G9RetainedRow[]>;\n reevaluateRetained(signal?: AbortSignal): Promise<number>;\n getCommands(state: G9ELocalCommandState | null, limit: number, signal?: AbortSignal): Promise<G9LocalCommand[]>;\n readChangeFeed(localTable: string, afterSequence: number, limit: number, signal?: AbortSignal): Promise<G9ChangeFeedEntry[]>;\n /** The feed narrowed to the keys `keyFilter` selects, plus every deletion; fewer than `limit` entries means the read is complete. */\n readChangeFeedFiltered(localTable: string, afterSequence: number, limit: number, keyFilter: G9ChangeFeedKeyFilter, signal?: AbortSignal): Promise<G9ChangeFeedEntry[]>;\n getChangeSequence(signal?: AbortSignal): Promise<number>;\n getChangeFeedFloor(signal?: AbortSignal): Promise<number>;\n getRejections(signal?: AbortSignal): Promise<G9SyncRejection[]>;\n retryRejected(localTable: string | null, key: string | null, signal?: AbortSignal): Promise<number>;\n markDirty(localTable: string, keys: readonly string[], signal?: AbortSignal): Promise<number>;\n adoptExistingRows(signal?: AbortSignal): Promise<number>;\n saveRunReport(report: G9SyncRunReport, keep: number, signal?: AbortSignal): Promise<void>;\n getRecentRunReports(count: number, signal?: AbortSignal): Promise<G9SyncRunReport[]>;\n getPhaseStatistics(signal?: AbortSignal): Promise<{ [key: string]: number }>;\n recordPhaseStatistics(samples: { [key: string]: number }, signal?: AbortSignal): Promise<void>;\n purgeDeletedFeed(keep: number, signal?: AbortSignal): Promise<number>;\n dispose(): Promise<void>;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** The package version; equal to the .NET packages' (G9SyncDataVersion) — one release, both sides. */
2
- export declare const VERSION = "1.3.1";
2
+ export declare const VERSION = "1.5.0";
3
3
  export { G9EConflictPolicy, G9EMoveOutPolicy, G9ESyncColumnType, G9ESyncDirection, columnTypeName, isColumnType } from './abstractions/enums.js';
4
4
  export { G9SyncErrorCodes, G9SyncException, isRetryableCode, isSyncException, type G9SyncExceptionOptions } from './abstractions/errors.js';
5
5
  export { G9Long, parseJson, stringifyJson } from './abstractions/long.js';
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // doubles `@g9tm/syncdata/testing`, the pure-JavaScript Brotli decoder `@g9tm/syncdata/brotli-js`; Lynx wiring is the
4
4
  // separate package `@g9tm/syncdata-lynx`. Every public type keeps its .NET name without the G9C prefix (PARITY.md).
5
5
  /** The package version; equal to the .NET packages' (G9SyncDataVersion) — one release, both sides. */
6
- export const VERSION = '1.3.1';
6
+ export const VERSION = '1.5.0';
7
7
  // abstractions
8
8
  export { G9EConflictPolicy, G9EMoveOutPolicy, G9ESyncColumnType, G9ESyncDirection, columnTypeName, isColumnType } from './abstractions/enums.js';
9
9
  export { G9SyncErrorCodes, G9SyncException, isRetryableCode, isSyncException } from './abstractions/errors.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wHAAwH;AACxH,uHAAuH;AACvH,sHAAsH;AACtH,oHAAoH;AAEpH,sGAAsG;AACtG,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,eAAe;AACf,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACjJ,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAA+B,MAAM,0BAA0B,CAAC;AAC5I,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACvG,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAElF,WAAW;AACX,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC5G,OAAO,EACL,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,UAAU,EACV,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,kBAAkB,GAEnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACrI,OAAO,EAAE,gBAAgB,EAA4E,MAAM,+BAA+B,CAAC;AAC3I,OAAO,EACL,gBAAgB,EAChB,2BAA2B,EAC3B,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,2BAA2B,GAI5B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAqB,MAAM,4BAA4B,CAAC;AAE3H,SAAS;AACT,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,wBAAwB,EAAmD,MAAM,mBAAmB,CAAC;AAC9K,cAAc,wBAAwB,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,SAAS,EAAyB,MAAM,sBAAsB,CAAC;AAC7G,OAAO,EACL,cAAc,EACd,eAAe,EACf,cAAc,EACd,SAAS,EACT,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,YAAY,EACZ,oBAAoB,GAYrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEnE,2CAA2C;AAC3C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["// @g9tm/syncdata — the TypeScript twin of the G9SyncData .NET client (abstractions, protocol, engine). The SQLite store\n// is `@g9tm/syncdata/sqlite`, the SignalR transport `@g9tm/syncdata/signalr`, Node helpers `@g9tm/syncdata/node`, test\n// doubles `@g9tm/syncdata/testing`, the pure-JavaScript Brotli decoder `@g9tm/syncdata/brotli-js`; Lynx wiring is the\n// separate package `@g9tm/syncdata-lynx`. Every public type keeps its .NET name without the G9C prefix (PARITY.md).\n\n/** The package version; equal to the .NET packages' (G9SyncDataVersion) — one release, both sides. */\nexport const VERSION = '1.3.1';\n\n// abstractions\nexport { G9EConflictPolicy, G9EMoveOutPolicy, G9ESyncColumnType, G9ESyncDirection, columnTypeName, isColumnType } from './abstractions/enums.js';\nexport { G9SyncErrorCodes, G9SyncException, isRetryableCode, isSyncException, type G9SyncExceptionOptions } from './abstractions/errors.js';\nexport { G9Long, parseJson, stringifyJson } from './abstractions/long.js';\nexport { EMPTY_GUID, guidToN, newGuid, normalizeGuid, tryNormalizeGuid } from './abstractions/guid.js';\nexport { G9SyncKey } from './abstractions/key.js';\nexport { G9SyncDigest } from './abstractions/digest.js';\nexport { G9SyncParameters, escapeDataString } from './abstractions/parameters.js';\n\n// protocol\nexport * from './protocol/dtos.js';\nexport { G9SyncHubErrors, G9SyncProtocol, negotiateFrameVersion, tableGroup } from './protocol/protocol.js';\nexport {\n bytesField,\n dateTimeMs,\n fromMessagePackNames,\n G9WireGuid,\n GUID_EXTENSION_TYPE,\n readChangesAvailable,\n readContinuation,\n readHelloResult,\n readPullPlan,\n readPushResult,\n readReconcileSummary,\n readSyncChunk,\n toMessagePackNames,\n type G9WireFormat,\n} from './protocol/wire.js';\nexport { G9ECompression, G9EPushRowOp, G9ERowBatchOp, G9RowBatchFormat, G9RowBatchFormatError } from './protocol/rowBatch/format.js';\nexport { G9RowBatchWriter, type G9DateTimeOffsetValue, type G9DateTimeValue, type G9FrameCompressor } from './protocol/rowBatch/writer.js';\nexport {\n G9RowBatchReader,\n getDefaultFrameDecompressor,\n openRowBatch,\n openRowBatchSync,\n readFrameHeader,\n setDefaultFrameDecompressor,\n type G9FrameDecompressor,\n type G9FrameHeader,\n type G9OpenRowBatchOptions,\n} from './protocol/rowBatch/reader.js';\nexport { decimalToDouble, formatDecimal, formatDouble, parseDecimal, type DecimalParts } from './internal/dotnetFormat.js';\n\n// client\nexport { G9SyncClientModel, G9SyncClientModelBuilder, G9SyncClientTable, G9SyncClientTableBuilder, type G9SyncClientColumn, type G9SyncClientIndex } from './client/model.js';\nexport * from './client/storeTypes.js';\nexport type { G9SyncTransport } from './client/transport.js';\nexport { G9ESyncPhase, G9SyncProgressTracker, phaseName, type G9DtSyncProgress } from './client/progress.js';\nexport {\n G9ESyncOutcome,\n G9ESyncPriority,\n G9SyncPostStep,\n G9SyncRun,\n describeModelProblem,\n isTransientError,\n normalizeError,\n outcomeName,\n priorityName,\n resolveEngineOptions,\n type G9RowsAppliedEvent,\n type G9SubscriptionOptions,\n type G9SyncEngineOptions,\n type G9SyncHealth,\n type G9SyncHostHooks,\n type G9SyncLogger,\n type G9SyncLogLevel,\n type G9SyncModelProblem,\n type G9SyncPostStepContext,\n type G9SyncRequest,\n type G9SyncRunResult,\n} from './client/runs.js';\nexport { G9SyncEngine, stageGeneration } from './client/engine.js';\n\n// helpers the Lynx package and tests share\nexport { base64ToBytes, bytesToBase64 } from './internal/base64.js';\nexport { utf8Decode, utf8Encode } from './internal/utf8.js';\nexport { md5 } from './internal/md5.js';\nexport { sha256, toHex } from './internal/sha256.js';\nexport { cancellationError, createAbortController, delay, linkSignals, onAbort, yieldToEventLoop } from './internal/abort.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wHAAwH;AACxH,uHAAuH;AACvH,sHAAsH;AACtH,oHAAoH;AAEpH,sGAAsG;AACtG,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,eAAe;AACf,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACjJ,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAA+B,MAAM,0BAA0B,CAAC;AAC5I,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACvG,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAElF,WAAW;AACX,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC5G,OAAO,EACL,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,UAAU,EACV,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,kBAAkB,GAEnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACrI,OAAO,EAAE,gBAAgB,EAA4E,MAAM,+BAA+B,CAAC;AAC3I,OAAO,EACL,gBAAgB,EAChB,2BAA2B,EAC3B,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,2BAA2B,GAI5B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAqB,MAAM,4BAA4B,CAAC;AAE3H,SAAS;AACT,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,wBAAwB,EAAmD,MAAM,mBAAmB,CAAC;AAC9K,cAAc,wBAAwB,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,SAAS,EAAyB,MAAM,sBAAsB,CAAC;AAC7G,OAAO,EACL,cAAc,EACd,eAAe,EACf,cAAc,EACd,SAAS,EACT,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,YAAY,EACZ,oBAAoB,GAYrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEnE,2CAA2C;AAC3C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["// @g9tm/syncdata — the TypeScript twin of the G9SyncData .NET client (abstractions, protocol, engine). The SQLite store\n// is `@g9tm/syncdata/sqlite`, the SignalR transport `@g9tm/syncdata/signalr`, Node helpers `@g9tm/syncdata/node`, test\n// doubles `@g9tm/syncdata/testing`, the pure-JavaScript Brotli decoder `@g9tm/syncdata/brotli-js`; Lynx wiring is the\n// separate package `@g9tm/syncdata-lynx`. Every public type keeps its .NET name without the G9C prefix (PARITY.md).\n\n/** The package version; equal to the .NET packages' (G9SyncDataVersion) — one release, both sides. */\nexport const VERSION = '1.5.0';\n\n// abstractions\nexport { G9EConflictPolicy, G9EMoveOutPolicy, G9ESyncColumnType, G9ESyncDirection, columnTypeName, isColumnType } from './abstractions/enums.js';\nexport { G9SyncErrorCodes, G9SyncException, isRetryableCode, isSyncException, type G9SyncExceptionOptions } from './abstractions/errors.js';\nexport { G9Long, parseJson, stringifyJson } from './abstractions/long.js';\nexport { EMPTY_GUID, guidToN, newGuid, normalizeGuid, tryNormalizeGuid } from './abstractions/guid.js';\nexport { G9SyncKey } from './abstractions/key.js';\nexport { G9SyncDigest } from './abstractions/digest.js';\nexport { G9SyncParameters, escapeDataString } from './abstractions/parameters.js';\n\n// protocol\nexport * from './protocol/dtos.js';\nexport { G9SyncHubErrors, G9SyncProtocol, negotiateFrameVersion, tableGroup } from './protocol/protocol.js';\nexport {\n bytesField,\n dateTimeMs,\n fromMessagePackNames,\n G9WireGuid,\n GUID_EXTENSION_TYPE,\n readChangesAvailable,\n readContinuation,\n readHelloResult,\n readPullPlan,\n readPushResult,\n readReconcileSummary,\n readSyncChunk,\n toMessagePackNames,\n type G9WireFormat,\n} from './protocol/wire.js';\nexport { G9ECompression, G9EPushRowOp, G9ERowBatchOp, G9RowBatchFormat, G9RowBatchFormatError } from './protocol/rowBatch/format.js';\nexport { G9RowBatchWriter, type G9DateTimeOffsetValue, type G9DateTimeValue, type G9FrameCompressor } from './protocol/rowBatch/writer.js';\nexport {\n G9RowBatchReader,\n getDefaultFrameDecompressor,\n openRowBatch,\n openRowBatchSync,\n readFrameHeader,\n setDefaultFrameDecompressor,\n type G9FrameDecompressor,\n type G9FrameHeader,\n type G9OpenRowBatchOptions,\n} from './protocol/rowBatch/reader.js';\nexport { decimalToDouble, formatDecimal, formatDouble, parseDecimal, type DecimalParts } from './internal/dotnetFormat.js';\n\n// client\nexport { G9SyncClientModel, G9SyncClientModelBuilder, G9SyncClientTable, G9SyncClientTableBuilder, type G9SyncClientColumn, type G9SyncClientIndex } from './client/model.js';\nexport * from './client/storeTypes.js';\nexport type { G9SyncTransport } from './client/transport.js';\nexport { G9ESyncPhase, G9SyncProgressTracker, phaseName, type G9DtSyncProgress } from './client/progress.js';\nexport {\n G9ESyncOutcome,\n G9ESyncPriority,\n G9SyncPostStep,\n G9SyncRun,\n describeModelProblem,\n isTransientError,\n normalizeError,\n outcomeName,\n priorityName,\n resolveEngineOptions,\n type G9RowsAppliedEvent,\n type G9SubscriptionOptions,\n type G9SyncEngineOptions,\n type G9SyncHealth,\n type G9SyncHostHooks,\n type G9SyncLogger,\n type G9SyncLogLevel,\n type G9SyncModelProblem,\n type G9SyncPostStepContext,\n type G9SyncRequest,\n type G9SyncRunResult,\n} from './client/runs.js';\nexport { G9SyncEngine, stageGeneration } from './client/engine.js';\n\n// helpers the Lynx package and tests share\nexport { base64ToBytes, bytesToBase64 } from './internal/base64.js';\nexport { utf8Decode, utf8Encode } from './internal/utf8.js';\nexport { md5 } from './internal/md5.js';\nexport { sha256, toHex } from './internal/sha256.js';\nexport { cancellationError, createAbortController, delay, linkSignals, onAbort, yieldToEventLoop } from './internal/abort.js';\n"]}
@@ -25,6 +25,19 @@ export interface G9SignalRSyncTransportOptions {
25
25
  connectTimeoutMs?: number;
26
26
  serverTimeoutMs?: number;
27
27
  keepAliveIntervalMs?: number;
28
+ /**
29
+ * How long a single-answer call (Hello, PlanPull) waits for its answer before it is a retryable G9S-NET-FAILURE (ADR-0074).
30
+ * Default 60000; 0 = no limit. A live connection does not guarantee an answer: pings keep it up while the call's message
31
+ * was lost on the way.
32
+ */
33
+ callTimeoutMs?: number;
34
+ /** How long a push may take from its first chunk to its result; a safety net, not a pace. Default 600000; 0 = no limit. */
35
+ pushTimeoutMs?: number;
36
+ /**
37
+ * How long a server stream (Pull, Reconcile, FetchRows) may go without a chunk before it is a retryable G9S-NET-FAILURE; the
38
+ * engine resumes from its last continuation. Default 120000 (a snapshot file may still be building); 0 = no limit (ADR-0074).
39
+ */
40
+ streamIdleTimeoutMs?: number;
28
41
  /**
29
42
  * Everything else the G9 client takes (transport, headers, WebSocket constructor, skipNegotiation, reconnect policy, log
30
43
  * level…) — the twin of ConfigureHttpConnection/ConfigureBuilder. `url`, `protocol` and the timeouts above win.
@@ -55,7 +68,10 @@ export declare class G9SignalRSyncTransport implements G9SyncTransport {
55
68
  dispose(): Promise<void>;
56
69
  private _wire;
57
70
  private _read;
71
+ private _callTimeoutMs;
58
72
  private _invoke;
73
+ /** Ends the connection after a call or stream ran out of time (ADR-0074); the next call connects afresh. Never throws. */
74
+ private _dropConnection;
59
75
  private _stream;
60
76
  private _onChanges;
61
77
  }
@@ -98,11 +98,12 @@ export class G9SignalRSyncTransport {
98
98
  }
99
99
  }
100
100
  hello(request, signal) {
101
- return this._invoke(G9SyncProtocol.Methods.Hello, [this._wire(request)], readHelloResult, signal);
101
+ return this._invoke(G9SyncProtocol.Methods.Hello, [this._wire(request)], readHelloResult, this._callTimeoutMs(), signal);
102
102
  }
103
103
  push(request, chunks, signal) {
104
+ var _a;
104
105
  const subject = new Subject();
105
- const result = this._invoke(G9SyncProtocol.Methods.Push, [this._wire(request), subject], readPushResult, signal);
106
+ const result = this._invoke(G9SyncProtocol.Methods.Push, [this._wire(request), subject], readPushResult, (_a = this._options.pushTimeoutMs) !== null && _a !== void 0 ? _a : 600000, signal);
106
107
  void (async () => {
107
108
  try {
108
109
  for await (const chunk of chunks)
@@ -116,7 +117,7 @@ export class G9SignalRSyncTransport {
116
117
  return result;
117
118
  }
118
119
  planPull(request, signal) {
119
- return this._invoke(G9SyncProtocol.Methods.PlanPull, [this._wire(request)], readPullPlan, signal);
120
+ return this._invoke(G9SyncProtocol.Methods.PlanPull, [this._wire(request)], readPullPlan, this._callTimeoutMs(), signal);
120
121
  }
121
122
  pull(token, continuation, signal) {
122
123
  return this._stream(G9SyncProtocol.Methods.Pull, [token, continuation === null ? null : this._wire(continuation)], signal);
@@ -138,33 +139,64 @@ export class G9SignalRSyncTransport {
138
139
  _read(value) {
139
140
  return this.format === 'messagepack' ? fromMessagePackNames(value) : value;
140
141
  }
141
- async _invoke(method, args, read, signal) {
142
+ _callTimeoutMs() {
143
+ var _a;
144
+ return (_a = this._options.callTimeoutMs) !== null && _a !== void 0 ? _a : 60000;
145
+ }
146
+ async _invoke(method, args, read, timeoutMs, signal) {
142
147
  var _a;
143
148
  if (signal && signal.aborted)
144
149
  throw new G9SyncException(G9SyncErrorCodes.Canceled, 'The call was canceled.');
145
150
  let unsubscribe = () => undefined;
151
+ let timer = null;
152
+ let timedOut = false;
146
153
  try {
147
154
  const call = this.client.connection.invoke(method, ...args);
148
- const raw = signal
155
+ const raw = signal || timeoutMs > 0
149
156
  ? await new Promise((resolve, reject) => {
150
- unsubscribe = onAbort(signal, () => reject(new G9SyncException(G9SyncErrorCodes.Canceled, 'The call was canceled.')));
157
+ if (signal)
158
+ unsubscribe = onAbort(signal, () => reject(new G9SyncException(G9SyncErrorCodes.Canceled, 'The call was canceled.')));
159
+ if (timeoutMs > 0) {
160
+ timer = setTimeout(() => {
161
+ timedOut = true;
162
+ reject(new G9SyncException(G9SyncErrorCodes.NetworkFailure, `The sync server did not answer ${method} within ${Math.round(timeoutMs / 1000)} s.`, {
163
+ retryable: true,
164
+ }));
165
+ }, timeoutMs);
166
+ }
151
167
  call.then(resolve, reject);
152
168
  })
153
169
  : await call;
154
170
  return read(this._read(raw));
155
171
  }
156
172
  catch (error) {
173
+ // ADR-0074: the answer did not come in time. The connection is not trusted any more (the server runs one invocation per
174
+ // connection at a time, and whatever lost this answer may lose the next): drop it, so the retry runs on a fresh one.
175
+ if (timedOut)
176
+ await this._dropConnection();
157
177
  throw (_a = mapError(error, signal ? signal.aborted : false)) !== null && _a !== void 0 ? _a : error;
158
178
  }
159
179
  finally {
180
+ if (timer !== null)
181
+ clearTimeout(timer);
160
182
  unsubscribe();
161
183
  }
162
184
  }
185
+ /** Ends the connection after a call or stream ran out of time (ADR-0074); the next call connects afresh. Never throws. */
186
+ async _dropConnection() {
187
+ try {
188
+ await this.client.connection.stop();
189
+ }
190
+ catch {
191
+ // Stopping a connection that is already gone may throw; the next call connects afresh either way.
192
+ }
193
+ }
163
194
  _stream(method, args, signal) {
164
195
  // eslint-disable-next-line @typescript-eslint/no-this-alias
165
196
  const self = this;
166
197
  return {
167
198
  [Symbol.asyncIterator]() {
199
+ var _a;
168
200
  const queue = [];
169
201
  let done = false;
170
202
  let failure = null;
@@ -177,6 +209,32 @@ export class G9SignalRSyncTransport {
177
209
  w();
178
210
  };
179
211
  let unsubscribeAbort = () => undefined;
212
+ // ADR-0074: the idle limit runs only while the iterator waits for the next chunk, never while the caller processes one.
213
+ const idleMs = (_a = self._options.streamIdleTimeoutMs) !== null && _a !== void 0 ? _a : 120000;
214
+ let idleTimer = null;
215
+ let idleExpired = false;
216
+ const stopIdle = () => {
217
+ if (idleTimer !== null)
218
+ clearTimeout(idleTimer);
219
+ idleTimer = null;
220
+ };
221
+ const armIdle = () => {
222
+ stopIdle();
223
+ if (idleMs <= 0 || done)
224
+ return;
225
+ idleTimer = setTimeout(() => {
226
+ idleTimer = null;
227
+ if (done)
228
+ return;
229
+ idleExpired = true;
230
+ failure = new G9SyncException(G9SyncErrorCodes.NetworkFailure, `The sync server sent nothing on ${method} for ${Math.round(idleMs / 1000)} s.`, {
231
+ retryable: true,
232
+ });
233
+ done = true;
234
+ subscription === null || subscription === void 0 ? void 0 : subscription.dispose();
235
+ notify();
236
+ }, idleMs);
237
+ };
180
238
  const start = () => {
181
239
  if (subscription || done)
182
240
  return;
@@ -212,6 +270,7 @@ export class G9SignalRSyncTransport {
212
270
  });
213
271
  };
214
272
  const finish = () => {
273
+ stopIdle();
215
274
  unsubscribeAbort();
216
275
  if (subscription && !done)
217
276
  subscription.dispose();
@@ -223,14 +282,21 @@ export class G9SignalRSyncTransport {
223
282
  start();
224
283
  for (;;) {
225
284
  const item = queue.shift();
226
- if (item)
285
+ if (item) {
286
+ stopIdle();
227
287
  return { value: item, done: false };
288
+ }
228
289
  if (done) {
290
+ stopIdle();
229
291
  unsubscribeAbort();
292
+ if (idleExpired)
293
+ await self._dropConnection();
230
294
  if (failure !== null)
231
295
  throw (_a = mapError(failure, signal ? signal.aborted : false)) !== null && _a !== void 0 ? _a : failure;
232
296
  return { value: undefined, done: true };
233
297
  }
298
+ if (idleTimer === null)
299
+ armIdle();
234
300
  await new Promise((resolve) => {
235
301
  wake = resolve;
236
302
  });