@tanstack/db-sqlite-persistence-core 0.4.4 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/dist/cjs/index.cjs +5 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/persisted.cjs +12 -10
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +7 -1
- package/dist/cjs/remote-subset-wire.cjs +100 -73
- package/dist/cjs/remote-subset-wire.cjs.map +1 -1
- package/dist/cjs/remote-subset-wire.d.cts +2 -0
- package/dist/cjs/sqlite-core-adapter.cjs +136 -28
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-value.cjs +51 -0
- package/dist/cjs/sqlite-value.cjs.map +1 -1
- package/dist/cjs/sqlite-value.d.cts +12 -0
- package/dist/esm/index.js +8 -3
- package/dist/esm/persisted.d.ts +7 -1
- package/dist/esm/persisted.js +14 -12
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/remote-subset-wire.d.ts +2 -0
- package/dist/esm/remote-subset-wire.js +101 -74
- package/dist/esm/remote-subset-wire.js.map +1 -1
- package/dist/esm/sqlite-core-adapter.js +138 -30
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/dist/esm/sqlite-value.d.ts +12 -0
- package/dist/esm/sqlite-value.js +53 -2
- package/dist/esm/sqlite-value.js.map +1 -1
- package/package.json +4 -3
- package/src/persisted.ts +31 -16
- package/src/remote-subset-wire.ts +149 -79
- package/src/sqlite-core-adapter.ts +221 -34
- package/src/sqlite-value.ts +98 -1
|
@@ -8,3 +8,15 @@ export type SerializedSQLiteBigInt = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare function assertSQLiteBigIntInRange(value: bigint): bigint;
|
|
10
10
|
export declare function serializeSQLiteBigInt(value: bigint): SerializedSQLiteBigInt;
|
|
11
|
+
type SQLiteTemporalKind = `Temporal.Instant` | `Temporal.PlainDate`;
|
|
12
|
+
export type SerializedSQLiteTemporal = {
|
|
13
|
+
[PERSISTED_TYPE_TAG]: SQLiteTemporalKind;
|
|
14
|
+
[PERSISTED_VALUE_TAG]: string;
|
|
15
|
+
order: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function sqliteTemporalKind(value: unknown): SQLiteTemporalKind | undefined;
|
|
18
|
+
export declare function reviveSQLiteTemporal(kind: SQLiteTemporalKind, value: string): unknown;
|
|
19
|
+
/** Registered prototype methods validate the brand; user toString is ignored. */
|
|
20
|
+
export declare function serializeSQLiteTemporal(value: unknown): SerializedSQLiteTemporal | undefined;
|
|
21
|
+
export declare function sqliteTemporalIdentity(value: SerializedSQLiteTemporal): string;
|
|
22
|
+
export {};
|
package/dist/esm/sqlite-value.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SQLiteBigIntOutOfRangeError } from "./errors.js";
|
|
1
|
+
import { SQLiteBigIntOutOfRangeError, InvalidPersistedCollectionConfigError } from "./errors.js";
|
|
2
2
|
const SQLITE_BIGINT_MIN = -9223372036854775808n;
|
|
3
3
|
const SQLITE_BIGINT_MAX = 9223372036854775807n;
|
|
4
4
|
const PERSISTED_TYPE_TAG = `__tanstack_db_persisted_type__`;
|
|
@@ -20,12 +20,63 @@ function serializeSQLiteBigInt(value) {
|
|
|
20
20
|
[PERSISTED_VALUE_TAG]: value.toString()
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
function sqliteTemporalKind(value) {
|
|
24
|
+
if (value === null || typeof value !== `object`) return;
|
|
25
|
+
const tag = value[Symbol.toStringTag];
|
|
26
|
+
if (tag === `Temporal.Instant` || tag === `Temporal.PlainDate`) return tag;
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
function requireSQLiteTemporalConstructor(kind) {
|
|
30
|
+
const name = kind.slice(`Temporal.`.length);
|
|
31
|
+
const constructor = globalThis.Temporal?.[name];
|
|
32
|
+
if (typeof constructor?.from !== `function` || typeof constructor.prototype.toString !== `function`) {
|
|
33
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
34
|
+
`Missing global ${kind} constructor for SQLite persistence`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return constructor;
|
|
38
|
+
}
|
|
39
|
+
function reviveSQLiteTemporal(kind, value) {
|
|
40
|
+
return requireSQLiteTemporalConstructor(kind).from(value);
|
|
41
|
+
}
|
|
42
|
+
function serializeSQLiteTemporal(value) {
|
|
43
|
+
if (value === null || typeof value !== `object`) return;
|
|
44
|
+
const prototype = Object.getPrototypeOf(value);
|
|
45
|
+
if (prototype === null || prototype === Object.prototype) return;
|
|
46
|
+
const tag = value[Symbol.toStringTag];
|
|
47
|
+
if (typeof tag !== `string` || !tag.startsWith(`Temporal.`)) return;
|
|
48
|
+
const kind = sqliteTemporalKind(value);
|
|
49
|
+
if (!kind)
|
|
50
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
51
|
+
`Unsupported ${tag} value for SQLite persistence`
|
|
52
|
+
);
|
|
53
|
+
const constructor = requireSQLiteTemporalConstructor(kind);
|
|
54
|
+
let text;
|
|
55
|
+
try {
|
|
56
|
+
text = constructor.prototype.toString.call(value);
|
|
57
|
+
} catch {
|
|
58
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
59
|
+
`Invalid ${kind} value for the registered global constructor`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const native = constructor.from(text);
|
|
63
|
+
const iso = kind === `Temporal.PlainDate` ? native.withCalendar(`iso8601`) : void 0;
|
|
64
|
+
const order = iso ? `${String(iso.year + 1e6).padStart(7, `0`)}${String(iso.month).padStart(2, `0`)}${String(iso.day).padStart(2, `0`)}` : (native.epochNanoseconds + 10000000000000000000000n).toString().padStart(23, `0`);
|
|
65
|
+
return { [PERSISTED_TYPE_TAG]: kind, [PERSISTED_VALUE_TAG]: text, order };
|
|
66
|
+
}
|
|
67
|
+
function sqliteTemporalIdentity(value) {
|
|
68
|
+
return `${value[PERSISTED_TYPE_TAG]}:${value[PERSISTED_VALUE_TAG]}`;
|
|
69
|
+
}
|
|
23
70
|
export {
|
|
24
71
|
PERSISTED_TYPE_TAG,
|
|
25
72
|
PERSISTED_VALUE_TAG,
|
|
26
73
|
SQLITE_BIGINT_MAX,
|
|
27
74
|
SQLITE_BIGINT_MIN,
|
|
28
75
|
assertSQLiteBigIntInRange,
|
|
29
|
-
|
|
76
|
+
reviveSQLiteTemporal,
|
|
77
|
+
serializeSQLiteBigInt,
|
|
78
|
+
serializeSQLiteTemporal,
|
|
79
|
+
sqliteTemporalIdentity,
|
|
80
|
+
sqliteTemporalKind
|
|
30
81
|
};
|
|
31
82
|
//# sourceMappingURL=sqlite-value.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-value.js","sources":["../../src/sqlite-value.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"sqlite-value.js","sources":["../../src/sqlite-value.ts"],"sourcesContent":["import {\n InvalidPersistedCollectionConfigError,\n SQLiteBigIntOutOfRangeError,\n} from './errors'\n\nexport const SQLITE_BIGINT_MIN = -9_223_372_036_854_775_808n\nexport const SQLITE_BIGINT_MAX = 9_223_372_036_854_775_807n\nexport const PERSISTED_TYPE_TAG = `__tanstack_db_persisted_type__`\nexport const PERSISTED_VALUE_TAG = `value`\n\nexport type SerializedSQLiteBigInt = {\n [PERSISTED_TYPE_TAG]: `bigint`\n [PERSISTED_VALUE_TAG]: string\n}\n\nexport function assertSQLiteBigIntInRange(value: bigint): bigint {\n if (value < SQLITE_BIGINT_MIN || value > SQLITE_BIGINT_MAX) {\n throw new SQLiteBigIntOutOfRangeError(\n value,\n SQLITE_BIGINT_MIN,\n SQLITE_BIGINT_MAX,\n )\n }\n return value\n}\n\nexport function serializeSQLiteBigInt(value: bigint): SerializedSQLiteBigInt {\n assertSQLiteBigIntInRange(value)\n return {\n [PERSISTED_TYPE_TAG]: `bigint`,\n [PERSISTED_VALUE_TAG]: value.toString(),\n }\n}\n\ntype SQLiteTemporalKind = `Temporal.Instant` | `Temporal.PlainDate`\ntype SQLiteTemporal = {\n epochNanoseconds: bigint\n year: number\n month: number\n day: number\n withCalendar: (calendar: string) => SQLiteTemporal\n}\ntype TemporalConstructor = {\n from: (value: string) => SQLiteTemporal\n prototype: { toString: () => string }\n}\n\nexport type SerializedSQLiteTemporal = {\n [PERSISTED_TYPE_TAG]: SQLiteTemporalKind\n [PERSISTED_VALUE_TAG]: string\n order: string\n}\n\nexport function sqliteTemporalKind(\n value: unknown,\n): SQLiteTemporalKind | undefined {\n if (value === null || typeof value !== `object`) return\n const tag = (value as { [Symbol.toStringTag]?: unknown })[Symbol.toStringTag]\n if (tag === `Temporal.Instant` || tag === `Temporal.PlainDate`) return tag\n return undefined\n}\n\nfunction requireSQLiteTemporalConstructor(\n kind: SQLiteTemporalKind,\n): TemporalConstructor {\n const name = kind.slice(`Temporal.`.length)\n const constructor = (\n globalThis as { Temporal?: Record<string, TemporalConstructor> }\n ).Temporal?.[name]\n if (\n typeof constructor?.from !== `function` ||\n typeof constructor.prototype.toString !== `function`\n ) {\n throw new InvalidPersistedCollectionConfigError(\n `Missing global ${kind} constructor for SQLite persistence`,\n )\n }\n return constructor\n}\n\nexport function reviveSQLiteTemporal(\n kind: SQLiteTemporalKind,\n value: string,\n): unknown {\n return requireSQLiteTemporalConstructor(kind).from(value)\n}\n\n/** Registered prototype methods validate the brand; user toString is ignored. */\nexport function serializeSQLiteTemporal(\n value: unknown,\n): SerializedSQLiteTemporal | undefined {\n if (value === null || typeof value !== `object`) return\n const prototype = Object.getPrototypeOf(value)\n if (prototype === null || prototype === Object.prototype) return\n const tag = (value as { [Symbol.toStringTag]?: unknown })[Symbol.toStringTag]\n if (typeof tag !== `string` || !tag.startsWith(`Temporal.`)) return\n const kind = sqliteTemporalKind(value)\n if (!kind)\n throw new InvalidPersistedCollectionConfigError(\n `Unsupported ${tag} value for SQLite persistence`,\n )\n const constructor = requireSQLiteTemporalConstructor(kind)\n let text: string\n try {\n text = constructor.prototype.toString.call(value)\n } catch {\n throw new InvalidPersistedCollectionConfigError(\n `Invalid ${kind} value for the registered global constructor`,\n )\n }\n const native = constructor.from(text)\n const iso =\n kind === `Temporal.PlainDate` ? native.withCalendar(`iso8601`) : undefined\n // Text keys avoid both floating-point precision loss and SQLite's int64 cap.\n const order = iso\n ? `${String(iso.year + 1_000_000).padStart(7, `0`)}${String(iso.month).padStart(2, `0`)}${String(iso.day).padStart(2, `0`)}`\n : (native.epochNanoseconds + 10_000_000_000_000_000_000_000n)\n .toString()\n .padStart(23, `0`)\n return { [PERSISTED_TYPE_TAG]: kind, [PERSISTED_VALUE_TAG]: text, order }\n}\n\nexport function sqliteTemporalIdentity(\n value: SerializedSQLiteTemporal,\n): string {\n return `${value[PERSISTED_TYPE_TAG]}:${value[PERSISTED_VALUE_TAG]}`\n}\n"],"names":[],"mappings":";AAKO,MAAM,oBAAoB,CAAC;AAC3B,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAO5B,SAAS,0BAA0B,OAAuB;AAC/D,MAAI,QAAQ,qBAAqB,QAAQ,mBAAmB;AAC1D,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AACA,SAAO;AACT;AAEO,SAAS,sBAAsB,OAAuC;AAC3E,4BAA0B,KAAK;AAC/B,SAAO;AAAA,IACL,CAAC,kBAAkB,GAAG;AAAA,IACtB,CAAC,mBAAmB,GAAG,MAAM,SAAA;AAAA,EAAS;AAE1C;AAqBO,SAAS,mBACd,OACgC;AAChC,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU;AACjD,QAAM,MAAO,MAA6C,OAAO,WAAW;AAC5E,MAAI,QAAQ,sBAAsB,QAAQ,qBAAsB,QAAO;AACvE,SAAO;AACT;AAEA,SAAS,iCACP,MACqB;AACrB,QAAM,OAAO,KAAK,MAAM,YAAY,MAAM;AAC1C,QAAM,cACJ,WACA,WAAW,IAAI;AACjB,MACE,OAAO,aAAa,SAAS,cAC7B,OAAO,YAAY,UAAU,aAAa,YAC1C;AACA,UAAM,IAAI;AAAA,MACR,kBAAkB,IAAI;AAAA,IAAA;AAAA,EAE1B;AACA,SAAO;AACT;AAEO,SAAS,qBACd,MACA,OACS;AACT,SAAO,iCAAiC,IAAI,EAAE,KAAK,KAAK;AAC1D;AAGO,SAAS,wBACd,OACsC;AACtC,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU;AACjD,QAAM,YAAY,OAAO,eAAe,KAAK;AAC7C,MAAI,cAAc,QAAQ,cAAc,OAAO,UAAW;AAC1D,QAAM,MAAO,MAA6C,OAAO,WAAW;AAC5E,MAAI,OAAO,QAAQ,YAAY,CAAC,IAAI,WAAW,WAAW,EAAG;AAC7D,QAAM,OAAO,mBAAmB,KAAK;AACrC,MAAI,CAAC;AACH,UAAM,IAAI;AAAA,MACR,eAAe,GAAG;AAAA,IAAA;AAEtB,QAAM,cAAc,iCAAiC,IAAI;AACzD,MAAI;AACJ,MAAI;AACF,WAAO,YAAY,UAAU,SAAS,KAAK,KAAK;AAAA,EAClD,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,WAAW,IAAI;AAAA,IAAA;AAAA,EAEnB;AACA,QAAM,SAAS,YAAY,KAAK,IAAI;AACpC,QAAM,MACJ,SAAS,uBAAuB,OAAO,aAAa,SAAS,IAAI;AAEnE,QAAM,QAAQ,MACV,GAAG,OAAO,IAAI,OAAO,GAAS,EAAE,SAAS,GAAG,GAAG,CAAC,GAAG,OAAO,IAAI,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,GAAG,OAAO,IAAI,GAAG,EAAE,SAAS,GAAG,GAAG,CAAC,MACvH,OAAO,mBAAmB,0BACxB,WACA,SAAS,IAAI,GAAG;AACvB,SAAO,EAAE,CAAC,kBAAkB,GAAG,MAAM,CAAC,mBAAmB,GAAG,MAAM,MAAA;AACpE;AAEO,SAAS,uBACd,OACQ;AACR,SAAO,GAAG,MAAM,kBAAkB,CAAC,IAAI,MAAM,mBAAmB,CAAC;AACnE;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/db-sqlite-persistence-core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "SQLite persisted collection core for TanStack DB",
|
|
5
5
|
"author": "TanStack Team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,13 +50,14 @@
|
|
|
50
50
|
],
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@standard-schema/spec": "^1.1.0",
|
|
53
|
-
"@tanstack/db": "0.12.
|
|
53
|
+
"@tanstack/db": "0.12.3"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"typescript": ">=4.7"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@vitest/coverage-istanbul": "^3.2.4"
|
|
59
|
+
"@vitest/coverage-istanbul": "^3.2.4",
|
|
60
|
+
"temporal-polyfill": "^0.3.0"
|
|
60
61
|
},
|
|
61
62
|
"scripts": {
|
|
62
63
|
"build": "vite build",
|
package/src/persisted.ts
CHANGED
|
@@ -24,10 +24,11 @@ import {
|
|
|
24
24
|
PersistedCollectionDurabilityError,
|
|
25
25
|
toPersistedCollectionDurabilityError,
|
|
26
26
|
} from './errors'
|
|
27
|
-
import { serializeSQLiteBigInt } from './sqlite-value'
|
|
27
|
+
import { serializeSQLiteBigInt, serializeSQLiteTemporal } from './sqlite-value'
|
|
28
28
|
import {
|
|
29
29
|
toProcessLocalLoadSubsetOptions,
|
|
30
30
|
toTransportedLoadSubsetOptions,
|
|
31
|
+
validateRemoteSubsetOptions,
|
|
31
32
|
} from './remote-subset-wire'
|
|
32
33
|
import {
|
|
33
34
|
reportRemoteSubsetOwnerError,
|
|
@@ -543,6 +544,12 @@ export interface PersistedCollectionCoordinator {
|
|
|
543
544
|
collectionId: string,
|
|
544
545
|
mutations: Array<PersistedMutationEnvelope>,
|
|
545
546
|
) => Promise<ApplyLocalMutationsResponse>
|
|
547
|
+
/**
|
|
548
|
+
* Applies a committed transaction through the supplied adapter when present.
|
|
549
|
+
* Wrappers must forward this leader-local loan: scheduling it again can wait
|
|
550
|
+
* on the hydration that is awaiting this call. Never serialize or retain the
|
|
551
|
+
* loan after the call; ordinary work uses the registered collection adapter.
|
|
552
|
+
*/
|
|
546
553
|
requestApplyCommittedTx: (
|
|
547
554
|
collectionId: string,
|
|
548
555
|
tx: PersistedTx,
|
|
@@ -887,8 +894,9 @@ export class SingleProcessCoordinator implements PersistedCollectionCoordinator
|
|
|
887
894
|
public async requestApplyCommittedTx(
|
|
888
895
|
collectionId: string,
|
|
889
896
|
tx: PersistedTx,
|
|
897
|
+
scopedAdapter?: HydrationPersistenceAdapter,
|
|
890
898
|
): Promise<ApplyCommittedTxResponse> {
|
|
891
|
-
const adapter = this.collectionAdapters.get(collectionId)
|
|
899
|
+
const adapter = scopedAdapter ?? this.collectionAdapters.get(collectionId)
|
|
892
900
|
if (!adapter) {
|
|
893
901
|
throw new InvalidPersistedCollectionConfigError(
|
|
894
902
|
`SingleProcessCoordinator has no persistence adapter configured for collection "${collectionId}"`,
|
|
@@ -1152,6 +1160,9 @@ function toStableSerializable(value: unknown): unknown {
|
|
|
1152
1160
|
return undefined
|
|
1153
1161
|
}
|
|
1154
1162
|
|
|
1163
|
+
const temporal = serializeSQLiteTemporal(value)
|
|
1164
|
+
if (temporal) return temporal
|
|
1165
|
+
|
|
1155
1166
|
if (value instanceof Date) {
|
|
1156
1167
|
return value.toISOString()
|
|
1157
1168
|
}
|
|
@@ -1730,7 +1741,6 @@ class PersistedCollectionRuntime<
|
|
|
1730
1741
|
await this.hydrateSubsetUnsafe(
|
|
1731
1742
|
baseline,
|
|
1732
1743
|
{
|
|
1733
|
-
requestRemoteEnsure: false,
|
|
1734
1744
|
lifecycleGeneration,
|
|
1735
1745
|
bindKeySetEvidence: true,
|
|
1736
1746
|
},
|
|
@@ -1866,6 +1876,8 @@ class PersistedCollectionRuntime<
|
|
|
1866
1876
|
const truncateGeneration = this.sourceTruncateGeneration
|
|
1867
1877
|
const routeRemoteDemandDuringHydration =
|
|
1868
1878
|
this.canRouteRemoteDemandThroughCoordinator()
|
|
1879
|
+
// Wire admission failures are permanent input errors, not transport retries.
|
|
1880
|
+
if (routeRemoteDemandDuringHydration) validateRemoteSubsetOptions(options)
|
|
1869
1881
|
this.activeSubsets.set(subsetKey, options)
|
|
1870
1882
|
const appliedCursor = this.appliedReceiptSequence
|
|
1871
1883
|
try {
|
|
@@ -1875,9 +1887,6 @@ class PersistedCollectionRuntime<
|
|
|
1875
1887
|
this.hydrateSubsetUnsafe(
|
|
1876
1888
|
options,
|
|
1877
1889
|
{
|
|
1878
|
-
requestRemoteEnsure:
|
|
1879
|
-
this.mode === `sync-present` &&
|
|
1880
|
-
!routeRemoteDemandDuringHydration,
|
|
1881
1890
|
lifecycleGeneration,
|
|
1882
1891
|
requestLocalLoadFailure: true,
|
|
1883
1892
|
rejectBufferedReplayFailure: true,
|
|
@@ -1896,6 +1905,14 @@ class PersistedCollectionRuntime<
|
|
|
1896
1905
|
})
|
|
1897
1906
|
if (lifecycleGeneration !== this.lifecycleGeneration) return
|
|
1898
1907
|
await this.waitForAppliedReceiptsAfter(appliedCursor)
|
|
1908
|
+
// Leadership can change while hydration waits. Validate newly remote
|
|
1909
|
+
// demand inside the admission cleanup boundary, before retry can own it.
|
|
1910
|
+
if (
|
|
1911
|
+
!routeRemoteDemandDuringHydration &&
|
|
1912
|
+
this.canRouteRemoteDemandThroughCoordinator()
|
|
1913
|
+
) {
|
|
1914
|
+
validateRemoteSubsetOptions(options)
|
|
1915
|
+
}
|
|
1899
1916
|
} catch (error) {
|
|
1900
1917
|
if (this.activeSubsets.get(subsetKey) === options) {
|
|
1901
1918
|
this.activeSubsets.delete(subsetKey)
|
|
@@ -2056,7 +2073,6 @@ class PersistedCollectionRuntime<
|
|
|
2056
2073
|
this.hydrateSubsetUnsafe(
|
|
2057
2074
|
options,
|
|
2058
2075
|
{
|
|
2059
|
-
requestRemoteEnsure: false,
|
|
2060
2076
|
lifecycleGeneration,
|
|
2061
2077
|
rejectBufferedReplayFailure: true,
|
|
2062
2078
|
},
|
|
@@ -2329,7 +2345,6 @@ class PersistedCollectionRuntime<
|
|
|
2329
2345
|
private async hydrateSubsetUnsafe(
|
|
2330
2346
|
options: LoadSubsetOptions,
|
|
2331
2347
|
config: {
|
|
2332
|
-
requestRemoteEnsure: boolean
|
|
2333
2348
|
lifecycleGeneration: number
|
|
2334
2349
|
bindKeySetEvidence?: boolean
|
|
2335
2350
|
requestLocalLoadFailure?: boolean
|
|
@@ -2390,10 +2405,6 @@ class PersistedCollectionRuntime<
|
|
|
2390
2405
|
if (config.lifecycleGeneration !== this.lifecycleGeneration) return
|
|
2391
2406
|
replayFailure = await this.flushQueuedHydrationTransactionsUnsafe(adapter)
|
|
2392
2407
|
if (config.lifecycleGeneration !== this.lifecycleGeneration) return
|
|
2393
|
-
|
|
2394
|
-
if (config.requestRemoteEnsure && !replayFailure) {
|
|
2395
|
-
this.queueRemoteSubsetEnsure(options)
|
|
2396
|
-
}
|
|
2397
2408
|
} catch (error) {
|
|
2398
2409
|
if (config.requestLocalLoadFailure && !rowsLoaded) {
|
|
2399
2410
|
// Keep admitting source work to the hydration queue while recovery
|
|
@@ -3262,6 +3273,9 @@ class PersistedCollectionRuntime<
|
|
|
3262
3273
|
const subsetKey = this.getSubsetKey(options)
|
|
3263
3274
|
if (this.activeSubsets.get(subsetKey) !== options) return
|
|
3264
3275
|
|
|
3276
|
+
// A previously local acquisition can become remote after an ownership
|
|
3277
|
+
// change. Recovery must reject permanent wire errors before retry owns it.
|
|
3278
|
+
validateRemoteSubsetOptions(options)
|
|
3265
3279
|
this.pendingRemoteSubsetEnsures.set(subsetKey, options)
|
|
3266
3280
|
void this.flushPendingRemoteSubsetEnsures()
|
|
3267
3281
|
}
|
|
@@ -3808,9 +3822,8 @@ class PersistedCollectionRuntime<
|
|
|
3808
3822
|
indexMetadata: CollectionIndexMetadata,
|
|
3809
3823
|
adapter: HydrationPersistenceAdapter,
|
|
3810
3824
|
): Promise<boolean> {
|
|
3811
|
-
const spec = this.buildPersistedIndexSpec(indexMetadata)
|
|
3812
|
-
|
|
3813
3825
|
try {
|
|
3826
|
+
const spec = this.buildPersistedIndexSpec(indexMetadata)
|
|
3814
3827
|
await adapter.ensureIndex(
|
|
3815
3828
|
this.collectionId,
|
|
3816
3829
|
indexMetadata.signature,
|
|
@@ -3827,9 +3840,8 @@ class PersistedCollectionRuntime<
|
|
|
3827
3840
|
indexMetadata: CollectionIndexMetadata,
|
|
3828
3841
|
completedLocally: boolean,
|
|
3829
3842
|
): Promise<void> {
|
|
3830
|
-
const spec = this.buildPersistedIndexSpec(indexMetadata)
|
|
3831
|
-
|
|
3832
3843
|
try {
|
|
3844
|
+
const spec = this.buildPersistedIndexSpec(indexMetadata)
|
|
3833
3845
|
await this.persistence.coordinator.requestEnsurePersistedIndex(
|
|
3834
3846
|
this.collectionId,
|
|
3835
3847
|
indexMetadata.signature,
|
|
@@ -4590,6 +4602,9 @@ function createWrappedSyncConfig<
|
|
|
4590
4602
|
return visibleAfterDurable(openTransaction, applied)
|
|
4591
4603
|
}
|
|
4592
4604
|
|
|
4605
|
+
// Admission is complete. Only queued hydration replay remains tied
|
|
4606
|
+
// to its sequence; retain hydrationContext for row metadata ownership.
|
|
4607
|
+
openTransaction.hydrationSequence = undefined
|
|
4593
4608
|
let applied: Promise<void>
|
|
4594
4609
|
try {
|
|
4595
4610
|
applied = runtime.applyHydrationBufferedTransaction(transaction)
|