@tearleads/client-sdk 0.1.113 → 0.1.115
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/data/persistence/container-contents/containerContentsPersistence.js +3 -8
- package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerHydrationPersistence.js +15 -5
- package/data/persistence/container-contents/containerHydrationPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js +8 -2
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMetadataRows.js +34 -2
- package/data/persistence/container-contents/containerMetadataRows.js.map +1 -1
- package/data/persistence/container-contents/containerPersistence.d.ts +231 -0
- package/data/persistence/container-contents/containerPersistence.js +20 -16
- package/data/persistence/container-contents/containerPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerRecoveryPersistence.js +167 -51
- package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentContainerProjectionPersistence.js +56 -19
- package/data/persistence/container-contents/documentContainerProjectionPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentMoveIntentPersistence.js +29 -5
- package/data/persistence/container-contents/documentMoveIntentPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentPlacement.js +24 -5
- package/data/persistence/container-contents/documentPlacement.js.map +1 -1
- package/data/persistence/container-contents/documentPlacementIntentEnqueue.js +1 -1
- package/data/persistence/container-contents/documentPlacementIntentEnqueue.js.map +1 -1
- package/data/persistence/documents/containerDocumentTombstoneHoldsPersistence.js +1 -1
- package/data/persistence/documents/containerDocumentTombstoneHoldsPersistence.js.map +1 -1
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js +43 -35
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js.map +1 -1
- package/data/persistence/documents/documentsPersistence.js +4 -58
- package/data/persistence/documents/documentsPersistence.js.map +1 -1
- package/data/persistence/documents/internal/containerDocumentTombstoneHolds.js +15 -11
- package/data/persistence/documents/internal/containerDocumentTombstoneHolds.js.map +1 -1
- package/data/persistence/documents/internal/containerDocumentTombstones.js +87 -35
- package/data/persistence/documents/internal/containerDocumentTombstones.js.map +1 -1
- package/data/persistence/documents/internal/discoveredDocumentPlacement.js +5 -2
- package/data/persistence/documents/internal/discoveredDocumentPlacement.js.map +1 -1
- package/data/persistence/documents/internal/discoveredDocumentUpsert.js +174 -0
- package/data/persistence/documents/internal/discoveredDocumentUpsert.js.map +1 -0
- package/data/persistence/documents/internal/documentMutationPersistence.js +15 -15
- package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
- package/data/persistence/documents/internal/documentRows.js +122 -11
- package/data/persistence/documents/internal/documentRows.js.map +1 -1
- package/data/persistence/documents/internal/rowQueryPersistence.js +70 -27
- package/data/persistence/documents/internal/rowQueryPersistence.js.map +1 -1
- package/data/persistence/documents/records/documentHistoryPersistence.js +17 -11
- package/data/persistence/documents/records/documentHistoryPersistence.js.map +1 -1
- package/data/persistence/documents/records/documentRecordPersistence.d.ts +5 -0
- package/data/persistence/documents/records/documentRecordPersistence.js +41 -6
- package/data/persistence/documents/records/documentRecordPersistence.js.map +1 -1
- package/data/persistence/documents/records/index.d.ts +1 -1
- package/data/persistence/documents/records/index.js +1 -1
- package/data/persistence/documents/records/index.js.map +1 -1
- package/data/persistence/keyingCheckpoints/documentPurgeCheckpointPersistence.d.ts +2 -0
- package/data/persistence/keyingCheckpoints/documentPurgeCheckpointPersistence.js +17 -1
- package/data/persistence/keyingCheckpoints/documentPurgeCheckpointPersistence.js.map +1 -1
- package/package.json +1 -1
- package/workflows/container-contents/documentQueries/index.js +21 -6
- package/workflows/container-contents/documentQueries/index.js.map +1 -1
- package/workflows/container-contents/remoteReset/containerRemoteReset.js +12 -7
- package/workflows/container-contents/remoteReset/containerRemoteReset.js.map +1 -1
- package/workflows/documents/documentMutationCommit.js +11 -12
- package/workflows/documents/documentMutationCommit.js.map +1 -1
- package/workflows/documents/documentMutationPreparation.d.ts +1 -0
- package/workflows/documents/documentMutationPreparation.js +1 -0
- package/workflows/documents/documentMutationPreparation.js.map +1 -1
- package/workflows/documents/documentPersistGuards.js +24 -0
- package/workflows/documents/documentPersistGuards.js.map +1 -1
- package/workflows/documents/remoteReset/documentRemoteReset.js +31 -18
- package/workflows/documents/remoteReset/documentRemoteReset.js.map +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discoveredDocumentUpsert.js","sourceRoot":"","sources":["discoveredDocumentUpsert.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport {\n DEFAULT_DOCUMENT_KIND,\n DOCUMENTS_APP_KIND,\n} from \"../../../documents/documentConstants\";\nimport type {\n DiscoveredDocumentInput,\n DocumentSummary,\n} from \"../../../documents/documentSummary\";\nimport type { ClientSQLiteTransactionScope } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport type { ExecSql } from \"../../../sqlite/sqlSchema\";\nimport {\n type DocumentMovePlacement,\n loadDocumentMovePlacements,\n} from \"../../container-contents/documentPlacement\";\nimport {\n hasDocumentPurgeCheckpointInTransaction,\n listPurgedDocumentIdsInTransaction,\n} from \"../../keyingCheckpoints/documentPurgeCheckpointPersistence\";\nimport { findLocalIdByDocumentId, findLocalIdsByDocumentIds } from \"../records\";\nimport type { StoredDocumentRecord } from \"../types\";\nimport {\n discoveredDocumentPlacement,\n resolveDiscoveredDocumentPlacement,\n} from \"./discoveredDocumentPlacement\";\nimport { toDocumentSummary } from \"./documentProjectionRows\";\nimport {\n documentSaveTimestampFrom,\n resolveDocumentSaveTimestamp,\n saveDocumentRows,\n saveDocumentRowsBatch,\n} from \"./documentRows\";\nimport { resolvePersistedDocumentRuntimeState } from \"./documentRuntimeState\";\nimport { loadPendingCreateSummary } from \"./pendingCreateAdoption\";\nimport {\n loadStoredDocumentState,\n loadStoredDocumentStates,\n type StoredDocumentState,\n} from \"./rowQueryPersistence\";\n\nconst DISCOVERED_DOCUMENT_PLACEHOLDER_TITLE = \"Syncing document...\";\n\nfunction purgedDocumentError(): KeyingVerificationError {\n return new KeyingVerificationError(\n \"rollback\",\n \"Document discovery targets a permanently purged document\",\n );\n}\n\nfunction fenceSkippedError(): Error {\n return new Error(\"Document recovery generation changed during discovery\");\n}\n\nfunction nextDiscoveredDocument(\n localId: string,\n input: DiscoveredDocumentInput,\n existingDocument: StoredDocumentRecord | null,\n placement: ReturnType<typeof discoveredDocumentPlacement>,\n): StoredDocumentRecord {\n return {\n id: localId,\n documentId: input.documentId,\n documentKind: existingDocument?.documentKind ?? DEFAULT_DOCUMENT_KIND,\n text: existingDocument?.text ?? \"\",\n title: existingDocument?.title ?? DISCOVERED_DOCUMENT_PLACEHOLDER_TITLE,\n snapshotEndVersion: existingDocument?.snapshotEndVersion ?? \"\",\n recoveryGeneration: existingDocument?.recoveryGeneration ?? 0,\n ...placement,\n effectiveAccessLevel:\n input.effectiveAccessLevel ??\n existingDocument?.effectiveAccessLevel ??\n null,\n ...resolvePersistedDocumentRuntimeState(existingDocument, {\n accessEpoch: placement.accessEpoch,\n documentId: input.documentId,\n }),\n };\n}\n\n/** One discovered document, read and written on its own (the per-row path). */\nasync function upsertDiscoveredDocumentWithExec(\n execSql: ExecSql,\n input: DiscoveredDocumentInput,\n pendingCreates: ReadonlyMap<string, string>,\n tx: ClientSQLiteTransactionScope,\n): Promise<DocumentSummary> {\n if (await hasDocumentPurgeCheckpointInTransaction(tx, input.documentId)) {\n throw purgedDocumentError();\n }\n const existingLocalId = await findLocalIdByDocumentId(\n execSql,\n DOCUMENTS_APP_KIND,\n input.documentId,\n );\n const localId = existingLocalId ?? input.documentId;\n const pendingLocalId = pendingCreates.get(input.documentId);\n if (!existingLocalId && pendingLocalId) {\n const pending = await loadPendingCreateSummary(execSql, pendingLocalId);\n if (!pending) throw new Error(\"Pending create projection is unavailable\");\n return pending;\n }\n const existingDocument =\n (await loadStoredDocumentState(execSql, localId))?.record ?? null;\n const placement = await resolveDiscoveredDocumentPlacement(\n tx,\n existingDocument,\n input,\n );\n const nextDocument = nextDiscoveredDocument(\n localId,\n input,\n existingDocument,\n placement,\n );\n const updatedAt = await resolveDocumentSaveTimestamp({\n document: nextDocument,\n options: existingDocument ? undefined : { updatedAt: input.createdAt },\n tx,\n });\n if (!(await saveDocumentRows({ document: nextDocument, tx, updatedAt }))) {\n throw fenceSkippedError();\n }\n return toDocumentSummary(nextDocument, updatedAt);\n}\n\nasync function upsertDiscoveredDocumentsPerRow(\n execSql: ExecSql,\n inputs: ReadonlyArray<DiscoveredDocumentInput>,\n pendingCreates: ReadonlyMap<string, string>,\n tx: ClientSQLiteTransactionScope,\n): Promise<DocumentSummary[]> {\n const summaries: DocumentSummary[] = [];\n for (const input of inputs) {\n summaries.push(\n await upsertDiscoveredDocumentWithExec(\n execSql,\n input,\n pendingCreates,\n tx,\n ),\n );\n }\n return summaries;\n}\n\ntype DiscoveryTarget =\n | { kind: \"pending\"; summary: DocumentSummary }\n | { input: DiscoveredDocumentInput; kind: \"write\"; localId: string };\n\n/**\n * Resolves every input to its pending-create summary or its target local id,\n * in input order, so the first purged or unavailable input throws exactly as\n * the per-row walk would. Returns null when one input's write could change\n * another input's reads, which only the per-row walk handles.\n */\nasync function resolveDiscoveryTargets(\n execSql: ExecSql,\n inputs: ReadonlyArray<DiscoveredDocumentInput>,\n pendingCreates: ReadonlyMap<string, string>,\n tx: ClientSQLiteTransactionScope,\n): Promise<DiscoveryTarget[] | null> {\n const documentIds = inputs.map((input) => input.documentId);\n const purged = await listPurgedDocumentIdsInTransaction(tx, documentIds);\n const existingLocalIds = await findLocalIdsByDocumentIds(\n execSql,\n DOCUMENTS_APP_KIND,\n documentIds,\n );\n const targets: DiscoveryTarget[] = [];\n const pendingLocalIds = new Set<string>();\n for (const input of inputs) {\n if (purged.has(input.documentId)) throw purgedDocumentError();\n const existingLocalId = existingLocalIds.get(input.documentId);\n const pendingLocalId = pendingCreates.get(input.documentId);\n if (!existingLocalId && pendingLocalId) {\n const pending = await loadPendingCreateSummary(execSql, pendingLocalId);\n if (!pending) throw new Error(\"Pending create projection is unavailable\");\n pendingLocalIds.add(pendingLocalId);\n targets.push({ kind: \"pending\", summary: pending });\n continue;\n }\n targets.push({\n input,\n kind: \"write\",\n localId: existingLocalId ?? input.documentId,\n });\n }\n const writeLocalIds = targets.flatMap((target) =>\n target.kind === \"write\" ? [target.localId] : [],\n );\n const independent =\n new Set(writeLocalIds).size === writeLocalIds.length &&\n !writeLocalIds.some((localId) => pendingLocalIds.has(localId));\n return independent ? targets : null;\n}\n\n/**\n * Whether writing each target leaves every other target's reads unchanged: a\n * stored row at a target local id that carries a different document id would\n * be re-pointed by its write, which another input's lookup could have read.\n */\nfunction targetsReadDisjointRows(\n targets: ReadonlyArray<DiscoveryTarget>,\n states: ReadonlyMap<string, StoredDocumentState>,\n): boolean {\n return targets.every(\n (target) =>\n target.kind !== \"write\" ||\n (states.get(target.localId)?.record.documentId ??\n target.input.documentId) === target.input.documentId,\n );\n}\n\nfunction plannedDiscoveryWrite(\n target: Extract<DiscoveryTarget, { kind: \"write\" }>,\n state: StoredDocumentState | undefined,\n intent: DocumentMovePlacement | undefined,\n) {\n const existingDocument = state?.record ?? null;\n const document = nextDiscoveredDocument(\n target.localId,\n target.input,\n existingDocument,\n discoveredDocumentPlacement(existingDocument, target.input, intent),\n );\n // The joined state read is the row and projection the per-row walk reads\n // again for its timestamp; nothing earlier in this transaction writes it.\n const updatedAt = state\n ? documentSaveTimestampFrom(\n {\n projection: state.projection,\n snapshotEndVersion: state.record.snapshotEndVersion,\n },\n document,\n )\n : target.input.createdAt;\n return { document, updatedAt };\n}\n\n/**\n * Discovery as set statements: one read per table for every input, then\n * multi-row upserts. Inputs whose rows could interact (a repeated document\n * id, a shared local id, or a row another input would re-point) keep the\n * per-row walk, which reads each input after the previous one's writes.\n */\nexport async function upsertDiscoveredDocumentsInTransaction(\n execSql: ExecSql,\n inputs: ReadonlyArray<DiscoveredDocumentInput>,\n pendingCreates: ReadonlyMap<string, string>,\n tx: ClientSQLiteTransactionScope,\n): Promise<DocumentSummary[]> {\n const documentIds = inputs.map((input) => input.documentId);\n if (new Set(documentIds).size !== documentIds.length) {\n return upsertDiscoveredDocumentsPerRow(execSql, inputs, pendingCreates, tx);\n }\n const targets = await resolveDiscoveryTargets(\n execSql,\n inputs,\n pendingCreates,\n tx,\n );\n if (!targets) {\n return upsertDiscoveredDocumentsPerRow(execSql, inputs, pendingCreates, tx);\n }\n const writes = targets.flatMap((target) =>\n target.kind === \"write\" ? [target] : [],\n );\n if (writes.length === 0) {\n return targets.flatMap((target) =>\n target.kind === \"pending\" ? [target.summary] : [],\n );\n }\n const states = await loadStoredDocumentStates(\n execSql,\n writes.map((target) => target.localId),\n );\n if (!targetsReadDisjointRows(targets, states)) {\n return upsertDiscoveredDocumentsPerRow(execSql, inputs, pendingCreates, tx);\n }\n const intents = await loadDocumentMovePlacements(\n tx,\n writes.map((target) => target.input.documentId),\n );\n const planned = new Map(\n writes.map((target) => [\n target.localId,\n plannedDiscoveryWrite(\n target,\n states.get(target.localId),\n intents.get(target.input.documentId),\n ),\n ]),\n );\n const saved = await saveDocumentRowsBatch({\n entries: [...planned.values()],\n tx,\n });\n if (saved.size !== planned.size) throw fenceSkippedError();\n return targets.map((target) => {\n if (target.kind === \"pending\") return target.summary;\n const write = planned.get(target.localId);\n if (!write) throw new Error(\"Discovered document write is missing\");\n return toDocumentSummary(write.document, write.updatedAt);\n });\n}\n"]}
|
|
@@ -8,10 +8,10 @@ import { getClientSQLitePersistenceRuntime, } from "../../../sqlite/sqlitePersis
|
|
|
8
8
|
import { runSerializedSqlMutation } from "../../../sqlite/sqlSchema.js";
|
|
9
9
|
import { appendDocumentHistoryUpdates, replaceDocumentHistoryCheckpoint, } from "../records/documentHistoryPersistence.js";
|
|
10
10
|
import { insertDocumentPendingUpdateWithHistoryInTransaction, settleQueuedDocumentUpdatesInTransaction, } from "../records/documentPendingUpdatePersistence.js";
|
|
11
|
-
import { clearDocumentSyncFailureInTransaction } from "../records/documentSyncFailurePersistence.js";
|
|
12
11
|
import { applyStoredAttachmentRemoval, upsertStoredAttachmentStagingRows, } from "./attachmentStagingPersistence.js";
|
|
13
12
|
import { sameDocumentSecurityIdentity, sameNullableDocumentValue, } from "./documentRecordIdentity.js";
|
|
14
|
-
import {
|
|
13
|
+
import { documentSaveTimestampFrom, saveDocumentRows } from "./documentRows.js";
|
|
14
|
+
import { loadStoredDocumentState } from "./rowQueryPersistence.js";
|
|
15
15
|
function sameRecoveryGeneration(current, expected) {
|
|
16
16
|
return ((current.recoveryGeneration ?? 0) === (expected.recoveryGeneration ?? 0));
|
|
17
17
|
}
|
|
@@ -140,7 +140,8 @@ async function findCoveredRecoveryLocalState(tx, localId, recoverySnapshot) {
|
|
|
140
140
|
}
|
|
141
141
|
async function applyStoredDocumentMutation(input) {
|
|
142
142
|
const { lockedExecSql, mutation, tx } = input;
|
|
143
|
-
const
|
|
143
|
+
const currentState = await loadStoredDocumentState(lockedExecSql, mutation.document.id);
|
|
144
|
+
const currentRecord = currentState?.record ?? null;
|
|
144
145
|
if (mutation.stillCurrent && !mutation.stillCurrent()) {
|
|
145
146
|
return { committed: false, currentRecord };
|
|
146
147
|
}
|
|
@@ -167,20 +168,19 @@ async function applyStoredDocumentMutation(input) {
|
|
|
167
168
|
await appendMutationHistory(lockedExecSql, mutation, tx);
|
|
168
169
|
await insertPendingUpdate(tx, mutation.document.id, mutation.pendingUpdate);
|
|
169
170
|
await deleteAcceptedPendingUpdates(tx, mutation.document.id, mutation.acceptedPendingUpdateIds);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
// Nothing above writes the document or projection rows, so the state read
|
|
172
|
+
// at the top of this transaction still decides the save timestamp.
|
|
173
|
+
const updatedAt = mutation.updatedAt ||
|
|
174
|
+
documentSaveTimestampFrom({
|
|
175
|
+
projection: currentState?.projection ?? null,
|
|
176
|
+
snapshotEndVersion: currentRecord.snapshotEndVersion,
|
|
177
|
+
}, mutation.document);
|
|
178
|
+
if (!(await saveDocumentRows({
|
|
179
|
+
clearSyncFailure: mutation.clearSyncFailure === true,
|
|
177
180
|
document: mutation.document,
|
|
178
|
-
options: mutation.updatedAt === undefined
|
|
179
|
-
? undefined
|
|
180
|
-
: { updatedAt: mutation.updatedAt },
|
|
181
181
|
tx,
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
updatedAt,
|
|
183
|
+
}))) {
|
|
184
184
|
throw new Error("Document recovery generation changed before mutation save");
|
|
185
185
|
}
|
|
186
186
|
await input.saveClientProjection(lockedExecSql, updatedAt);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentMutationPersistence.js","sourceRoot":"","sources":["documentMutationPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { base64ToBytes } from \"@tearleads/encoding\";\nimport {\n createDocument,\n importSnapshot,\n updateMatchesDocumentHistory,\n} from \"@tearleads/loro\";\nimport { and, eq } from \"drizzle-orm\";\nimport { DOCUMENTS_APP_KIND } from \"../../../documents/documentConstants\";\nimport { documentSyncPullContinuationsEqual } from \"../../../documents/shared/sync/pullContinuation\";\nimport { documentHistoryUpdates } from \"../../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport { runSerializedSqlMutation } from \"../../../sqlite/sqlSchema\";\nimport {\n appendDocumentHistoryUpdates,\n replaceDocumentHistoryCheckpoint,\n} from \"../records/documentHistoryPersistence\";\nimport {\n insertDocumentPendingUpdateWithHistoryInTransaction,\n settleQueuedDocumentUpdatesInTransaction,\n} from \"../records/documentPendingUpdatePersistence\";\nimport { clearDocumentSyncFailureInTransaction } from \"../records/documentSyncFailurePersistence\";\nimport type { DocumentsPersistence, StoredDocumentRecord } from \"../types\";\nimport {\n applyStoredAttachmentRemoval,\n upsertStoredAttachmentStagingRows,\n} from \"./attachmentStagingPersistence\";\nimport {\n sameDocumentSecurityIdentity,\n sameNullableDocumentValue,\n} from \"./documentRecordIdentity\";\nimport { resolveDocumentSaveTimestamp, saveDocumentRows } from \"./documentRows\";\n\nfunction sameRecoveryGeneration(\n current: StoredDocumentRecord,\n expected: StoredDocumentRecord,\n): boolean {\n return (\n (current.recoveryGeneration ?? 0) === (expected.recoveryGeneration ?? 0)\n );\n}\n\nfunction sameExpectedDocumentRecord(\n current: StoredDocumentRecord,\n expected: StoredDocumentRecord,\n): boolean {\n return (\n sameDocumentSecurityIdentity(current, expected) &&\n sameRecoveryGeneration(current, expected) &&\n sameNullableDocumentValue(current.lastCommitLsn, expected.lastCommitLsn) &&\n current.snapshotEndVersion === expected.snapshotEndVersion &&\n sameNullableDocumentValue(\n current.pendingBaseVersion,\n expected.pendingBaseVersion,\n ) &&\n documentSyncPullContinuationsEqual(\n current.pullContinuation,\n expected.pullContinuation,\n ) &&\n Boolean(current.pullContinuationRecoveryRequired) ===\n Boolean(expected.pullContinuationRecoveryRequired) &&\n (current.documentKind ?? null) === (expected.documentKind ?? null) &&\n current.text === expected.text &&\n (current.title ?? null) === (expected.title ?? null)\n );\n}\n\nasync function deleteAcceptedPendingUpdates(\n tx: ClientSQLiteTransactionScope,\n localId: string,\n ids: readonly string[],\n): Promise<void> {\n await settleQueuedDocumentUpdatesInTransaction(\n tx,\n { appKind: DOCUMENTS_APP_KIND, localId },\n ids,\n );\n}\n\nasync function insertPendingUpdate(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n pendingUpdate: Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"pendingUpdate\"],\n): Promise<void> {\n if (!pendingUpdate) return;\n await insertDocumentPendingUpdateWithHistoryInTransaction({\n createdAt: new Date().toISOString(),\n pendingUpdate,\n scope: { appKind: DOCUMENTS_APP_KIND, localId: documentId },\n tx,\n });\n}\n\nasync function settleMutationConflict(input: {\n currentRecord: StoredDocumentRecord | null;\n mutation: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[1];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (\n input.currentRecord &&\n input.mutation.settleAcceptedPendingOnConflict &&\n sameDocumentSecurityIdentity(\n input.currentRecord,\n input.mutation.expectedRecord,\n ) &&\n sameRecoveryGeneration(input.currentRecord, input.mutation.expectedRecord)\n ) {\n await deleteAcceptedPendingUpdates(\n input.tx,\n input.mutation.document.id,\n input.mutation.acceptedPendingUpdateIds,\n );\n }\n}\n\nasync function appendMutationHistory(\n execSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0],\n input: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[1],\n tx: ClientSQLiteTransactionScope,\n): Promise<void> {\n if (input.historyCheckpoint) {\n const coveredLocalState = input.historyCheckpoint.pruneCoveredLocalState\n ? await findCoveredRecoveryLocalState(\n tx,\n input.document.id,\n input.historyCheckpoint.snapshot,\n )\n : {\n checkpointIds: [],\n hasOrdinaryPendingUpdates: false,\n hasUnprunablePendingUpdates: false,\n hasUnverifiedHistoryTail: false,\n tailIds: [],\n };\n if (\n input.historyCheckpoint.pruneCoveredLocalState &&\n (coveredLocalState.hasOrdinaryPendingUpdates ||\n coveredLocalState.hasUnprunablePendingUpdates)\n ) {\n throw new Error(\n \"Document recovery found unproven pending updates before installation\",\n );\n }\n if (\n input.historyCheckpoint.pruneCoveredLocalState &&\n coveredLocalState.hasUnverifiedHistoryTail\n ) {\n throw new Error(\n \"Document recovery found unverified history tail before installation\",\n );\n }\n // A queued rotation baseline the recovery snapshot covers is unqueued\n // first so the replacement below can delete it with the covered tail.\n await deleteAcceptedPendingUpdates(\n tx,\n input.document.id,\n coveredLocalState.checkpointIds,\n );\n const checkpointReplaced = await replaceDocumentHistoryCheckpoint(\n execSql,\n { appKind: DOCUMENTS_APP_KIND, localId: input.document.id },\n {\n ...input.historyCheckpoint,\n coveredTailIds: input.historyCheckpoint.pruneCoveredLocalState\n ? coveredLocalState.tailIds\n : input.historyCheckpoint.coveredTailIds,\n },\n );\n if (input.historyCheckpoint.pruneCoveredLocalState && !checkpointReplaced) {\n throw new Error(\n \"Document recovery checkpoint was superseded before installation\",\n );\n }\n }\n if (input.historyUpdates && input.historyUpdates.length > 0) {\n await appendDocumentHistoryUpdates(\n execSql,\n { appKind: DOCUMENTS_APP_KIND, localId: input.document.id },\n input.historyUpdates,\n input.historyUpdateOrigin ?? \"local\",\n );\n }\n}\n\nasync function findCoveredRecoveryLocalState(\n tx: ClientSQLiteTransactionScope,\n localId: string,\n recoverySnapshot: string,\n): Promise<{\n checkpointIds: string[];\n hasOrdinaryPendingUpdates: boolean;\n hasUnprunablePendingUpdates: boolean;\n hasUnverifiedHistoryTail: boolean;\n tailIds: string[];\n}> {\n const recoveredDocument = await createDocument(\n `recovery-tail-verification:${localId}`,\n );\n try {\n importSnapshot(recoveredDocument, base64ToBytes(recoverySnapshot));\n const rows = await tx\n .select({\n id: documentHistoryUpdates.id,\n origin: documentHistoryUpdates.origin,\n sourceVersionVector: documentHistoryUpdates.sourceVersionVector,\n updateData: documentHistoryUpdates.updateData,\n updateId: documentHistoryUpdates.updateId,\n })\n .from(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, DOCUMENTS_APP_KIND),\n eq(documentHistoryUpdates.localId, localId),\n ),\n );\n const pendingUpdates = rows.filter((row) => row.updateId !== null);\n const tail = rows.filter((row) => row.origin !== \"outbox\");\n const checkpointUpdateData = new Set(\n pendingUpdates.flatMap((row) =>\n row.sourceVersionVector === null ? [] : [row.updateData],\n ),\n );\n let hasUnverifiedHistoryTail = false;\n const tailIds = tail.flatMap((row) => {\n if (row.id === null) {\n // An unverifiable row identity cannot participate in the guarded\n // delete, so accepting it would leave a stale recovery redirect.\n hasUnverifiedHistoryTail = true;\n return [];\n }\n if (checkpointUpdateData.has(row.updateData)) return [row.id];\n try {\n if (\n updateMatchesDocumentHistory(\n recoveredDocument,\n base64ToBytes(row.updateData),\n )\n ) {\n return [row.id];\n }\n } catch {\n // Fall through: malformed history is not proven by the rebuild.\n }\n hasUnverifiedHistoryTail = true;\n return [];\n });\n return {\n checkpointIds: pendingUpdates.flatMap((row) =>\n row.updateId !== null && row.sourceVersionVector !== null\n ? [row.updateId]\n : [],\n ),\n hasOrdinaryPendingUpdates: pendingUpdates.some(\n (row) => row.sourceVersionVector === null,\n ),\n hasUnprunablePendingUpdates: pendingUpdates.some(\n (row) => row.id === null,\n ),\n hasUnverifiedHistoryTail,\n tailIds,\n };\n } finally {\n recoveredDocument.free();\n }\n}\n\ntype CommitDocumentMutationInput = Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n>[1];\ntype SaveClientProjection = Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n>[2];\n\nasync function applyStoredDocumentMutation(input: {\n lockedExecSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0];\n mutation: CommitDocumentMutationInput;\n saveClientProjection: SaveClientProjection;\n tx: ClientSQLiteTransactionScope;\n loadDocument: DocumentsPersistence[\"loadDocument\"];\n}) {\n const { lockedExecSql, mutation, tx } = input;\n const currentRecord = await input.loadDocument(\n lockedExecSql,\n mutation.document.id,\n );\n if (mutation.stillCurrent && !mutation.stillCurrent()) {\n return { committed: false as const, currentRecord };\n }\n if (\n !currentRecord ||\n !sameExpectedDocumentRecord(currentRecord, mutation.expectedRecord)\n ) {\n await settleMutationConflict({ currentRecord, mutation, tx });\n return { committed: false as const, currentRecord };\n }\n\n if (mutation.attachmentRemoval) {\n await applyStoredAttachmentRemoval({\n localId: mutation.document.id,\n removal: mutation.attachmentRemoval,\n tx,\n });\n }\n if (mutation.attachmentStaging) {\n await upsertStoredAttachmentStagingRows({\n createdAt: new Date().toISOString(),\n localId: mutation.document.id,\n staging: mutation.attachmentStaging,\n tx,\n });\n }\n await appendMutationHistory(lockedExecSql, mutation, tx);\n await insertPendingUpdate(tx, mutation.document.id, mutation.pendingUpdate);\n await deleteAcceptedPendingUpdates(\n tx,\n mutation.document.id,\n mutation.acceptedPendingUpdateIds,\n );\n if (mutation.clearSyncFailure) {\n await clearDocumentSyncFailureInTransaction(tx, {\n appKind: DOCUMENTS_APP_KIND,\n localId: mutation.document.id,\n });\n }\n const updatedAt = await resolveDocumentSaveTimestamp({\n document: mutation.document,\n options:\n mutation.updatedAt === undefined\n ? undefined\n : { updatedAt: mutation.updatedAt },\n tx,\n });\n if (\n !(await saveDocumentRows({ document: mutation.document, tx, updatedAt }))\n ) {\n throw new Error(\n \"Document recovery generation changed before mutation save\",\n );\n }\n await input.saveClientProjection(lockedExecSql, updatedAt);\n return { committed: true as const, updatedAt };\n}\n\nasync function runStoredDocumentMutationTransaction(input: {\n lockedExecSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0];\n mutation: CommitDocumentMutationInput;\n saveClientProjection: SaveClientProjection;\n loadDocument: DocumentsPersistence[\"loadDocument\"];\n}) {\n const outcome = await getClientSQLitePersistenceRuntime(\n input.lockedExecSql,\n ).guardedTransaction(\n (tx) => applyStoredDocumentMutation({ ...input, tx }),\n () => !input.mutation.stillCurrent || input.mutation.stillCurrent(),\n { behavior: \"immediate\" },\n );\n if (outcome.committed && outcome.result) {\n return outcome.result;\n }\n return {\n committed: false as const,\n currentRecord: await input.loadDocument(\n input.lockedExecSql,\n input.mutation.document.id,\n ),\n };\n}\n\nexport async function commitStoredDocumentMutation(\n execSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0],\n input: CommitDocumentMutationInput,\n saveClientProjection: SaveClientProjection,\n loadDocument: DocumentsPersistence[\"loadDocument\"],\n) {\n return runSerializedSqlMutation(execSql, (lockedExecSql) =>\n runStoredDocumentMutationTransaction({\n lockedExecSql,\n loadDocument,\n mutation: input,\n saveClientProjection,\n }),\n );\n}\n\nexport async function settleStoredDocumentPendingUpdates(\n execSql: Parameters<DocumentsPersistence[\"settleAcceptedPendingUpdates\"]>[0],\n input: Parameters<DocumentsPersistence[\"settleAcceptedPendingUpdates\"]>[1],\n loadDocument: DocumentsPersistence[\"loadDocument\"],\n) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const currentRecord = await loadDocument(\n lockedExecSql,\n input.expectedRecord.id,\n );\n if (\n currentRecord &&\n sameDocumentSecurityIdentity(currentRecord, input.expectedRecord) &&\n sameRecoveryGeneration(currentRecord, input.expectedRecord)\n ) {\n await deleteAcceptedPendingUpdates(\n tx,\n input.expectedRecord.id,\n input.pendingUpdateIds,\n );\n }\n return currentRecord;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"documentMutationPersistence.js","sourceRoot":"","sources":["documentMutationPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { base64ToBytes } from \"@tearleads/encoding\";\nimport {\n createDocument,\n importSnapshot,\n updateMatchesDocumentHistory,\n} from \"@tearleads/loro\";\nimport { and, eq } from \"drizzle-orm\";\nimport { DOCUMENTS_APP_KIND } from \"../../../documents/documentConstants\";\nimport { documentSyncPullContinuationsEqual } from \"../../../documents/shared/sync/pullContinuation\";\nimport { documentHistoryUpdates } from \"../../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport { runSerializedSqlMutation } from \"../../../sqlite/sqlSchema\";\nimport {\n appendDocumentHistoryUpdates,\n replaceDocumentHistoryCheckpoint,\n} from \"../records/documentHistoryPersistence\";\nimport {\n insertDocumentPendingUpdateWithHistoryInTransaction,\n settleQueuedDocumentUpdatesInTransaction,\n} from \"../records/documentPendingUpdatePersistence\";\nimport type { DocumentsPersistence, StoredDocumentRecord } from \"../types\";\nimport {\n applyStoredAttachmentRemoval,\n upsertStoredAttachmentStagingRows,\n} from \"./attachmentStagingPersistence\";\nimport {\n sameDocumentSecurityIdentity,\n sameNullableDocumentValue,\n} from \"./documentRecordIdentity\";\nimport { documentSaveTimestampFrom, saveDocumentRows } from \"./documentRows\";\nimport { loadStoredDocumentState } from \"./rowQueryPersistence\";\n\nfunction sameRecoveryGeneration(\n current: StoredDocumentRecord,\n expected: StoredDocumentRecord,\n): boolean {\n return (\n (current.recoveryGeneration ?? 0) === (expected.recoveryGeneration ?? 0)\n );\n}\n\nfunction sameExpectedDocumentRecord(\n current: StoredDocumentRecord,\n expected: StoredDocumentRecord,\n): boolean {\n return (\n sameDocumentSecurityIdentity(current, expected) &&\n sameRecoveryGeneration(current, expected) &&\n sameNullableDocumentValue(current.lastCommitLsn, expected.lastCommitLsn) &&\n current.snapshotEndVersion === expected.snapshotEndVersion &&\n sameNullableDocumentValue(\n current.pendingBaseVersion,\n expected.pendingBaseVersion,\n ) &&\n documentSyncPullContinuationsEqual(\n current.pullContinuation,\n expected.pullContinuation,\n ) &&\n Boolean(current.pullContinuationRecoveryRequired) ===\n Boolean(expected.pullContinuationRecoveryRequired) &&\n (current.documentKind ?? null) === (expected.documentKind ?? null) &&\n current.text === expected.text &&\n (current.title ?? null) === (expected.title ?? null)\n );\n}\n\nasync function deleteAcceptedPendingUpdates(\n tx: ClientSQLiteTransactionScope,\n localId: string,\n ids: readonly string[],\n): Promise<void> {\n await settleQueuedDocumentUpdatesInTransaction(\n tx,\n { appKind: DOCUMENTS_APP_KIND, localId },\n ids,\n );\n}\n\nasync function insertPendingUpdate(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n pendingUpdate: Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"pendingUpdate\"],\n): Promise<void> {\n if (!pendingUpdate) return;\n await insertDocumentPendingUpdateWithHistoryInTransaction({\n createdAt: new Date().toISOString(),\n pendingUpdate,\n scope: { appKind: DOCUMENTS_APP_KIND, localId: documentId },\n tx,\n });\n}\n\nasync function settleMutationConflict(input: {\n currentRecord: StoredDocumentRecord | null;\n mutation: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[1];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (\n input.currentRecord &&\n input.mutation.settleAcceptedPendingOnConflict &&\n sameDocumentSecurityIdentity(\n input.currentRecord,\n input.mutation.expectedRecord,\n ) &&\n sameRecoveryGeneration(input.currentRecord, input.mutation.expectedRecord)\n ) {\n await deleteAcceptedPendingUpdates(\n input.tx,\n input.mutation.document.id,\n input.mutation.acceptedPendingUpdateIds,\n );\n }\n}\n\nasync function appendMutationHistory(\n execSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0],\n input: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[1],\n tx: ClientSQLiteTransactionScope,\n): Promise<void> {\n if (input.historyCheckpoint) {\n const coveredLocalState = input.historyCheckpoint.pruneCoveredLocalState\n ? await findCoveredRecoveryLocalState(\n tx,\n input.document.id,\n input.historyCheckpoint.snapshot,\n )\n : {\n checkpointIds: [],\n hasOrdinaryPendingUpdates: false,\n hasUnprunablePendingUpdates: false,\n hasUnverifiedHistoryTail: false,\n tailIds: [],\n };\n if (\n input.historyCheckpoint.pruneCoveredLocalState &&\n (coveredLocalState.hasOrdinaryPendingUpdates ||\n coveredLocalState.hasUnprunablePendingUpdates)\n ) {\n throw new Error(\n \"Document recovery found unproven pending updates before installation\",\n );\n }\n if (\n input.historyCheckpoint.pruneCoveredLocalState &&\n coveredLocalState.hasUnverifiedHistoryTail\n ) {\n throw new Error(\n \"Document recovery found unverified history tail before installation\",\n );\n }\n // A queued rotation baseline the recovery snapshot covers is unqueued\n // first so the replacement below can delete it with the covered tail.\n await deleteAcceptedPendingUpdates(\n tx,\n input.document.id,\n coveredLocalState.checkpointIds,\n );\n const checkpointReplaced = await replaceDocumentHistoryCheckpoint(\n execSql,\n { appKind: DOCUMENTS_APP_KIND, localId: input.document.id },\n {\n ...input.historyCheckpoint,\n coveredTailIds: input.historyCheckpoint.pruneCoveredLocalState\n ? coveredLocalState.tailIds\n : input.historyCheckpoint.coveredTailIds,\n },\n );\n if (input.historyCheckpoint.pruneCoveredLocalState && !checkpointReplaced) {\n throw new Error(\n \"Document recovery checkpoint was superseded before installation\",\n );\n }\n }\n if (input.historyUpdates && input.historyUpdates.length > 0) {\n await appendDocumentHistoryUpdates(\n execSql,\n { appKind: DOCUMENTS_APP_KIND, localId: input.document.id },\n input.historyUpdates,\n input.historyUpdateOrigin ?? \"local\",\n );\n }\n}\n\nasync function findCoveredRecoveryLocalState(\n tx: ClientSQLiteTransactionScope,\n localId: string,\n recoverySnapshot: string,\n): Promise<{\n checkpointIds: string[];\n hasOrdinaryPendingUpdates: boolean;\n hasUnprunablePendingUpdates: boolean;\n hasUnverifiedHistoryTail: boolean;\n tailIds: string[];\n}> {\n const recoveredDocument = await createDocument(\n `recovery-tail-verification:${localId}`,\n );\n try {\n importSnapshot(recoveredDocument, base64ToBytes(recoverySnapshot));\n const rows = await tx\n .select({\n id: documentHistoryUpdates.id,\n origin: documentHistoryUpdates.origin,\n sourceVersionVector: documentHistoryUpdates.sourceVersionVector,\n updateData: documentHistoryUpdates.updateData,\n updateId: documentHistoryUpdates.updateId,\n })\n .from(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, DOCUMENTS_APP_KIND),\n eq(documentHistoryUpdates.localId, localId),\n ),\n );\n const pendingUpdates = rows.filter((row) => row.updateId !== null);\n const tail = rows.filter((row) => row.origin !== \"outbox\");\n const checkpointUpdateData = new Set(\n pendingUpdates.flatMap((row) =>\n row.sourceVersionVector === null ? [] : [row.updateData],\n ),\n );\n let hasUnverifiedHistoryTail = false;\n const tailIds = tail.flatMap((row) => {\n if (row.id === null) {\n // An unverifiable row identity cannot participate in the guarded\n // delete, so accepting it would leave a stale recovery redirect.\n hasUnverifiedHistoryTail = true;\n return [];\n }\n if (checkpointUpdateData.has(row.updateData)) return [row.id];\n try {\n if (\n updateMatchesDocumentHistory(\n recoveredDocument,\n base64ToBytes(row.updateData),\n )\n ) {\n return [row.id];\n }\n } catch {\n // Fall through: malformed history is not proven by the rebuild.\n }\n hasUnverifiedHistoryTail = true;\n return [];\n });\n return {\n checkpointIds: pendingUpdates.flatMap((row) =>\n row.updateId !== null && row.sourceVersionVector !== null\n ? [row.updateId]\n : [],\n ),\n hasOrdinaryPendingUpdates: pendingUpdates.some(\n (row) => row.sourceVersionVector === null,\n ),\n hasUnprunablePendingUpdates: pendingUpdates.some(\n (row) => row.id === null,\n ),\n hasUnverifiedHistoryTail,\n tailIds,\n };\n } finally {\n recoveredDocument.free();\n }\n}\n\ntype CommitDocumentMutationInput = Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n>[1];\ntype SaveClientProjection = Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n>[2];\n\nasync function applyStoredDocumentMutation(input: {\n lockedExecSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0];\n mutation: CommitDocumentMutationInput;\n saveClientProjection: SaveClientProjection;\n tx: ClientSQLiteTransactionScope;\n}) {\n const { lockedExecSql, mutation, tx } = input;\n const currentState = await loadStoredDocumentState(\n lockedExecSql,\n mutation.document.id,\n );\n const currentRecord = currentState?.record ?? null;\n if (mutation.stillCurrent && !mutation.stillCurrent()) {\n return { committed: false as const, currentRecord };\n }\n if (\n !currentRecord ||\n !sameExpectedDocumentRecord(currentRecord, mutation.expectedRecord)\n ) {\n await settleMutationConflict({ currentRecord, mutation, tx });\n return { committed: false as const, currentRecord };\n }\n\n if (mutation.attachmentRemoval) {\n await applyStoredAttachmentRemoval({\n localId: mutation.document.id,\n removal: mutation.attachmentRemoval,\n tx,\n });\n }\n if (mutation.attachmentStaging) {\n await upsertStoredAttachmentStagingRows({\n createdAt: new Date().toISOString(),\n localId: mutation.document.id,\n staging: mutation.attachmentStaging,\n tx,\n });\n }\n await appendMutationHistory(lockedExecSql, mutation, tx);\n await insertPendingUpdate(tx, mutation.document.id, mutation.pendingUpdate);\n await deleteAcceptedPendingUpdates(\n tx,\n mutation.document.id,\n mutation.acceptedPendingUpdateIds,\n );\n // Nothing above writes the document or projection rows, so the state read\n // at the top of this transaction still decides the save timestamp.\n const updatedAt =\n mutation.updatedAt ||\n documentSaveTimestampFrom(\n {\n projection: currentState?.projection ?? null,\n snapshotEndVersion: currentRecord.snapshotEndVersion,\n },\n mutation.document,\n );\n if (\n !(await saveDocumentRows({\n clearSyncFailure: mutation.clearSyncFailure === true,\n document: mutation.document,\n tx,\n updatedAt,\n }))\n ) {\n throw new Error(\n \"Document recovery generation changed before mutation save\",\n );\n }\n await input.saveClientProjection(lockedExecSql, updatedAt);\n return { committed: true as const, updatedAt };\n}\n\nasync function runStoredDocumentMutationTransaction(input: {\n lockedExecSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0];\n mutation: CommitDocumentMutationInput;\n saveClientProjection: SaveClientProjection;\n loadDocument: DocumentsPersistence[\"loadDocument\"];\n}) {\n const outcome = await getClientSQLitePersistenceRuntime(\n input.lockedExecSql,\n ).guardedTransaction(\n (tx) => applyStoredDocumentMutation({ ...input, tx }),\n () => !input.mutation.stillCurrent || input.mutation.stillCurrent(),\n { behavior: \"immediate\" },\n );\n if (outcome.committed && outcome.result) {\n return outcome.result;\n }\n return {\n committed: false as const,\n currentRecord: await input.loadDocument(\n input.lockedExecSql,\n input.mutation.document.id,\n ),\n };\n}\n\nexport async function commitStoredDocumentMutation(\n execSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0],\n input: CommitDocumentMutationInput,\n saveClientProjection: SaveClientProjection,\n loadDocument: DocumentsPersistence[\"loadDocument\"],\n) {\n return runSerializedSqlMutation(execSql, (lockedExecSql) =>\n runStoredDocumentMutationTransaction({\n lockedExecSql,\n loadDocument,\n mutation: input,\n saveClientProjection,\n }),\n );\n}\n\nexport async function settleStoredDocumentPendingUpdates(\n execSql: Parameters<DocumentsPersistence[\"settleAcceptedPendingUpdates\"]>[0],\n input: Parameters<DocumentsPersistence[\"settleAcceptedPendingUpdates\"]>[1],\n loadDocument: DocumentsPersistence[\"loadDocument\"],\n) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const currentRecord = await loadDocument(\n lockedExecSql,\n input.expectedRecord.id,\n );\n if (\n currentRecord &&\n sameDocumentSecurityIdentity(currentRecord, input.expectedRecord) &&\n sameRecoveryGeneration(currentRecord, input.expectedRecord)\n ) {\n await deleteAcceptedPendingUpdates(\n tx,\n input.expectedRecord.id,\n input.pendingUpdateIds,\n );\n }\n return currentRecord;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n"]}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { and, eq, lte } from "drizzle-orm";
|
|
1
|
+
import { and, eq, getTableColumns, lte } from "drizzle-orm";
|
|
2
2
|
import { DEFAULT_DOCUMENT_KIND, DOCUMENTS_APP_KIND, } from "../../../documents/documentConstants.js";
|
|
3
|
-
import { deriveStoredDocumentTitle } from "../../../documents/documentKinds.js";
|
|
3
|
+
import { deriveStoredDocumentTitle, } from "../../../documents/documentKinds.js";
|
|
4
4
|
import { serializeDocumentSyncPullContinuation } from "../../../documents/shared/sync/pullContinuation.js";
|
|
5
5
|
import { normalizeEffectiveAccessLevel } from "../../../keying/accessLevel.js";
|
|
6
6
|
import { documentProjection, documentProjectionText, documents, } from "../../../sqlite/schema/index.js";
|
|
7
|
+
import { sqliteInsertBatchSize } from "../../../sqlite/sqliteBindBudget.js";
|
|
7
8
|
import { getClientSQLitePersistenceRuntime, } from "../../../sqlite/sqlitePersistenceRuntime.js";
|
|
9
|
+
import { excludedValue } from "../../../sqlite/sqliteUpsert.js";
|
|
10
|
+
import { CLEARED_DOCUMENT_SYNC_FAILURE } from "../records/documentSyncFailurePersistence.js";
|
|
8
11
|
import { getProjectionDocumentKind, getProjectionText, getProjectionTitle, getProjectionUpdatedAt, } from "./documentProjectionRows.js";
|
|
9
12
|
export function getDocumentScope(localId) {
|
|
10
13
|
return {
|
|
@@ -102,7 +105,10 @@ export async function createDocumentRowsIfAbsent(input) {
|
|
|
102
105
|
}
|
|
103
106
|
export async function saveDocumentRows(input) {
|
|
104
107
|
const { document, tx, updatedAt } = input;
|
|
105
|
-
const documentRow =
|
|
108
|
+
const documentRow = {
|
|
109
|
+
...toDocumentRecordRow({ document, updatedAt }),
|
|
110
|
+
...(input.clearSyncFailure ? CLEARED_DOCUMENT_SYNC_FAILURE : {}),
|
|
111
|
+
};
|
|
106
112
|
// The recovery-generation fence rides on the upsert: a stored row from a
|
|
107
113
|
// newer recovery generation is left untouched and returns no row, so a
|
|
108
114
|
// stale writer skips the projection writes without a separate read.
|
|
@@ -121,6 +127,99 @@ export async function saveDocumentRows(input) {
|
|
|
121
127
|
await saveDocumentProjectionRows(input);
|
|
122
128
|
return true;
|
|
123
129
|
}
|
|
130
|
+
/** A `set` clause that applies each conflicting row's own proposed values. */
|
|
131
|
+
function excludedSet(columns, row) {
|
|
132
|
+
const set = {};
|
|
133
|
+
for (const key of Object.keys(row)) {
|
|
134
|
+
const column = columns[key];
|
|
135
|
+
if (!column)
|
|
136
|
+
throw new Error(`Unknown upsert column ${key}`);
|
|
137
|
+
set[key] = excludedValue(column);
|
|
138
|
+
}
|
|
139
|
+
return set;
|
|
140
|
+
}
|
|
141
|
+
function chunks(values, size) {
|
|
142
|
+
const result = [];
|
|
143
|
+
for (let offset = 0; offset < values.length; offset += size) {
|
|
144
|
+
result.push(values.slice(offset, offset + size));
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* `saveDocumentRows` for documents with DISTINCT local ids, as multi-row
|
|
150
|
+
* statements: one fenced documents upsert per chunk and row shape, then the
|
|
151
|
+
* projection and text upserts for the rows the fence let through. Returns
|
|
152
|
+
* those local ids; a row the recovery-generation fence skips returns no row,
|
|
153
|
+
* exactly as the single-row upsert does.
|
|
154
|
+
*/
|
|
155
|
+
export async function saveDocumentRowsBatch(input) {
|
|
156
|
+
const { entries, tx } = input;
|
|
157
|
+
const documentColumns = getTableColumns(documents);
|
|
158
|
+
// A row that omits an optional column leaves it untouched on update, so
|
|
159
|
+
// rows are grouped by their column shape and each shape gets its own set.
|
|
160
|
+
const shapes = new Map();
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
const row = toDocumentRecordRow(entry);
|
|
163
|
+
const shape = Object.keys(row).join(",");
|
|
164
|
+
shapes.set(shape, [...(shapes.get(shape) ?? []), row]);
|
|
165
|
+
}
|
|
166
|
+
const saved = new Set();
|
|
167
|
+
for (const rows of shapes.values()) {
|
|
168
|
+
const [first] = rows;
|
|
169
|
+
if (!first)
|
|
170
|
+
continue;
|
|
171
|
+
const set = excludedSet(documentColumns, first);
|
|
172
|
+
for (const chunk of chunks(rows, sqliteInsertBatchSize(documents))) {
|
|
173
|
+
const returned = await tx
|
|
174
|
+
.insert(documents)
|
|
175
|
+
.values(chunk)
|
|
176
|
+
.onConflictDoUpdate({
|
|
177
|
+
target: [documents.appKind, documents.localId],
|
|
178
|
+
set,
|
|
179
|
+
setWhere: lte(documents.recoveryGeneration, excludedValue(documents.recoveryGeneration)),
|
|
180
|
+
})
|
|
181
|
+
.returning({ localId: documents.localId });
|
|
182
|
+
for (const row of returned)
|
|
183
|
+
saved.add(row.localId);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const savedEntries = entries.filter((entry) => saved.has(entry.document.id));
|
|
187
|
+
const projectionRows = savedEntries.map(({ document, updatedAt }) => ({
|
|
188
|
+
localId: document.id,
|
|
189
|
+
documentId: document.documentId,
|
|
190
|
+
containerId: document.containerId,
|
|
191
|
+
documentKind: document.documentKind ?? DEFAULT_DOCUMENT_KIND,
|
|
192
|
+
title: document.title ?? deriveStoredDocumentTitle(document.text),
|
|
193
|
+
updatedAt,
|
|
194
|
+
}));
|
|
195
|
+
const [firstProjection] = projectionRows;
|
|
196
|
+
if (firstProjection) {
|
|
197
|
+
const set = excludedSet(getTableColumns(documentProjection), firstProjection);
|
|
198
|
+
for (const chunk of chunks(projectionRows, sqliteInsertBatchSize(documentProjection))) {
|
|
199
|
+
await tx
|
|
200
|
+
.insert(documentProjection)
|
|
201
|
+
.values(chunk)
|
|
202
|
+
.onConflictDoUpdate({ target: documentProjection.localId, set })
|
|
203
|
+
.run();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const textRows = savedEntries.map(({ document }) => ({
|
|
207
|
+
localId: document.id,
|
|
208
|
+
text: document.text,
|
|
209
|
+
}));
|
|
210
|
+
const [firstText] = textRows;
|
|
211
|
+
if (firstText) {
|
|
212
|
+
const set = excludedSet(getTableColumns(documentProjectionText), firstText);
|
|
213
|
+
for (const chunk of chunks(textRows, sqliteInsertBatchSize(documentProjectionText))) {
|
|
214
|
+
await tx
|
|
215
|
+
.insert(documentProjectionText)
|
|
216
|
+
.values(chunk)
|
|
217
|
+
.onConflictDoUpdate({ target: documentProjectionText.localId, set })
|
|
218
|
+
.run();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return saved;
|
|
222
|
+
}
|
|
124
223
|
function didStoredDocumentContentChange(existing, next) {
|
|
125
224
|
return (existing === null ||
|
|
126
225
|
existing.documentKind !== (next.documentKind ?? DEFAULT_DOCUMENT_KIND) ||
|
|
@@ -148,16 +247,28 @@ export async function resolveDocumentSaveTimestamp(input) {
|
|
|
148
247
|
.leftJoin(documentProjectionText, eq(documentProjectionText.localId, documentProjection.localId))
|
|
149
248
|
.where(and(eq(documents.appKind, DOCUMENTS_APP_KIND), eq(documents.localId, document.id)))
|
|
150
249
|
.limit(1);
|
|
151
|
-
|
|
152
|
-
existing.title != null &&
|
|
153
|
-
existing.updatedAt != null
|
|
250
|
+
return documentSaveTimestampFrom(existing
|
|
154
251
|
? {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
252
|
+
projection: existing.documentKind != null &&
|
|
253
|
+
existing.title != null &&
|
|
254
|
+
existing.updatedAt != null
|
|
255
|
+
? {
|
|
256
|
+
documentKind: existing.documentKind,
|
|
257
|
+
text: existing.text,
|
|
258
|
+
title: existing.title,
|
|
259
|
+
updatedAt: existing.updatedAt,
|
|
260
|
+
}
|
|
261
|
+
: null,
|
|
262
|
+
snapshotEndVersion: existing.snapshotEndVersion,
|
|
159
263
|
}
|
|
160
|
-
:
|
|
264
|
+
: null, document);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The save timestamp from rows already read in the saving transaction: the
|
|
268
|
+
* stored time survives only when the content is unchanged.
|
|
269
|
+
*/
|
|
270
|
+
export function documentSaveTimestampFrom(existing, document) {
|
|
271
|
+
const existingProjection = existing?.projection ?? undefined;
|
|
161
272
|
const now = new Date().toISOString();
|
|
162
273
|
return didStoredDocumentContentChange(existing
|
|
163
274
|
? {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentRows.js","sourceRoot":"","sources":["documentRows.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, lte } from \"drizzle-orm\";\nimport {\n DEFAULT_DOCUMENT_KIND,\n DOCUMENTS_APP_KIND,\n} from \"../../../documents/documentConstants\";\nimport { deriveStoredDocumentTitle } from \"../../../documents/documentKinds\";\nimport { serializeDocumentSyncPullContinuation } from \"../../../documents/shared/sync/pullContinuation\";\nimport { normalizeEffectiveAccessLevel } from \"../../../keying/accessLevel\";\nimport {\n documentProjection,\n documentProjectionText,\n documents,\n} from \"../../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport type { ExecSql } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentScope } from \"../records\";\nimport type { StoredDocumentRecord } from \"../types\";\nimport {\n getProjectionDocumentKind,\n getProjectionText,\n getProjectionTitle,\n getProjectionUpdatedAt,\n} from \"./documentProjectionRows\";\n\nexport function getDocumentScope(localId: string): DocumentScope {\n return {\n appKind: DOCUMENTS_APP_KIND,\n localId,\n };\n}\n\nexport async function hasDocumentRow(\n execSql: ExecSql,\n localId: string,\n): Promise<boolean> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ localId: documents.localId })\n .from(documents)\n .where(\n and(\n eq(documents.appKind, DOCUMENTS_APP_KIND),\n eq(documents.localId, localId),\n ),\n )\n .limit(1);\n return rows.length > 0;\n}\n\nfunction toDocumentRecordRow(input: {\n document: StoredDocumentRecord;\n updatedAt: string;\n}) {\n const { document, updatedAt } = input;\n return {\n appKind: DOCUMENTS_APP_KIND,\n localId: document.id,\n documentId: document.documentId,\n recoveryGeneration: document.recoveryGeneration ?? 0,\n recoveryDocumentId: document.documentId\n ? null\n : (document.recoveryDocumentId ?? null),\n snapshotEndVersion: document.snapshotEndVersion,\n accessEpoch: document.accessEpoch,\n accessStateHash: document.accessStateHash ?? null,\n effectiveAccessLevel: normalizeEffectiveAccessLevel(\n document.effectiveAccessLevel,\n ),\n lastCommitLsn: document.lastCommitLsn ?? null,\n documentManifestBundle: document.documentManifestBundle ?? null,\n contentKeyBundle: document.contentKeyBundle ?? null,\n documentKekTargets: document.documentKekTargets ?? null,\n // Only touch the outgoing-delta marker when the caller manages it. Callers\n // that leave it undefined (e.g. discovery upserts, registration bootstrap)\n // must not clobber a marker a device-first deferRemoteSync write persisted.\n ...(document.pendingBaseVersion === undefined\n ? {}\n : { pendingBaseVersion: document.pendingBaseVersion }),\n ...(document.pullContinuation === undefined\n ? {}\n : {\n pullContinuation: serializeDocumentSyncPullContinuation(\n document.pullContinuation,\n ),\n }),\n updatedAt,\n };\n}\n\nasync function saveDocumentProjectionRows(input: {\n document: StoredDocumentRecord;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<void> {\n const { document, tx, updatedAt } = input;\n const projectionRow = {\n localId: document.id,\n documentId: document.documentId,\n containerId: document.containerId,\n documentKind: document.documentKind ?? DEFAULT_DOCUMENT_KIND,\n title: document.title ?? deriveStoredDocumentTitle(document.text),\n updatedAt,\n };\n await tx\n .insert(documentProjection)\n .values(projectionRow)\n .onConflictDoUpdate({\n target: documentProjection.localId,\n set: projectionRow,\n })\n .run();\n\n const projectionTextRow = {\n localId: document.id,\n text: document.text,\n };\n await tx\n .insert(documentProjectionText)\n .values(projectionTextRow)\n .onConflictDoUpdate({\n target: documentProjectionText.localId,\n set: projectionTextRow,\n })\n .run();\n}\n\nexport async function createDocumentRowsIfAbsent(input: {\n document: StoredDocumentRecord;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<boolean> {\n const { document, tx, updatedAt } = input;\n const documentRow = toDocumentRecordRow({ document, updatedAt });\n const inserted = await tx\n .insert(documents)\n .values(documentRow)\n .onConflictDoNothing({\n target: [documents.appKind, documents.localId],\n })\n .returning({ localId: documents.localId });\n if (inserted.length === 0) {\n return false;\n }\n\n await saveDocumentProjectionRows(input);\n return true;\n}\n\nexport async function saveDocumentRows(input: {\n document: StoredDocumentRecord;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<boolean> {\n const { document, tx, updatedAt } = input;\n const documentRow = toDocumentRecordRow({ document, updatedAt });\n // The recovery-generation fence rides on the upsert: a stored row from a\n // newer recovery generation is left untouched and returns no row, so a\n // stale writer skips the projection writes without a separate read.\n const saved = await tx\n .insert(documents)\n .values(documentRow)\n .onConflictDoUpdate({\n target: [documents.appKind, documents.localId],\n set: documentRow,\n setWhere: lte(\n documents.recoveryGeneration,\n documentRow.recoveryGeneration,\n ),\n })\n .returning({ localId: documents.localId });\n if (saved.length === 0) {\n return false;\n }\n await saveDocumentProjectionRows(input);\n return true;\n}\n\nfunction didStoredDocumentContentChange(\n existing: Pick<\n StoredDocumentRecord,\n \"documentKind\" | \"snapshotEndVersion\" | \"text\" | \"title\"\n > | null,\n next: Pick<\n StoredDocumentRecord,\n \"documentKind\" | \"snapshotEndVersion\" | \"text\" | \"title\"\n >,\n): boolean {\n return (\n existing === null ||\n existing.documentKind !== (next.documentKind ?? DEFAULT_DOCUMENT_KIND) ||\n existing.snapshotEndVersion !== next.snapshotEndVersion ||\n existing.text !== next.text ||\n existing.title !== (next.title ?? deriveStoredDocumentTitle(next.text))\n );\n}\n\nexport async function resolveDocumentSaveTimestamp(input: {\n document: StoredDocumentRecord;\n options?: { updatedAt?: string } | undefined;\n tx: ClientSQLiteTransactionScope;\n}): Promise<string> {\n const { document, options, tx } = input;\n if (options?.updatedAt) {\n return options.updatedAt;\n }\n\n // One read for the stored row and its projection; the projection columns\n // are not nullable, so they are null exactly when the projection is absent.\n const [existing] = await tx\n .select({\n snapshotEndVersion: documents.snapshotEndVersion,\n documentKind: documentProjection.documentKind,\n text: documentProjectionText.text,\n title: documentProjection.title,\n updatedAt: documentProjection.updatedAt,\n })\n .from(documents)\n .leftJoin(\n documentProjection,\n eq(documentProjection.localId, documents.localId),\n )\n .leftJoin(\n documentProjectionText,\n eq(documentProjectionText.localId, documentProjection.localId),\n )\n .where(\n and(\n eq(documents.appKind, DOCUMENTS_APP_KIND),\n eq(documents.localId, document.id),\n ),\n )\n .limit(1);\n const existingProjection =\n existing?.documentKind != null &&\n existing.title != null &&\n existing.updatedAt != null\n ? {\n documentKind: existing.documentKind,\n text: existing.text,\n title: existing.title,\n updatedAt: existing.updatedAt,\n }\n : undefined;\n const now = new Date().toISOString();\n return didStoredDocumentContentChange(\n existing\n ? {\n documentKind: getProjectionDocumentKind(existingProjection),\n snapshotEndVersion: existing.snapshotEndVersion,\n text: getProjectionText(existingProjection),\n title: getProjectionTitle(existingProjection),\n }\n : null,\n document,\n )\n ? now\n : getProjectionUpdatedAt(existingProjection) || now;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"documentRows.js","sourceRoot":"","sources":["documentRows.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, getTableColumns, lte, type SQL } from \"drizzle-orm\";\nimport type { SQLiteColumn } from \"drizzle-orm/sqlite-core\";\nimport {\n DEFAULT_DOCUMENT_KIND,\n DOCUMENTS_APP_KIND,\n} from \"../../../documents/documentConstants\";\nimport {\n deriveStoredDocumentTitle,\n type StoredDocumentKind,\n} from \"../../../documents/documentKinds\";\nimport { serializeDocumentSyncPullContinuation } from \"../../../documents/shared/sync/pullContinuation\";\nimport { normalizeEffectiveAccessLevel } from \"../../../keying/accessLevel\";\nimport {\n documentProjection,\n documentProjectionText,\n documents,\n} from \"../../../sqlite/schema\";\nimport { sqliteInsertBatchSize } from \"../../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../../sqlite/sqliteUpsert\";\nimport type { ExecSql } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentScope } from \"../records\";\nimport { CLEARED_DOCUMENT_SYNC_FAILURE } from \"../records/documentSyncFailurePersistence\";\nimport type { StoredDocumentRecord } from \"../types\";\nimport {\n getProjectionDocumentKind,\n getProjectionText,\n getProjectionTitle,\n getProjectionUpdatedAt,\n} from \"./documentProjectionRows\";\n\nexport function getDocumentScope(localId: string): DocumentScope {\n return {\n appKind: DOCUMENTS_APP_KIND,\n localId,\n };\n}\n\nexport async function hasDocumentRow(\n execSql: ExecSql,\n localId: string,\n): Promise<boolean> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ localId: documents.localId })\n .from(documents)\n .where(\n and(\n eq(documents.appKind, DOCUMENTS_APP_KIND),\n eq(documents.localId, localId),\n ),\n )\n .limit(1);\n return rows.length > 0;\n}\n\nfunction toDocumentRecordRow(input: {\n document: StoredDocumentRecord;\n updatedAt: string;\n}) {\n const { document, updatedAt } = input;\n return {\n appKind: DOCUMENTS_APP_KIND,\n localId: document.id,\n documentId: document.documentId,\n recoveryGeneration: document.recoveryGeneration ?? 0,\n recoveryDocumentId: document.documentId\n ? null\n : (document.recoveryDocumentId ?? null),\n snapshotEndVersion: document.snapshotEndVersion,\n accessEpoch: document.accessEpoch,\n accessStateHash: document.accessStateHash ?? null,\n effectiveAccessLevel: normalizeEffectiveAccessLevel(\n document.effectiveAccessLevel,\n ),\n lastCommitLsn: document.lastCommitLsn ?? null,\n documentManifestBundle: document.documentManifestBundle ?? null,\n contentKeyBundle: document.contentKeyBundle ?? null,\n documentKekTargets: document.documentKekTargets ?? null,\n // Only touch the outgoing-delta marker when the caller manages it. Callers\n // that leave it undefined (e.g. discovery upserts, registration bootstrap)\n // must not clobber a marker a device-first deferRemoteSync write persisted.\n ...(document.pendingBaseVersion === undefined\n ? {}\n : { pendingBaseVersion: document.pendingBaseVersion }),\n ...(document.pullContinuation === undefined\n ? {}\n : {\n pullContinuation: serializeDocumentSyncPullContinuation(\n document.pullContinuation,\n ),\n }),\n updatedAt,\n };\n}\n\nasync function saveDocumentProjectionRows(input: {\n document: StoredDocumentRecord;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<void> {\n const { document, tx, updatedAt } = input;\n const projectionRow = {\n localId: document.id,\n documentId: document.documentId,\n containerId: document.containerId,\n documentKind: document.documentKind ?? DEFAULT_DOCUMENT_KIND,\n title: document.title ?? deriveStoredDocumentTitle(document.text),\n updatedAt,\n };\n await tx\n .insert(documentProjection)\n .values(projectionRow)\n .onConflictDoUpdate({\n target: documentProjection.localId,\n set: projectionRow,\n })\n .run();\n\n const projectionTextRow = {\n localId: document.id,\n text: document.text,\n };\n await tx\n .insert(documentProjectionText)\n .values(projectionTextRow)\n .onConflictDoUpdate({\n target: documentProjectionText.localId,\n set: projectionTextRow,\n })\n .run();\n}\n\nexport async function createDocumentRowsIfAbsent(input: {\n document: StoredDocumentRecord;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<boolean> {\n const { document, tx, updatedAt } = input;\n const documentRow = toDocumentRecordRow({ document, updatedAt });\n const inserted = await tx\n .insert(documents)\n .values(documentRow)\n .onConflictDoNothing({\n target: [documents.appKind, documents.localId],\n })\n .returning({ localId: documents.localId });\n if (inserted.length === 0) {\n return false;\n }\n\n await saveDocumentProjectionRows(input);\n return true;\n}\n\nexport async function saveDocumentRows(input: {\n /** Clears the recorded sync failure in the same guarded upsert. */\n clearSyncFailure?: boolean;\n document: StoredDocumentRecord;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<boolean> {\n const { document, tx, updatedAt } = input;\n const documentRow = {\n ...toDocumentRecordRow({ document, updatedAt }),\n ...(input.clearSyncFailure ? CLEARED_DOCUMENT_SYNC_FAILURE : {}),\n };\n // The recovery-generation fence rides on the upsert: a stored row from a\n // newer recovery generation is left untouched and returns no row, so a\n // stale writer skips the projection writes without a separate read.\n const saved = await tx\n .insert(documents)\n .values(documentRow)\n .onConflictDoUpdate({\n target: [documents.appKind, documents.localId],\n set: documentRow,\n setWhere: lte(\n documents.recoveryGeneration,\n documentRow.recoveryGeneration,\n ),\n })\n .returning({ localId: documents.localId });\n if (saved.length === 0) {\n return false;\n }\n await saveDocumentProjectionRows(input);\n return true;\n}\n\ntype UpsertRow = Record<string, unknown>;\n\n/** A `set` clause that applies each conflicting row's own proposed values. */\nfunction excludedSet(\n columns: Record<string, SQLiteColumn>,\n row: UpsertRow,\n): Record<string, SQL> {\n const set: Record<string, SQL> = {};\n for (const key of Object.keys(row)) {\n const column = columns[key];\n if (!column) throw new Error(`Unknown upsert column ${key}`);\n set[key] = excludedValue(column);\n }\n return set;\n}\n\nfunction chunks<T>(values: ReadonlyArray<T>, size: number): T[][] {\n const result: T[][] = [];\n for (let offset = 0; offset < values.length; offset += size) {\n result.push(values.slice(offset, offset + size));\n }\n return result;\n}\n\n/**\n * `saveDocumentRows` for documents with DISTINCT local ids, as multi-row\n * statements: one fenced documents upsert per chunk and row shape, then the\n * projection and text upserts for the rows the fence let through. Returns\n * those local ids; a row the recovery-generation fence skips returns no row,\n * exactly as the single-row upsert does.\n */\nexport async function saveDocumentRowsBatch(input: {\n entries: ReadonlyArray<{ document: StoredDocumentRecord; updatedAt: string }>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<Set<string>> {\n const { entries, tx } = input;\n const documentColumns: Record<string, SQLiteColumn> =\n getTableColumns(documents);\n // A row that omits an optional column leaves it untouched on update, so\n // rows are grouped by their column shape and each shape gets its own set.\n const shapes = new Map<string, ReturnType<typeof toDocumentRecordRow>[]>();\n for (const entry of entries) {\n const row = toDocumentRecordRow(entry);\n const shape = Object.keys(row).join(\",\");\n shapes.set(shape, [...(shapes.get(shape) ?? []), row]);\n }\n const saved = new Set<string>();\n for (const rows of shapes.values()) {\n const [first] = rows;\n if (!first) continue;\n const set = excludedSet(documentColumns, first);\n for (const chunk of chunks(rows, sqliteInsertBatchSize(documents))) {\n const returned = await tx\n .insert(documents)\n .values(chunk)\n .onConflictDoUpdate({\n target: [documents.appKind, documents.localId],\n set,\n setWhere: lte(\n documents.recoveryGeneration,\n excludedValue(documents.recoveryGeneration),\n ),\n })\n .returning({ localId: documents.localId });\n for (const row of returned) saved.add(row.localId);\n }\n }\n\n const savedEntries = entries.filter((entry) => saved.has(entry.document.id));\n const projectionRows = savedEntries.map(({ document, updatedAt }) => ({\n localId: document.id,\n documentId: document.documentId,\n containerId: document.containerId,\n documentKind: document.documentKind ?? DEFAULT_DOCUMENT_KIND,\n title: document.title ?? deriveStoredDocumentTitle(document.text),\n updatedAt,\n }));\n const [firstProjection] = projectionRows;\n if (firstProjection) {\n const set = excludedSet(\n getTableColumns(documentProjection),\n firstProjection,\n );\n for (const chunk of chunks(\n projectionRows,\n sqliteInsertBatchSize(documentProjection),\n )) {\n await tx\n .insert(documentProjection)\n .values(chunk)\n .onConflictDoUpdate({ target: documentProjection.localId, set })\n .run();\n }\n }\n const textRows = savedEntries.map(({ document }) => ({\n localId: document.id,\n text: document.text,\n }));\n const [firstText] = textRows;\n if (firstText) {\n const set = excludedSet(getTableColumns(documentProjectionText), firstText);\n for (const chunk of chunks(\n textRows,\n sqliteInsertBatchSize(documentProjectionText),\n )) {\n await tx\n .insert(documentProjectionText)\n .values(chunk)\n .onConflictDoUpdate({ target: documentProjectionText.localId, set })\n .run();\n }\n }\n return saved;\n}\n\nfunction didStoredDocumentContentChange(\n existing: Pick<\n StoredDocumentRecord,\n \"documentKind\" | \"snapshotEndVersion\" | \"text\" | \"title\"\n > | null,\n next: Pick<\n StoredDocumentRecord,\n \"documentKind\" | \"snapshotEndVersion\" | \"text\" | \"title\"\n >,\n): boolean {\n return (\n existing === null ||\n existing.documentKind !== (next.documentKind ?? DEFAULT_DOCUMENT_KIND) ||\n existing.snapshotEndVersion !== next.snapshotEndVersion ||\n existing.text !== next.text ||\n existing.title !== (next.title ?? deriveStoredDocumentTitle(next.text))\n );\n}\n\nexport async function resolveDocumentSaveTimestamp(input: {\n document: StoredDocumentRecord;\n options?: { updatedAt?: string } | undefined;\n tx: ClientSQLiteTransactionScope;\n}): Promise<string> {\n const { document, options, tx } = input;\n if (options?.updatedAt) {\n return options.updatedAt;\n }\n\n // One read for the stored row and its projection; the projection columns\n // are not nullable, so they are null exactly when the projection is absent.\n const [existing] = await tx\n .select({\n snapshotEndVersion: documents.snapshotEndVersion,\n documentKind: documentProjection.documentKind,\n text: documentProjectionText.text,\n title: documentProjection.title,\n updatedAt: documentProjection.updatedAt,\n })\n .from(documents)\n .leftJoin(\n documentProjection,\n eq(documentProjection.localId, documents.localId),\n )\n .leftJoin(\n documentProjectionText,\n eq(documentProjectionText.localId, documentProjection.localId),\n )\n .where(\n and(\n eq(documents.appKind, DOCUMENTS_APP_KIND),\n eq(documents.localId, document.id),\n ),\n )\n .limit(1);\n return documentSaveTimestampFrom(\n existing\n ? {\n projection:\n existing.documentKind != null &&\n existing.title != null &&\n existing.updatedAt != null\n ? {\n documentKind: existing.documentKind,\n text: existing.text,\n title: existing.title,\n updatedAt: existing.updatedAt,\n }\n : null,\n snapshotEndVersion: existing.snapshotEndVersion,\n }\n : null,\n document,\n );\n}\n\n/**\n * The save timestamp from rows already read in the saving transaction: the\n * stored time survives only when the content is unchanged.\n */\nexport function documentSaveTimestampFrom(\n existing: {\n projection: {\n documentKind: StoredDocumentKind;\n text: string | null;\n title: string;\n updatedAt: string;\n } | null;\n snapshotEndVersion: string;\n } | null,\n document: StoredDocumentRecord,\n): string {\n const existingProjection = existing?.projection ?? undefined;\n const now = new Date().toISOString();\n return didStoredDocumentContentChange(\n existing\n ? {\n documentKind: getProjectionDocumentKind(existingProjection),\n snapshotEndVersion: existing.snapshotEndVersion,\n text: getProjectionText(existingProjection),\n title: getProjectionTitle(existingProjection),\n }\n : null,\n document,\n )\n ? now\n : getProjectionUpdatedAt(existingProjection) || now;\n}\n"]}
|
|
@@ -1,11 +1,78 @@
|
|
|
1
|
-
import { desc, eq, notInArray } from "drizzle-orm";
|
|
1
|
+
import { and, desc, eq, inArray, notInArray } from "drizzle-orm";
|
|
2
2
|
import { DOCUMENTS_APP_KIND } from "../../../documents/documentConstants.js";
|
|
3
3
|
import { HIDDEN_DOCUMENT_SUMMARY_KINDS } from "../../../documents/documentSummary.js";
|
|
4
4
|
import { documentProjection, documentProjectionText, documents, } from "../../../sqlite/schema/index.js";
|
|
5
|
+
import { sqliteRowsPerStatement } from "../../../sqlite/sqliteBindBudget.js";
|
|
5
6
|
import { getClientSQLitePersistenceRuntime } from "../../../sqlite/sqlitePersistenceRuntime.js";
|
|
6
|
-
import { findLocalIdByDocumentId, loadDocumentRecord } from "../records/index.js";
|
|
7
|
+
import { documentRecordSelection, findLocalIdByDocumentId, loadDocumentRecord, mapSelectedDocumentRecord, } from "../records/index.js";
|
|
7
8
|
import { documentSummaryJoin, documentSummarySelection, getProjectionContainerId, getProjectionDocumentKind, getProjectionText, getProjectionTitle, mapDocumentSummary, } from "./documentProjectionRows.js";
|
|
8
9
|
import { getDocumentScope, hasDocumentRow } from "./documentRows.js";
|
|
10
|
+
const storedDocumentStateSelection = {
|
|
11
|
+
...documentRecordSelection,
|
|
12
|
+
projectionContainerId: documentProjection.containerId,
|
|
13
|
+
projectionDocumentKind: documentProjection.documentKind,
|
|
14
|
+
projectionText: documentProjectionText.text,
|
|
15
|
+
projectionTitle: documentProjection.title,
|
|
16
|
+
projectionUpdatedAt: documentProjection.updatedAt,
|
|
17
|
+
};
|
|
18
|
+
function storedDocumentStateQuery(execSql, localIds) {
|
|
19
|
+
const { db } = getClientSQLitePersistenceRuntime(execSql);
|
|
20
|
+
return db
|
|
21
|
+
.select(storedDocumentStateSelection)
|
|
22
|
+
.from(documents)
|
|
23
|
+
.leftJoin(documentProjection, eq(documentProjection.localId, documents.localId))
|
|
24
|
+
.leftJoin(documentProjectionText, eq(documentProjectionText.localId, documentProjection.localId))
|
|
25
|
+
.where(and(eq(documents.appKind, DOCUMENTS_APP_KIND), localIds));
|
|
26
|
+
}
|
|
27
|
+
function mapStoredDocumentState(row) {
|
|
28
|
+
const { projectionContainerId, projectionDocumentKind, projectionText, projectionTitle, projectionUpdatedAt, ...recordRow } = row;
|
|
29
|
+
// Projection columns are NOT NULL, so they are null exactly when the
|
|
30
|
+
// projection row is absent.
|
|
31
|
+
const projectionRow = projectionDocumentKind !== null &&
|
|
32
|
+
projectionTitle !== null &&
|
|
33
|
+
projectionUpdatedAt !== null
|
|
34
|
+
? {
|
|
35
|
+
containerId: projectionContainerId,
|
|
36
|
+
documentKind: projectionDocumentKind,
|
|
37
|
+
text: projectionText,
|
|
38
|
+
title: projectionTitle,
|
|
39
|
+
}
|
|
40
|
+
: undefined;
|
|
41
|
+
return {
|
|
42
|
+
projection: projectionRow && projectionUpdatedAt !== null
|
|
43
|
+
? { ...projectionRow, updatedAt: projectionUpdatedAt }
|
|
44
|
+
: null,
|
|
45
|
+
record: {
|
|
46
|
+
...mapSelectedDocumentRecord(recordRow),
|
|
47
|
+
containerId: getProjectionContainerId(projectionRow),
|
|
48
|
+
documentKind: getProjectionDocumentKind(projectionRow),
|
|
49
|
+
text: getProjectionText(projectionRow),
|
|
50
|
+
title: getProjectionTitle(projectionRow),
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The canonical record and its projection from ONE joined statement, so the
|
|
56
|
+
* pair comes from one snapshot and a save inside the same transaction can
|
|
57
|
+
* reuse the projection instead of reading it again.
|
|
58
|
+
*/
|
|
59
|
+
export async function loadStoredDocumentState(execSql, localId) {
|
|
60
|
+
const [row] = await storedDocumentStateQuery(execSql, eq(documents.localId, localId)).limit(1);
|
|
61
|
+
return row ? mapStoredDocumentState(row) : null;
|
|
62
|
+
}
|
|
63
|
+
/** `loadStoredDocumentState` for many local ids, one joined read per chunk. */
|
|
64
|
+
export async function loadStoredDocumentStates(execSql, localIds) {
|
|
65
|
+
const states = new Map();
|
|
66
|
+
const unique = Array.from(new Set(localIds));
|
|
67
|
+
// One bind for the app kind; the rest carry local ids.
|
|
68
|
+
const chunkSize = sqliteRowsPerStatement(1, 1);
|
|
69
|
+
for (let offset = 0; offset < unique.length; offset += chunkSize) {
|
|
70
|
+
const rows = await storedDocumentStateQuery(execSql, inArray(documents.localId, unique.slice(offset, offset + chunkSize)));
|
|
71
|
+
for (const row of rows)
|
|
72
|
+
states.set(row.id, mapStoredDocumentState(row));
|
|
73
|
+
}
|
|
74
|
+
return states;
|
|
75
|
+
}
|
|
9
76
|
export const documentRowQueryPersistence = {
|
|
10
77
|
async listDocuments(execSql) {
|
|
11
78
|
const { db } = getClientSQLitePersistenceRuntime(execSql);
|
|
@@ -48,31 +115,7 @@ export const documentRowQueryPersistence = {
|
|
|
48
115
|
(record.recoveryGeneration ?? 0) === expectedRecoveryGeneration);
|
|
49
116
|
},
|
|
50
117
|
async loadDocument(execSql, localId) {
|
|
51
|
-
|
|
52
|
-
const [documentRecord, projectionRows] = await Promise.all([
|
|
53
|
-
loadDocumentRecord(execSql, getDocumentScope(localId)),
|
|
54
|
-
db
|
|
55
|
-
.select({
|
|
56
|
-
documentKind: documentProjection.documentKind,
|
|
57
|
-
text: documentProjectionText.text,
|
|
58
|
-
title: documentProjection.title,
|
|
59
|
-
containerId: documentProjection.containerId,
|
|
60
|
-
})
|
|
61
|
-
.from(documentProjection)
|
|
62
|
-
.leftJoin(documentProjectionText, eq(documentProjectionText.localId, documentProjection.localId))
|
|
63
|
-
.where(eq(documentProjection.localId, localId))
|
|
64
|
-
.limit(1),
|
|
65
|
-
]);
|
|
66
|
-
if (!documentRecord) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
...documentRecord,
|
|
71
|
-
containerId: getProjectionContainerId(projectionRows[0]),
|
|
72
|
-
documentKind: getProjectionDocumentKind(projectionRows[0]),
|
|
73
|
-
text: getProjectionText(projectionRows[0]),
|
|
74
|
-
title: getProjectionTitle(projectionRows[0]),
|
|
75
|
-
};
|
|
118
|
+
return (await loadStoredDocumentState(execSql, localId))?.record ?? null;
|
|
76
119
|
},
|
|
77
120
|
async loadDocumentContainer(execSql, localId) {
|
|
78
121
|
const { db } = getClientSQLitePersistenceRuntime(execSql);
|