@korajs/store 1.0.0-beta.13 → 1.0.0-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/better-sqlite3.cjs.map +1 -1
- package/dist/adapters/better-sqlite3.js +1 -1
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.js +4 -4
- package/dist/adapters/sqlite-wasm-worker.cjs +67 -15
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +65 -17
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.js +5 -5
- package/dist/{backup-4NUPDEMB.js → backup-IHKVMUAM.js} +5 -5
- package/dist/{chunk-Y5LRH7KI.js → chunk-5AQSGMJV.js} +151 -21
- package/dist/chunk-5AQSGMJV.js.map +1 -0
- package/dist/{chunk-W5F2LWG6.js → chunk-5JMGMPUE.js} +2 -2
- package/dist/{chunk-HZTDBLDB.js → chunk-BILSA6XZ.js} +4 -3
- package/dist/{chunk-HZTDBLDB.js.map → chunk-BILSA6XZ.js.map} +1 -1
- package/dist/{chunk-QDUSJMFV.js → chunk-BQSIL6TT.js} +5 -5
- package/dist/{chunk-MCQKEHOD.js → chunk-KK3GBONL.js} +2 -2
- package/dist/{chunk-32EDKWMI.js → chunk-KTVNANUH.js} +2 -2
- package/dist/{chunk-SJP2HTYZ.js → chunk-QW4UU7NG.js} +2 -2
- package/dist/{chunk-DUKTWVZW.js → chunk-SRHDJF5B.js} +13 -2
- package/dist/{chunk-DUKTWVZW.js.map → chunk-SRHDJF5B.js.map} +1 -1
- package/dist/{chunk-P4V375FY.js → chunk-UC2LGFPV.js} +4 -4
- package/dist/index.cjs +203 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +96 -6
- package/dist/index.d.ts +96 -6
- package/dist/index.js +55 -39
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +1 -0
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +3 -3
- package/dist/{restore-FR7XIKT2.js → restore-MNDPR557.js} +4 -4
- package/dist/{sql-builder-C4u55jKW.d.cts → sql-builder-D0tC3sv9.d.cts} +8 -1
- package/dist/{sql-builder-CGckdBDL.d.ts → sql-builder-D5UMzHEo.d.ts} +8 -1
- package/dist/sqlite-wasm-channel-QCCNBYCU.js +12 -0
- package/dist/store-HNGX4KTT.js +16 -0
- package/package.json +2 -2
- package/dist/chunk-Y5LRH7KI.js.map +0 -1
- package/dist/sqlite-wasm-channel-MLHLKEED.js +0 -12
- package/dist/store-IMWKZLWB.js +0 -16
- /package/dist/{backup-4NUPDEMB.js.map → backup-IHKVMUAM.js.map} +0 -0
- /package/dist/{chunk-W5F2LWG6.js.map → chunk-5JMGMPUE.js.map} +0 -0
- /package/dist/{chunk-QDUSJMFV.js.map → chunk-BQSIL6TT.js.map} +0 -0
- /package/dist/{chunk-MCQKEHOD.js.map → chunk-KK3GBONL.js.map} +0 -0
- /package/dist/{chunk-32EDKWMI.js.map → chunk-KTVNANUH.js.map} +0 -0
- /package/dist/{chunk-SJP2HTYZ.js.map → chunk-QW4UU7NG.js.map} +0 -0
- /package/dist/{chunk-P4V375FY.js.map → chunk-UC2LGFPV.js.map} +0 -0
- /package/dist/{restore-FR7XIKT2.js.map → restore-MNDPR557.js.map} +0 -0
- /package/dist/{sqlite-wasm-channel-MLHLKEED.js.map → sqlite-wasm-channel-QCCNBYCU.js.map} +0 -0
- /package/dist/{store-IMWKZLWB.js.map → store-HNGX4KTT.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -31,7 +31,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
33
|
// src/errors.ts
|
|
34
|
-
var import_core, QueryError, RecordNotFoundError, OptimisticLockError, AdapterError, StorageDurabilityError, SchemaVersionAheadError, StoreNotOpenError, WorkerInitError, WorkerTimeoutError, NoLeaderError, LeaderUnresponsiveError, BridgeTerminatedError, RequestAbortedError, StorageInUseError, UnsyncedDataError, StorageBackendMismatchError, PersistenceError;
|
|
34
|
+
var import_core, QueryError, RecordNotFoundError, OptimisticLockError, AdapterError, StorageDurabilityError, SchemaVersionAheadError, StoreNotOpenError, WorkerInitError, WorkerTimeoutError, NoLeaderError, LeaderUnresponsiveError, BridgeTerminatedError, RequestAbortedError, StorageInUseError, UnsyncedDataError, StorageBackendMismatchError, PersistenceError, NodeOwnedByAnotherUserError;
|
|
35
35
|
var init_errors = __esm({
|
|
36
36
|
"src/errors.ts"() {
|
|
37
37
|
"use strict";
|
|
@@ -188,6 +188,16 @@ var init_errors = __esm({
|
|
|
188
188
|
this.name = "PersistenceError";
|
|
189
189
|
}
|
|
190
190
|
};
|
|
191
|
+
NodeOwnedByAnotherUserError = class extends import_core.KoraError {
|
|
192
|
+
constructor(nodeId, principal) {
|
|
193
|
+
super(
|
|
194
|
+
"This device belongs to another account, so changes made while this account is signed in here cannot be saved: they would be uploaded as the device owner's. Sign in as the device's owner, or use this account on its own device.",
|
|
195
|
+
"NODE_OWNED_BY_ANOTHER_USER",
|
|
196
|
+
{ nodeId, principal }
|
|
197
|
+
);
|
|
198
|
+
this.name = "NodeOwnedByAnotherUserError";
|
|
199
|
+
}
|
|
200
|
+
};
|
|
191
201
|
}
|
|
192
202
|
});
|
|
193
203
|
|
|
@@ -3628,6 +3638,7 @@ async function writeDeleteInTx(env, scope, collection, id, options = {}) {
|
|
|
3628
3638
|
};
|
|
3629
3639
|
}
|
|
3630
3640
|
async function buildLocalOperation(env, scope, input) {
|
|
3641
|
+
env.assertLocalWriteAllowed?.();
|
|
3631
3642
|
const sequenceNumber = await scope.sequence.take();
|
|
3632
3643
|
const causalDeps = scope.causal.depsFor(input.collection, input.extraCausalDeps);
|
|
3633
3644
|
const operation = await (0, import_core17.createOperation)(
|
|
@@ -3762,7 +3773,7 @@ var init_collection = __esm({
|
|
|
3762
3773
|
init_execute_update();
|
|
3763
3774
|
init_serializer();
|
|
3764
3775
|
Collection = class {
|
|
3765
|
-
constructor(name, definition, schema, adapter, clock, nodeId, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider, onStorageError, fold, maxOperationBytes) {
|
|
3776
|
+
constructor(name, definition, schema, adapter, clock, nodeId, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider, onStorageError, fold, maxOperationBytes, assertLocalWriteAllowed) {
|
|
3766
3777
|
this.name = name;
|
|
3767
3778
|
this.definition = definition;
|
|
3768
3779
|
this.schema = schema;
|
|
@@ -3777,6 +3788,7 @@ var init_collection = __esm({
|
|
|
3777
3788
|
this.onStorageError = onStorageError;
|
|
3778
3789
|
this.fold = fold;
|
|
3779
3790
|
this.maxOperationBytes = maxOperationBytes;
|
|
3791
|
+
this.assertLocalWriteAllowed = assertLocalWriteAllowed;
|
|
3780
3792
|
}
|
|
3781
3793
|
name;
|
|
3782
3794
|
definition;
|
|
@@ -3792,6 +3804,7 @@ var init_collection = __esm({
|
|
|
3792
3804
|
onStorageError;
|
|
3793
3805
|
fold;
|
|
3794
3806
|
maxOperationBytes;
|
|
3807
|
+
assertLocalWriteAllowed;
|
|
3795
3808
|
/**
|
|
3796
3809
|
* Point this collection at the store's new device identity after a node-id
|
|
3797
3810
|
* rotation (RT-21). Internal: called by the owning Store only.
|
|
@@ -3819,7 +3832,8 @@ var init_collection = __esm({
|
|
|
3819
3832
|
causalTracker: this.causalTracker,
|
|
3820
3833
|
...this.secretKeyProvider ? { secretKeyProvider: this.secretKeyProvider } : {},
|
|
3821
3834
|
...this.onStorageError ? { onStorageError: this.onStorageError } : {},
|
|
3822
|
-
...this.maxOperationBytes !== void 0 ? { maxOperationBytes: this.maxOperationBytes } : {}
|
|
3835
|
+
...this.maxOperationBytes !== void 0 ? { maxOperationBytes: this.maxOperationBytes } : {},
|
|
3836
|
+
...this.assertLocalWriteAllowed ? { assertLocalWriteAllowed: this.assertLocalWriteAllowed } : {}
|
|
3823
3837
|
};
|
|
3824
3838
|
}
|
|
3825
3839
|
async insert(data) {
|
|
@@ -5938,7 +5952,8 @@ var init_rebase_unsynced_operations = __esm({
|
|
|
5938
5952
|
});
|
|
5939
5953
|
|
|
5940
5954
|
// src/sync/rotate-node-id.ts
|
|
5941
|
-
async function rotateUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, oldNodeId, newNodeId) {
|
|
5955
|
+
async function rotateUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, oldNodeId, newNodeId, options = {}) {
|
|
5956
|
+
const moveDatabaseNode = options.moveDatabaseNode !== false;
|
|
5942
5957
|
const rotateIds = new Set(unsyncedOpIds);
|
|
5943
5958
|
const idMapping = {};
|
|
5944
5959
|
const rewritten = [];
|
|
@@ -6008,10 +6023,12 @@ async function rotateUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, old
|
|
|
6008
6023
|
);
|
|
6009
6024
|
}
|
|
6010
6025
|
}
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6026
|
+
if (moveDatabaseNode) {
|
|
6027
|
+
await tx.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES ('node_id', ?)", [
|
|
6028
|
+
newNodeId
|
|
6029
|
+
]);
|
|
6030
|
+
await tx.execute("DELETE FROM _kora_meta WHERE key = ?", [NODE_TOKEN_META_KEY]);
|
|
6031
|
+
}
|
|
6015
6032
|
if (rewritten.length > 0) {
|
|
6016
6033
|
await tx.execute(
|
|
6017
6034
|
`INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)
|
|
@@ -6173,7 +6190,9 @@ var init_transaction_context = __esm({
|
|
|
6173
6190
|
...this.config.secretKeyProvider ? { secretKeyProvider: this.config.secretKeyProvider } : {},
|
|
6174
6191
|
...this.mutationName !== void 0 ? { mutationName: this.mutationName } : {},
|
|
6175
6192
|
...this.config.beforeLocalDelete ? { beforeLocalDelete: this.config.beforeLocalDelete } : {},
|
|
6176
|
-
...this.config.fold ? { fold: this.config.fold } : {}
|
|
6193
|
+
...this.config.fold ? { fold: this.config.fold } : {},
|
|
6194
|
+
...this.config.maxOperationBytes !== void 0 ? { maxOperationBytes: this.config.maxOperationBytes } : {},
|
|
6195
|
+
...this.config.assertLocalWriteAllowed ? { assertLocalWriteAllowed: this.config.assertLocalWriteAllowed } : {}
|
|
6177
6196
|
};
|
|
6178
6197
|
}
|
|
6179
6198
|
ensureActive() {
|
|
@@ -7214,6 +7233,12 @@ var init_store = __esm({
|
|
|
7214
7233
|
schema;
|
|
7215
7234
|
adapter;
|
|
7216
7235
|
configNodeId;
|
|
7236
|
+
/** The owner of the pinned node while it belongs to another user than the signed-in one (F9). */
|
|
7237
|
+
pinnedNodeOwnedBy = null;
|
|
7238
|
+
/** The sync server refused the pinned node as another user's (F9). */
|
|
7239
|
+
pinnedNodeRefusedByServer = false;
|
|
7240
|
+
/** The user of the last {@link bindPrincipal}. */
|
|
7241
|
+
signedInPrincipal = null;
|
|
7217
7242
|
dbName;
|
|
7218
7243
|
isolation;
|
|
7219
7244
|
emitter;
|
|
@@ -7288,8 +7313,10 @@ var init_store = __esm({
|
|
|
7288
7313
|
this.foldActive = false;
|
|
7289
7314
|
await relaxValueDomainConstraints(this.adapter, this.schema);
|
|
7290
7315
|
await dropLegacyIndexes(this.adapter, this.schema);
|
|
7316
|
+
const persistedNodeId = await this.readPersistedNodeId();
|
|
7291
7317
|
this.nodeId = await this.loadOrGenerateNodeId();
|
|
7292
7318
|
await registerLocalNode(this.adapter, this.nodeId);
|
|
7319
|
+
await this.registerUnregisteredAuthors(persistedNodeId);
|
|
7293
7320
|
await this.scanLog("quick");
|
|
7294
7321
|
await repairSequenceUniqueness(this.adapter, this.schema, this.nodeId);
|
|
7295
7322
|
await seedTerminalRejectionsOnce(this.adapter);
|
|
@@ -7333,7 +7360,8 @@ var init_store = __esm({
|
|
|
7333
7360
|
this.secretKeyProvider,
|
|
7334
7361
|
(error) => this.reportStorageError(error),
|
|
7335
7362
|
() => this.activeFold(),
|
|
7336
|
-
this.maxOperationBytes
|
|
7363
|
+
this.maxOperationBytes,
|
|
7364
|
+
() => this.assertLocalWriteAllowed()
|
|
7337
7365
|
);
|
|
7338
7366
|
this.collections.set(name, col);
|
|
7339
7367
|
}
|
|
@@ -8625,6 +8653,7 @@ var init_store = __esm({
|
|
|
8625
8653
|
...options?.extraCausalDeps ? { extraCausalDeps: options.extraCausalDeps } : {},
|
|
8626
8654
|
...this.secretKeyProvider ? { secretKeyProvider: this.secretKeyProvider } : {},
|
|
8627
8655
|
...this.maxOperationBytes !== void 0 ? { maxOperationBytes: this.maxOperationBytes } : {},
|
|
8656
|
+
assertLocalWriteAllowed: () => this.assertLocalWriteAllowed(),
|
|
8628
8657
|
...beforeLocalDelete ? { beforeLocalDelete: beforeLocalDelete.bind(this.localMutationHandler) } : {},
|
|
8629
8658
|
onStorageError: (error) => this.reportStorageError(error)
|
|
8630
8659
|
};
|
|
@@ -8894,6 +8923,46 @@ var init_store = __esm({
|
|
|
8894
8923
|
await this.refoldRecordsOf(result.operations.map((op) => op.id));
|
|
8895
8924
|
return result;
|
|
8896
8925
|
}
|
|
8926
|
+
/**
|
|
8927
|
+
* Re-author another local node's never-acknowledged operations under a fresh node id
|
|
8928
|
+
* bound to `principal` (the user they belong to). Used when the sync server refuses an
|
|
8929
|
+
* adopted node whose writes belong to the signed-in user (the app assigned them, or
|
|
8930
|
+
* the node was created for them) but which no claims-aware server ever accepted: a
|
|
8931
|
+
* beta.12 database's node with history the server recorded no owner for. The store's
|
|
8932
|
+
* own node id, clock and counters are untouched; the fresh node is registered, so the
|
|
8933
|
+
* sync engine adopts it like any other local node.
|
|
8934
|
+
*
|
|
8935
|
+
* @param fromNodeId - The refused local node (never the store's own)
|
|
8936
|
+
* @param unsyncedOpIds - Ids of its operations the server never acknowledged
|
|
8937
|
+
* @param principal - The user the writes belong to (the fresh node's binding)
|
|
8938
|
+
* @returns The fresh node id and the rewritten operations (for the outbound queue)
|
|
8939
|
+
* @throws {KoraError} `NODE_ID_UNKNOWN` when `fromNodeId` is the store's own node or
|
|
8940
|
+
* not a local node
|
|
8941
|
+
*/
|
|
8942
|
+
async reauthorLocalNode(fromNodeId, unsyncedOpIds, principal) {
|
|
8943
|
+
this.ensureOpen();
|
|
8944
|
+
const from = (await listLocalNodes(this.adapter)).find((node) => node.nodeId === fromNodeId);
|
|
8945
|
+
if (fromNodeId === this.nodeId || !from) {
|
|
8946
|
+
throw new import_core37.KoraError(
|
|
8947
|
+
`Node id "${fromNodeId}" is not another local node of this database, so its writes cannot be re-authored.`,
|
|
8948
|
+
"NODE_ID_UNKNOWN",
|
|
8949
|
+
{ nodeId: fromNodeId, storeNodeId: this.nodeId }
|
|
8950
|
+
);
|
|
8951
|
+
}
|
|
8952
|
+
const result = await rotateUnsyncedOperationsInLog(
|
|
8953
|
+
this.adapter,
|
|
8954
|
+
this.schema,
|
|
8955
|
+
unsyncedOpIds,
|
|
8956
|
+
fromNodeId,
|
|
8957
|
+
(0, import_core37.generateUUIDv7)(),
|
|
8958
|
+
{ moveDatabaseNode: false }
|
|
8959
|
+
);
|
|
8960
|
+
await registerLocalNode(this.adapter, result.nodeId);
|
|
8961
|
+
await setLocalNodePrincipal(this.adapter, result.nodeId, principal, "fresh");
|
|
8962
|
+
this.versionVector = await this.loadVersionVector();
|
|
8963
|
+
await this.refoldRecordsOf(result.operations.map((op) => op.id));
|
|
8964
|
+
return result;
|
|
8965
|
+
}
|
|
8897
8966
|
/**
|
|
8898
8967
|
* Move this database back to a node id it authored under before (RT-38): after the
|
|
8899
8968
|
* sync server refused the current node, the device tries a node a returning user
|
|
@@ -8973,6 +9042,31 @@ var init_store = __esm({
|
|
|
8973
9042
|
* @returns The node now in use and whether it changed
|
|
8974
9043
|
*/
|
|
8975
9044
|
async bindPrincipal(principal) {
|
|
9045
|
+
const binding = await this.bindPrincipalToNode(principal);
|
|
9046
|
+
if (principal !== this.signedInPrincipal) this.pinnedNodeRefusedByServer = false;
|
|
9047
|
+
this.pinnedNodeOwnedBy = binding.conflict ? (await listLocalNodes(this.adapter)).find((node) => node.nodeId === this.nodeId)?.principal ?? null : null;
|
|
9048
|
+
this.signedInPrincipal = principal;
|
|
9049
|
+
return binding;
|
|
9050
|
+
}
|
|
9051
|
+
/**
|
|
9052
|
+
* The sync server refused this store's pinned node id because another user owns it
|
|
9053
|
+
* (`owned: true`), or later accepted it (`false`). A pinned node cannot move to a fresh
|
|
9054
|
+
* id, so writes made under it could only ever upload as its owner: while the server
|
|
9055
|
+
* says so, local writes are refused (F9). A no-op for a store whose node is not pinned
|
|
9056
|
+
* (it moves to a fresh node instead).
|
|
9057
|
+
*
|
|
9058
|
+
* @param owned - Whether the server reported the node owned by another user
|
|
9059
|
+
*/
|
|
9060
|
+
setPinnedNodeOwnedElsewhere(owned) {
|
|
9061
|
+
if (!this.configNodeId) return;
|
|
9062
|
+
this.pinnedNodeRefusedByServer = owned;
|
|
9063
|
+
}
|
|
9064
|
+
/** Refuse a local write while the pinned node belongs to another user (F9). */
|
|
9065
|
+
assertLocalWriteAllowed() {
|
|
9066
|
+
if (this.pinnedNodeOwnedBy === null && !this.pinnedNodeRefusedByServer) return;
|
|
9067
|
+
throw new NodeOwnedByAnotherUserError(this.nodeId, this.signedInPrincipal);
|
|
9068
|
+
}
|
|
9069
|
+
async bindPrincipalToNode(principal) {
|
|
8976
9070
|
this.ensureOpen();
|
|
8977
9071
|
const previousNodeId = this.nodeId;
|
|
8978
9072
|
const nodes = await listLocalNodes(this.adapter);
|
|
@@ -9352,6 +9446,8 @@ var init_store = __esm({
|
|
|
9352
9446
|
const beforeLocalDelete = this.localMutationHandler?.beforeLocalDelete;
|
|
9353
9447
|
const fold = this.activeFold();
|
|
9354
9448
|
return new TransactionContext({
|
|
9449
|
+
...this.maxOperationBytes !== void 0 ? { maxOperationBytes: this.maxOperationBytes } : {},
|
|
9450
|
+
assertLocalWriteAllowed: () => this.assertLocalWriteAllowed(),
|
|
9355
9451
|
...fold ? { fold } : {},
|
|
9356
9452
|
schema: this.schema,
|
|
9357
9453
|
adapter: this.adapter,
|
|
@@ -9411,8 +9507,9 @@ var init_store = __esm({
|
|
|
9411
9507
|
* move backwards, and unsynced own writes are kept (see
|
|
9412
9508
|
* `RestoreOptions.keepUnsyncedWrites`). Then {@link reloadFromDisk} runs.
|
|
9413
9509
|
*
|
|
9414
|
-
* A version-1 file
|
|
9415
|
-
* `convertBackupV1
|
|
9510
|
+
* A version-1 file (Kora 1.0.0-beta.12 and earlier) is converted first, exactly as
|
|
9511
|
+
* `convertBackupV1` does (`result.convertedFromVersion: 1`); one holding an operation
|
|
9512
|
+
* whose timestamp cannot be recovered is refused (`BACKUP_OPERATION_INVALID`).
|
|
9416
9513
|
*
|
|
9417
9514
|
* @param data - The backup data
|
|
9418
9515
|
* @param options - Restore options (merge, collections, keepUnsyncedWrites, onProgress)
|
|
@@ -9425,13 +9522,19 @@ var init_store = __esm({
|
|
|
9425
9522
|
const started = Date.now();
|
|
9426
9523
|
const onProgress = options?.onProgress ?? (() => {
|
|
9427
9524
|
});
|
|
9428
|
-
const { BackupFormatError: BackupFormatError2, parseBackup: parseBackup2 } = await Promise.resolve().then(() => (init_backup(), backup_exports));
|
|
9525
|
+
const { BackupFormatError: BackupFormatError2, convertBackupV1: convertBackupV12, parseBackup: parseBackup2, readBackupManifest: readBackupManifest2 } = await Promise.resolve().then(() => (init_backup(), backup_exports));
|
|
9429
9526
|
const { restoreMerge: restoreMerge2, restoreReplace: restoreReplace2 } = await Promise.resolve().then(() => (init_restore(), restore_exports));
|
|
9430
9527
|
onProgress({ phase: "verifying", progress: 0, message: "Verifying backup" });
|
|
9431
9528
|
let parsed;
|
|
9529
|
+
let convertedFromVersion;
|
|
9432
9530
|
try {
|
|
9531
|
+
let bytes = data;
|
|
9532
|
+
if (readBackupManifest2(data).version === 1) {
|
|
9533
|
+
bytes = await convertBackupV12(data);
|
|
9534
|
+
convertedFromVersion = 1;
|
|
9535
|
+
}
|
|
9433
9536
|
parsed = await parseBackup2(
|
|
9434
|
-
|
|
9537
|
+
bytes,
|
|
9435
9538
|
options?.collections ? { collections: options.collections } : void 0
|
|
9436
9539
|
);
|
|
9437
9540
|
} catch (error) {
|
|
@@ -9483,6 +9586,7 @@ var init_store = __esm({
|
|
|
9483
9586
|
recordsRestored: counts.recordsRestored,
|
|
9484
9587
|
...options?.merge ? {} : { unsyncedWritesKept: counts.unsyncedWritesKept },
|
|
9485
9588
|
...counts.serverOperationsSkipped ? { serverOperationsSkipped: counts.serverOperationsSkipped } : {},
|
|
9589
|
+
...convertedFromVersion ? { convertedFromVersion } : {},
|
|
9486
9590
|
success: true,
|
|
9487
9591
|
duration: Date.now() - started
|
|
9488
9592
|
};
|
|
@@ -9645,6 +9749,42 @@ var init_store = __esm({
|
|
|
9645
9749
|
onOperation: (operation) => this.publishLocalOperation(operation.collection, operation)
|
|
9646
9750
|
});
|
|
9647
9751
|
}
|
|
9752
|
+
/** The node id the database persisted before this open, if any. */
|
|
9753
|
+
async readPersistedNodeId() {
|
|
9754
|
+
const rows = await this.adapter.query(
|
|
9755
|
+
"SELECT value FROM _kora_meta WHERE key = 'node_id'"
|
|
9756
|
+
);
|
|
9757
|
+
return rows[0]?.value ?? null;
|
|
9758
|
+
}
|
|
9759
|
+
/**
|
|
9760
|
+
* Register node ids this database authored under that the registry does not know
|
|
9761
|
+
* yet: the node id a pinned `nodeId` (the auth device id) just replaced, and the
|
|
9762
|
+
* authors of queued operations. A beta.12 database had no registry, and a template
|
|
9763
|
+
* app created before its user signed in authored under a random node; once the
|
|
9764
|
+
* store is pinned to the device id, that node's queued writes would otherwise sit in
|
|
9765
|
+
* the queue forever, neither uploaded nor reported. Registered, they are adopted or
|
|
9766
|
+
* held (`status.heldNodes`) like any other local node's writes. Only the outbound
|
|
9767
|
+
* queue is read: received operations never enter it.
|
|
9768
|
+
*/
|
|
9769
|
+
async registerUnregisteredAuthors(persistedNodeId) {
|
|
9770
|
+
const authors = /* @__PURE__ */ new Set();
|
|
9771
|
+
if (persistedNodeId !== null && persistedNodeId !== this.nodeId) authors.add(persistedNodeId);
|
|
9772
|
+
const queued = await this.adapter.query(
|
|
9773
|
+
"SELECT payload FROM _kora_sync_queue"
|
|
9774
|
+
);
|
|
9775
|
+
for (const row of queued) {
|
|
9776
|
+
try {
|
|
9777
|
+
const nodeId = JSON.parse(row.payload).node_id;
|
|
9778
|
+
if (typeof nodeId === "string" && nodeId.length > 0) authors.add(nodeId);
|
|
9779
|
+
} catch {
|
|
9780
|
+
}
|
|
9781
|
+
}
|
|
9782
|
+
authors.delete(this.nodeId);
|
|
9783
|
+
for (const nodeId of authors) {
|
|
9784
|
+
if ((0, import_core37.isReservedNodeId)(nodeId)) continue;
|
|
9785
|
+
await registerLocalNode(this.adapter, nodeId);
|
|
9786
|
+
}
|
|
9787
|
+
}
|
|
9648
9788
|
async loadOrGenerateNodeId() {
|
|
9649
9789
|
if (this.configNodeId && (0, import_core37.isReservedNodeId)(this.configNodeId)) {
|
|
9650
9790
|
throw new ReservedNodeIdError(this.configNodeId, "StoreConfig.nodeId");
|
|
@@ -9717,6 +9857,7 @@ __export(index_exports, {
|
|
|
9717
9857
|
LeaderUnresponsiveError: () => LeaderUnresponsiveError,
|
|
9718
9858
|
MemoryBlobStore: () => MemoryBlobStore,
|
|
9719
9859
|
NoLeaderError: () => NoLeaderError,
|
|
9860
|
+
NodeOwnedByAnotherUserError: () => NodeOwnedByAnotherUserError,
|
|
9720
9861
|
OpfsBlobStore: () => OpfsBlobStore,
|
|
9721
9862
|
OptimisticLockError: () => OptimisticLockError,
|
|
9722
9863
|
PersistenceError: () => PersistenceError,
|
|
@@ -10087,7 +10228,6 @@ var LOAD_ORIGIN = "kora-load";
|
|
|
10087
10228
|
var REMOTE_ORIGIN = "kora-remote";
|
|
10088
10229
|
var DOC_CHANNEL_ORIGIN = "kora-doc-channel";
|
|
10089
10230
|
var TEXT_KEY2 = "content";
|
|
10090
|
-
var COMPACT_AFTER_DELTAS = 20;
|
|
10091
10231
|
var PERSIST_DEBOUNCE_MS = 400;
|
|
10092
10232
|
function createRichTextController(options) {
|
|
10093
10233
|
const {
|
|
@@ -10109,16 +10249,19 @@ function createRichTextController(options) {
|
|
|
10109
10249
|
let canUndo = false;
|
|
10110
10250
|
let canRedo = false;
|
|
10111
10251
|
let cursors = [];
|
|
10252
|
+
let editGeneration = 0;
|
|
10253
|
+
let savedGeneration = 0;
|
|
10254
|
+
let saving = null;
|
|
10255
|
+
let saveAgain = false;
|
|
10112
10256
|
const listeners = /* @__PURE__ */ new Set();
|
|
10113
10257
|
let snapshot = {
|
|
10114
10258
|
ready: false,
|
|
10115
10259
|
error: null,
|
|
10116
10260
|
canUndo: false,
|
|
10117
10261
|
canRedo: false,
|
|
10118
|
-
cursors: []
|
|
10262
|
+
cursors: [],
|
|
10263
|
+
hasUnsavedChanges: false
|
|
10119
10264
|
};
|
|
10120
|
-
const baseUpdateRef = { current: null };
|
|
10121
|
-
const pendingDeltasRef = { current: [] };
|
|
10122
10265
|
const docChannelActiveRef = { current: false };
|
|
10123
10266
|
let persistTimer = null;
|
|
10124
10267
|
let recordUnsubscribe = null;
|
|
@@ -10130,7 +10273,8 @@ function createRichTextController(options) {
|
|
|
10130
10273
|
error,
|
|
10131
10274
|
canUndo,
|
|
10132
10275
|
canRedo,
|
|
10133
|
-
cursors: [...cursors]
|
|
10276
|
+
cursors: [...cursors],
|
|
10277
|
+
hasUnsavedChanges: editGeneration > savedGeneration
|
|
10134
10278
|
};
|
|
10135
10279
|
};
|
|
10136
10280
|
const notify = () => {
|
|
@@ -10201,26 +10345,38 @@ function createRichTextController(options) {
|
|
|
10201
10345
|
return;
|
|
10202
10346
|
}
|
|
10203
10347
|
Y3.applyUpdate(doc, encoded, REMOTE_ORIGIN);
|
|
10204
|
-
baseUpdateRef.current = encoded;
|
|
10205
|
-
pendingDeltasRef.current = [];
|
|
10206
10348
|
syncHistoryState();
|
|
10207
10349
|
};
|
|
10208
|
-
const
|
|
10209
|
-
const
|
|
10210
|
-
|
|
10211
|
-
baseUpdateRef.current = snapshot2;
|
|
10212
|
-
pendingDeltasRef.current = [];
|
|
10213
|
-
}
|
|
10350
|
+
const persistOnce = async () => {
|
|
10351
|
+
const generation = editGeneration;
|
|
10352
|
+
const state = Y3.encodeStateAsUpdate(doc);
|
|
10214
10353
|
try {
|
|
10215
10354
|
await collection.update(recordId, {
|
|
10216
|
-
[fieldName]:
|
|
10355
|
+
[fieldName]: state
|
|
10217
10356
|
});
|
|
10357
|
+
if (generation > savedGeneration) savedGeneration = generation;
|
|
10358
|
+
error = null;
|
|
10218
10359
|
} catch (cause) {
|
|
10219
|
-
|
|
10220
|
-
error = cause instanceof Error ? cause : new Error(String(cause));
|
|
10221
|
-
notify();
|
|
10222
|
-
}
|
|
10360
|
+
error = cause instanceof Error ? cause : new Error(String(cause));
|
|
10223
10361
|
}
|
|
10362
|
+
if (!disposed) notify();
|
|
10363
|
+
};
|
|
10364
|
+
const flushPersist = () => {
|
|
10365
|
+
if (saving) {
|
|
10366
|
+
saveAgain = true;
|
|
10367
|
+
return saving;
|
|
10368
|
+
}
|
|
10369
|
+
const run = (async () => {
|
|
10370
|
+
do {
|
|
10371
|
+
saveAgain = false;
|
|
10372
|
+
await persistOnce();
|
|
10373
|
+
} while (saveAgain);
|
|
10374
|
+
})();
|
|
10375
|
+
saving = run;
|
|
10376
|
+
void run.finally(() => {
|
|
10377
|
+
if (saving === run) saving = null;
|
|
10378
|
+
});
|
|
10379
|
+
return run;
|
|
10224
10380
|
};
|
|
10225
10381
|
const schedulePersist = () => {
|
|
10226
10382
|
if (persistTimer) {
|
|
@@ -10236,7 +10392,7 @@ function createRichTextController(options) {
|
|
|
10236
10392
|
if (origin === LOAD_ORIGIN || origin === REMOTE_ORIGIN || origin === DOC_CHANNEL_ORIGIN) {
|
|
10237
10393
|
return;
|
|
10238
10394
|
}
|
|
10239
|
-
|
|
10395
|
+
editGeneration += 1;
|
|
10240
10396
|
if (docChannelActiveRef.current && syncEngine) {
|
|
10241
10397
|
syncEngine.getRichtextDocChannel?.().send(collectionName, recordId, fieldName, update);
|
|
10242
10398
|
schedulePersist();
|
|
@@ -10320,8 +10476,6 @@ function createRichTextController(options) {
|
|
|
10320
10476
|
target.delete(0, target.length);
|
|
10321
10477
|
}, LOAD_ORIGIN);
|
|
10322
10478
|
const encoded = encodeRichtextInput(record?.[fieldName]);
|
|
10323
|
-
baseUpdateRef.current = encoded;
|
|
10324
|
-
pendingDeltasRef.current = [];
|
|
10325
10479
|
if (encoded) {
|
|
10326
10480
|
Y3.applyUpdate(doc, encoded, LOAD_ORIGIN);
|
|
10327
10481
|
}
|
|
@@ -10364,6 +10518,16 @@ function createRichTextController(options) {
|
|
|
10364
10518
|
setUser(nextUser) {
|
|
10365
10519
|
user = nextUser;
|
|
10366
10520
|
},
|
|
10521
|
+
retrySave() {
|
|
10522
|
+
if (persistTimer) {
|
|
10523
|
+
clearTimeout(persistTimer);
|
|
10524
|
+
persistTimer = null;
|
|
10525
|
+
}
|
|
10526
|
+
return flushPersist();
|
|
10527
|
+
},
|
|
10528
|
+
getUnsavedState() {
|
|
10529
|
+
return editGeneration > savedGeneration ? Y3.encodeStateAsUpdate(doc) : null;
|
|
10530
|
+
},
|
|
10367
10531
|
destroy() {
|
|
10368
10532
|
if (disposed) {
|
|
10369
10533
|
return;
|
|
@@ -10373,14 +10537,15 @@ function createRichTextController(options) {
|
|
|
10373
10537
|
clearTimeout(persistTimer);
|
|
10374
10538
|
persistTimer = null;
|
|
10375
10539
|
}
|
|
10540
|
+
if (editGeneration > savedGeneration) {
|
|
10541
|
+
void flushPersist();
|
|
10542
|
+
}
|
|
10376
10543
|
doc.off("update", onDocUpdate);
|
|
10377
10544
|
recordUnsubscribe?.();
|
|
10378
10545
|
docChannelUnsubscribe?.();
|
|
10379
10546
|
awarenessUnsubscribe?.();
|
|
10380
10547
|
clearCursor();
|
|
10381
10548
|
undoManager.destroy();
|
|
10382
|
-
baseUpdateRef.current = null;
|
|
10383
|
-
pendingDeltasRef.current = [];
|
|
10384
10549
|
docChannelActiveRef.current = false;
|
|
10385
10550
|
listeners.clear();
|
|
10386
10551
|
}
|
|
@@ -10401,16 +10566,6 @@ function encodeRichtextInput(value) {
|
|
|
10401
10566
|
throw new Error("Richtext record value must be a string, Uint8Array, ArrayBuffer, or null.");
|
|
10402
10567
|
}
|
|
10403
10568
|
}
|
|
10404
|
-
function composeRichtextSnapshot(base, deltas) {
|
|
10405
|
-
const mergedDoc = new Y3.Doc();
|
|
10406
|
-
if (base) {
|
|
10407
|
-
Y3.applyUpdate(mergedDoc, base);
|
|
10408
|
-
}
|
|
10409
|
-
for (const delta of deltas) {
|
|
10410
|
-
Y3.applyUpdate(mergedDoc, delta);
|
|
10411
|
-
}
|
|
10412
|
-
return Y3.encodeStateAsUpdate(mergedDoc);
|
|
10413
|
-
}
|
|
10414
10569
|
function updatesEqual(left, right) {
|
|
10415
10570
|
if (left.length !== right.length) {
|
|
10416
10571
|
return false;
|
|
@@ -11039,6 +11194,7 @@ function createChunkPortPair() {
|
|
|
11039
11194
|
LeaderUnresponsiveError,
|
|
11040
11195
|
MemoryBlobStore,
|
|
11041
11196
|
NoLeaderError,
|
|
11197
|
+
NodeOwnedByAnotherUserError,
|
|
11042
11198
|
OpfsBlobStore,
|
|
11043
11199
|
OptimisticLockError,
|
|
11044
11200
|
PersistenceError,
|