@tearleads/client-sdk 0.1.41 → 0.1.43
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/client/Tearleads.js +3 -0
- package/client/Tearleads.js.map +1 -1
- package/data/blobs/transferPool.js +35 -8
- package/data/blobs/transferPool.js.map +1 -1
- package/data/containers/shared/projection.js +12 -0
- package/data/containers/shared/projection.js.map +1 -1
- package/data/documents/blob/shared/types.d.ts +2 -3
- package/data/documents/blob/shared/types.js.map +1 -1
- package/data/documents/shared/events.js +0 -1
- package/data/documents/shared/events.js.map +1 -1
- package/data/keyingProjectionVerification/accessManifestCheckpointEnforcement.js +2 -0
- package/data/keyingProjectionVerification/accessManifestCheckpointEnforcement.js.map +1 -1
- package/data/keyingProjectionVerification/checkpointContext.d.ts +12 -0
- package/data/keyingProjectionVerification/checkpointContext.js +16 -0
- package/data/keyingProjectionVerification/checkpointContext.js.map +1 -1
- package/data/keyingProjectionVerification/containerDestinationVerification.js +3 -1
- package/data/keyingProjectionVerification/containerDestinationVerification.js.map +1 -1
- package/data/keyingProjectionVerification/containerProjectionVerification.js +12 -6
- package/data/keyingProjectionVerification/containerProjectionVerification.js.map +1 -1
- package/data/keyingProjectionVerification/documentProjectionVerification.js +10 -4
- package/data/keyingProjectionVerification/documentProjectionVerification.js.map +1 -1
- package/data/persistence/container-contents/containerContentsPersistence.js +6 -22
- package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js +5 -15
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerPendingUpdatePersistence.js +6 -15
- package/data/persistence/container-contents/containerPendingUpdatePersistence.js.map +1 -1
- package/data/persistence/container-contents/containerRecoveryPersistence.js +3 -4
- package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerRemovalWork.js +3 -5
- package/data/persistence/container-contents/containerRemovalWork.js.map +1 -1
- package/data/persistence/container-contents/dormantContainerMetadata.js +1 -5
- package/data/persistence/container-contents/dormantContainerMetadata.js.map +1 -1
- package/data/persistence/documents/documentsPersistence.js +4 -9
- package/data/persistence/documents/documentsPersistence.js.map +1 -1
- package/data/persistence/documents/internal/discardDocument.js +1 -2
- package/data/persistence/documents/internal/discardDocument.js.map +1 -1
- package/data/persistence/documents/internal/documentDeletionPersistence.js +1 -2
- package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
- package/data/persistence/documents/internal/documentMutationPersistence.js +17 -25
- package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
- package/data/persistence/documents/internal/orphanSideRows.js +2 -14
- package/data/persistence/documents/internal/orphanSideRows.js.map +1 -1
- package/data/persistence/documents/internal/syncQueuePersistence.js +3 -3
- package/data/persistence/documents/internal/syncQueuePersistence.js.map +1 -1
- package/data/persistence/keyingCheckpointAdvancePersistence.js +27 -8
- package/data/persistence/keyingCheckpointAdvancePersistence.js.map +1 -1
- package/data/persistence/verifiedManifestBundlePersistence.d.ts +45 -0
- package/data/persistence/verifiedManifestBundlePersistence.js +282 -0
- package/data/persistence/verifiedManifestBundlePersistence.js.map +1 -0
- package/data/sqlite/documentHistoryPersistence.d.ts +9 -3
- package/data/sqlite/documentHistoryPersistence.js +31 -8
- package/data/sqlite/documentHistoryPersistence.js.map +1 -1
- package/data/sqlite/documentPendingUpdatePersistence.d.ts +58 -11
- package/data/sqlite/documentPendingUpdatePersistence.js +137 -63
- package/data/sqlite/documentPendingUpdatePersistence.js.map +1 -1
- package/data/sqlite/documentPersistence.d.ts +1 -1
- package/data/sqlite/documentPersistence.js +1 -1
- package/data/sqlite/documentPersistence.js.map +1 -1
- package/data/sqlite/documentPersistenceTypes.d.ts +2 -2
- package/data/sqlite/documentPersistenceTypes.js.map +1 -1
- package/data/sqlite/documentRecordPersistence.js +5 -4
- package/data/sqlite/documentRecordPersistence.js.map +1 -1
- package/data/sqlite/schema.d.ts +350 -443
- package/data/sqlite/schema.js +49 -47
- package/data/sqlite/schema.js.map +1 -1
- package/data/sqlite/verifiedManifestBundleSchema.d.ts +174 -0
- package/data/sqlite/verifiedManifestBundleSchema.js +36 -0
- package/data/sqlite/verifiedManifestBundleSchema.js.map +1 -0
- package/internal/api-client/ApiClient.d.ts +17 -19
- package/internal/api-client/ApiClient.js +10 -11
- package/internal/api-client/ApiClient.js.map +1 -1
- package/internal/api-client/binaryRequestOperationTransport.d.ts +18 -0
- package/internal/api-client/index.d.ts +2 -1
- package/internal/api-client/index.js +1 -1
- package/internal/api-client/index.js.map +1 -1
- package/internal/api-client/operationTransportFactory.d.ts +6 -0
- package/internal/api-client/principalHistoryRequest.js +8 -2
- package/internal/api-client/principalHistoryRequest.js.map +1 -1
- package/internal/api-client/principalHistoryRequestContext.js +15 -5
- package/internal/api-client/principalHistoryRequestContext.js.map +1 -1
- package/internal/api-client/routes/blobs/get.d.ts +7 -0
- package/internal/api-client/routes/blobs/get.js.map +1 -1
- package/internal/api-client/routes/blobs/multipart.js +1 -11
- package/internal/api-client/routes/blobs/multipart.js.map +1 -1
- package/internal/api-client/routes/blobs/multipartUpload.d.ts +14 -0
- package/internal/api-client/routes/blobs/multipartUpload.js +94 -43
- package/internal/api-client/routes/blobs/multipartUpload.js.map +1 -1
- package/internal/api-client/routes/writerProjections.js +6 -3
- package/internal/api-client/routes/writerProjections.js.map +1 -1
- package/internal/api-client/verifiedManifestCitations.d.ts +23 -0
- package/internal/api-client/verifiedManifestCitations.js +60 -0
- package/internal/api-client/verifiedManifestCitations.js.map +1 -0
- package/internal/api-client/writerProjectionRequests.js +75 -14
- package/internal/api-client/writerProjectionRequests.js.map +1 -1
- package/internal/crypto/index.d.ts +0 -1
- package/internal/crypto/index.js +0 -1
- package/internal/crypto/index.js.map +1 -1
- package/internal/validators/knownManifestsRefinements.js +5 -0
- package/internal/validators/knownManifestsRefinements.js.map +1 -0
- package/internal/validators/operation/attachments.d.ts +80 -160
- package/internal/validators/operation/auth.d.ts +760 -1664
- package/internal/validators/operation/blobDownloads.js +1 -1
- package/internal/validators/operation/blobDownloads.js.map +1 -1
- package/internal/validators/operation/containerMutations.d.ts +416 -856
- package/internal/validators/operation/documentMutations.d.ts +120 -264
- package/internal/validators/operation/documentSync.d.ts +40 -80
- package/internal/validators/operation/index.d.ts +1 -1
- package/internal/validators/operation/index.js +1 -1
- package/internal/validators/operation/index.js.map +1 -1
- package/internal/validators/operation/multipartBlobs.d.ts +70 -190
- package/internal/validators/operation/multipartBlobs.js +2 -24
- package/internal/validators/operation/multipartBlobs.js.map +1 -1
- package/internal/validators/operation/multipartPartUploads.d.ts +5 -5
- package/internal/validators/operation/multipartPartUploads.js +6 -20
- package/internal/validators/operation/multipartPartUploads.js.map +1 -1
- package/internal/validators/operation/organizations.d.ts +882 -1906
- package/internal/validators/operation/principals.d.ts +120 -240
- package/internal/validators/operation/registry.d.ts +1 -2
- package/internal/validators/operation/registry.js +1 -2
- package/internal/validators/operation/registry.js.map +1 -1
- package/internal/validators/operation/writerProjections.d.ts +85 -319
- package/internal/validators/operation/writerProjections.js +11 -11
- package/internal/validators/operation/writerProjections.js.map +1 -1
- package/internal/validators/request/blob.d.ts +100 -201
- package/internal/validators/request/blob.js +6 -13
- package/internal/validators/request/blob.js.map +1 -1
- package/internal/validators/request/container.d.ts +136 -272
- package/internal/validators/request/container.js +16 -10
- package/internal/validators/request/container.js.map +1 -1
- package/internal/validators/request/containerManifestRef.d.ts +54 -0
- package/internal/validators/request/containerManifestRef.js +28 -0
- package/internal/validators/request/containerManifestRef.js.map +1 -0
- package/internal/validators/request/containerMetadata.d.ts +160 -368
- package/internal/validators/request/document.d.ts +100 -245
- package/internal/validators/request/document.js +1 -2
- package/internal/validators/request/document.js.map +1 -1
- package/internal/validators/request/documentSyncSchema.d.ts +42 -109
- package/internal/validators/request/documentSyncSchema.js +8 -25
- package/internal/validators/request/documentSyncSchema.js.map +1 -1
- package/internal/validators/request/index.d.ts +1 -1
- package/internal/validators/request/index.js +1 -1
- package/internal/validators/request/index.js.map +1 -1
- package/internal/validators/request/organization.d.ts +160 -320
- package/internal/validators/request/organizationProvisioning.d.ts +2424 -5312
- package/internal/validators/request/principal.d.ts +160 -320
- package/internal/validators/request/registration.d.ts +760 -1664
- package/internal/validators/response/blob.d.ts +48 -27
- package/internal/validators/response/blob.js +3 -10
- package/internal/validators/response/blob.js.map +1 -1
- package/internal/validators/response/container.d.ts +429 -20
- package/internal/validators/response/container.js +40 -5
- package/internal/validators/response/container.js.map +1 -1
- package/internal/validators/response/documentMutation.d.ts +440 -20
- package/internal/validators/response/documentMutation.js +33 -3
- package/internal/validators/response/documentMutation.js.map +1 -1
- package/internal/validators/response/index.d.ts +4 -3
- package/internal/validators/response/index.js +4 -3
- package/internal/validators/response/index.js.map +1 -1
- package/internal/validators/response/manifestBundleReferences.js +79 -0
- package/internal/validators/response/manifestBundleReferences.js.map +1 -0
- package/internal/validators/response/writerProjectionWire.d.ts +19 -0
- package/internal/validators/response/writerProjectionWire.js +132 -0
- package/internal/validators/response/writerProjectionWire.js.map +1 -0
- package/internal/validators/schema.js +20 -0
- package/internal/validators/schema.js.map +1 -1
- package/internal/validators/util/blob.d.ts +7 -0
- package/internal/validators/util/blob.js +7 -0
- package/internal/validators/util/blob.js.map +1 -1
- package/internal/validators/util/blobTransferSchemas.d.ts +41 -0
- package/internal/validators/util/blobTransferSchemas.js +39 -0
- package/internal/validators/util/blobTransferSchemas.js.map +1 -0
- package/internal/validators/util/containerLimits.d.ts +7 -0
- package/internal/validators/util/containerLimits.js +7 -0
- package/internal/validators/util/containerLimits.js.map +1 -1
- package/internal/validators/util/index.d.ts +3 -2
- package/internal/validators/util/index.js +3 -2
- package/internal/validators/util/index.js.map +1 -1
- package/internal/validators/util/knownManifestWire.d.ts +11 -0
- package/internal/validators/util/knownManifestWire.js +28 -0
- package/internal/validators/util/knownManifestWire.js.map +1 -0
- package/internal/validators/writerProjectionRefinements.js +5 -0
- package/internal/validators/writerProjectionRefinements.js.map +1 -1
- package/package.json +1 -1
- package/workflows/blobs/multipartFirstParts.js +82 -0
- package/workflows/blobs/multipartFirstParts.js.map +1 -0
- package/workflows/blobs/multipartStageResolution.js +83 -0
- package/workflows/blobs/multipartStageResolution.js.map +1 -0
- package/workflows/blobs/multipartUpload.js +45 -89
- package/workflows/blobs/multipartUpload.js.map +1 -1
- package/workflows/container-contents/documentPriming.js +2 -1
- package/workflows/container-contents/documentPriming.js.map +1 -1
- package/workflows/container-contents/documentQueries/sql.js +4 -4
- package/workflows/container-contents/documentQueries/sql.js.map +1 -1
- package/workflows/container-contents/pendingWrites/deferredTails.js +6 -3
- package/workflows/container-contents/pendingWrites/deferredTails.js.map +1 -1
- package/workflows/container-contents/pendingWrites/sources.js +2 -1
- package/workflows/container-contents/pendingWrites/sources.js.map +1 -1
- package/workflows/container-contents/pendingWrites.js +2 -1
- package/workflows/container-contents/pendingWrites.js.map +1 -1
- package/workflows/containers/child/create.js +3 -3
- package/workflows/containers/child/create.js.map +1 -1
- package/workflows/containers/child/move.js +2 -2
- package/workflows/containers/child/move.js.map +1 -1
- package/workflows/containers/child/mutationRequestCore.js +5 -4
- package/workflows/containers/child/mutationRequestCore.js.map +1 -1
- package/workflows/containers/child/recitePlan.js +3 -2
- package/workflows/containers/child/recitePlan.js.map +1 -1
- package/workflows/containers/child/sharePlanCore.js +2 -2
- package/workflows/containers/child/sharePlanCore.js.map +1 -1
- package/workflows/containers/root/create.js +2 -2
- package/workflows/containers/root/create.js.map +1 -1
- package/workflows/registration/provisioningBootstrapBuilders.d.ts +20 -51
- package/workflows/sync/remoteReset.js +11 -9
- package/workflows/sync/remoteReset.js.map +1 -1
- package/workflows/sync/remoteResetPlans.js +6 -3
- package/workflows/sync/remoteResetPlans.js.map +1 -1
- package/internal/api-client/projectionHistoryTransport.js +0 -107
- package/internal/api-client/projectionHistoryTransport.js.map +0 -1
- package/internal/api-client/verifiedProjectionHistory.d.ts +0 -4
- package/internal/api-client/verifiedProjectionHistory.js +0 -12
- package/internal/api-client/verifiedProjectionHistory.js.map +0 -1
- package/internal/crypto/projectionHistory.d.ts +0 -14
- package/internal/crypto/projectionHistory.js +0 -90
- package/internal/crypto/projectionHistory.js.map +0 -1
- package/internal/crypto/projectionHistorySlots.d.ts +0 -13
- package/internal/crypto/projectionHistorySlots.js +0 -81
- package/internal/crypto/projectionHistorySlots.js.map +0 -1
- package/internal/validators/operation/blobTransferSchemas.js +0 -11
- package/internal/validators/operation/blobTransferSchemas.js.map +0 -1
- package/internal/validators/projectionHistoryRefinements.js +0 -5
- package/internal/validators/projectionHistoryRefinements.js.map +0 -1
- package/internal/validators/util/projectionHistoryWire.d.ts +0 -23
- package/internal/validators/util/projectionHistoryWire.js +0 -33
- package/internal/validators/util/projectionHistoryWire.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifiedManifestBundlePersistence.js","sourceRoot":"","sources":["verifiedManifestBundlePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n VerifiedManifestCitations,\n WriterProjectionTarget,\n} from \"@tearleads/api-client\";\nimport { toFingerprint } from \"@tearleads/crypto\";\nimport {\n type AccessManifestBundleWireResponse,\n isAccessManifestBundleWireResponse,\n} from \"@tearleads/validators/response\";\nimport {\n KNOWN_MANIFEST_HINT_LIMIT,\n MAX_CONTAINER_PATH_LENGTH,\n} from \"@tearleads/validators/util\";\nimport { and, asc, desc, eq, inArray, sql } from \"drizzle-orm\";\nimport { containers } from \"../sqlite/containerSchema\";\nimport {\n documentDiscoveryHeads,\n pendingDocumentDiscoveries,\n} from \"../sqlite/documentDiscoveryEvidenceSchema\";\nimport {\n containerTables,\n documentContainerProjection,\n documentContainerProjectionTables,\n} from \"../sqlite/schema\";\nimport {\n type ClientSQLiteDatabase,\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../sqlite/sqlSchema\";\nimport {\n verifiedAccessManifestBundles as bundles,\n verifiedManifestBundleTables,\n} from \"../sqlite/verifiedManifestBundleSchema\";\n\n/**\n * Bounds local bytes (a bundle is ~10–20 KB). Evicting the least recently\n * verified rows only makes a later read carry those bundles again.\n */\nexport const MAX_VERIFIED_MANIFEST_BUNDLES = 1024;\nconst STATEMENT_BATCH = 200;\n\nexport interface VerifiedManifestBundle {\n readonly bundle: AccessManifestBundleWireResponse;\n readonly checkpoint: {\n readonly objectKind: string;\n readonly organizationId: string;\n readonly objectId: string;\n readonly epoch: number;\n };\n}\n\nfunction sha256Hex(text: string): Promise<string> {\n return toFingerprint(new TextEncoder().encode(text));\n}\n\nfunction batches<T>(values: readonly T[]): T[][] {\n const result: T[][] = [];\n for (let index = 0; index < values.length; index += STATEMENT_BATCH)\n result.push(values.slice(index, index + STATEMENT_BATCH));\n return result;\n}\n\nasync function pruneInTransaction(\n tx: ClientSQLiteTransactionScope,\n): Promise<void> {\n const [row] = await tx.select({ count: sql<number>`count(*)` }).from(bundles);\n const excess = (row?.count ?? 0) - MAX_VERIFIED_MANIFEST_BUNDLES;\n if (excess <= 0) return;\n const evicted = await tx\n .select({ manifestHash: bundles.manifestHash })\n .from(bundles)\n .orderBy(asc(bundles.verifiedAt), asc(bundles.manifestHash))\n .limit(excess);\n for (const batch of batches(evicted.map((entry) => entry.manifestHash)))\n await tx.delete(bundles).where(inArray(bundles.manifestHash, batch)).run();\n}\n\n/** A verified bundle's exact JSON and that JSON's digest. */\nexport type VerifiedManifestBundleRow = Omit<\n typeof bundles.$inferInsert,\n \"verifiedAt\"\n>;\n\n/**\n * Prepares rows for bundles that just passed projection verification. The\n * digests are computed here, before the checkpoint transaction that writes\n * them begins.\n */\nexport function verifiedManifestBundleRows(\n entries: readonly VerifiedManifestBundle[],\n): Promise<VerifiedManifestBundleRow[]> {\n const unique = new Map(\n entries.map((entry) => [entry.bundle.manifestHash, entry]),\n );\n return Promise.all(\n [...unique.values()].map(async ({ bundle, checkpoint }) => {\n const bundleJson = JSON.stringify(bundle);\n return {\n manifestHash: bundle.manifestHash,\n organizationId: checkpoint.organizationId,\n objectKind: checkpoint.objectKind,\n objectId: checkpoint.objectId,\n epoch: checkpoint.epoch,\n bundleJson,\n bundleDigest: await sha256Hex(bundleJson),\n };\n }),\n );\n}\n\n/**\n * Records verified bundles inside the transaction that finalizes their\n * projection's checkpoints, so they commit or roll back with it and no cache\n * work outlives the verification. Freshly verified bytes replace any stored\n * copy.\n */\nexport async function writeVerifiedManifestBundlesInTransaction(\n tx: ClientSQLiteTransactionScope,\n rows: readonly VerifiedManifestBundleRow[],\n verifiedAt: string,\n): Promise<void> {\n if (rows.length === 0) return;\n const stored = await storedDigests(\n tx,\n rows.map((row) => row.manifestHash),\n );\n // An identical stored row only needs its recency refreshed.\n const unchanged = rows.filter(\n (row) => stored.get(row.manifestHash) === row.bundleDigest,\n );\n for (const batch of batches(unchanged.map((row) => row.manifestHash)))\n await tx\n .update(bundles)\n .set({ verifiedAt })\n .where(inArray(bundles.manifestHash, batch))\n .run();\n for (const row of rows) {\n if (stored.get(row.manifestHash) === row.bundleDigest) continue;\n const verified = { ...row, verifiedAt };\n await tx\n .insert(bundles)\n .values(verified)\n .onConflictDoUpdate({ target: bundles.manifestHash, set: verified })\n .run();\n }\n await pruneInTransaction(tx);\n}\n\nasync function storedDigests(\n tx: ClientSQLiteTransactionScope,\n manifestHashes: readonly string[],\n): Promise<Map<string, string>> {\n const digests = new Map<string, string>();\n for (const batch of batches(manifestHashes)) {\n const rows = await tx\n .select({\n bundleDigest: bundles.bundleDigest,\n manifestHash: bundles.manifestHash,\n })\n .from(bundles)\n .where(inArray(bundles.manifestHash, batch));\n for (const row of rows) digests.set(row.manifestHash, row.bundleDigest);\n }\n return digests;\n}\n\n/** The requested object first, then each container whose lineage it cites. */\nasync function containerLineage(\n db: ClientSQLiteDatabase,\n containerId: string,\n): Promise<string[]> {\n const lineage = [containerId];\n while (lineage.length < MAX_CONTAINER_PATH_LENGTH) {\n const [row] = await db\n .select({ parentId: containers.parentId })\n .from(containers)\n .where(eq(containers.id, lineage.at(-1) ?? \"\"))\n .limit(1);\n const parentId = row?.parentId;\n if (!parentId || lineage.includes(parentId)) break;\n lineage.push(parentId);\n }\n return lineage;\n}\n\nasync function citedObjects(\n db: ClientSQLiteDatabase,\n target: WriterProjectionTarget,\n): Promise<{ readonly kind: string; readonly id: string }[]> {\n if (target.kind === \"container\")\n return (await containerLineage(db, target.id)).map((id) => ({\n kind: \"container\",\n id,\n }));\n // Linked containers, containers listing the document before its signed\n // placement is checked, and a container whose metadata document it is.\n const placements = [\n ...(await db\n .select({ containerId: documentContainerProjection.containerId })\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, target.id))),\n ...(await db\n .select({ containerId: pendingDocumentDiscoveries.containerId })\n .from(pendingDocumentDiscoveries)\n .where(eq(pendingDocumentDiscoveries.documentId, target.id))),\n ...(await db\n .select({ containerId: containers.id })\n .from(containers)\n .where(eq(containers.metadataDocumentId, target.id))),\n ];\n const containerIds = new Set<string>();\n for (const { containerId } of placements)\n if (containerId)\n for (const id of await containerLineage(db, containerId))\n containerIds.add(id);\n return [\n { kind: \"document\", id: target.id },\n ...[...containerIds].map((id) => ({ kind: \"container\", id })),\n ];\n}\n\n/**\n * The one organization every local source places the read in. Verified\n * ownership (stored verified bundles of the target and of the containers on\n * its walk, and a document's verified discovery head) and local placement\n * (listed container rows reached through links, pending listings, metadata,\n * or parents) must all agree: any disagreement, or no source at all, yields\n * null, so the read is sent with no hints rather than another\n * organization's.\n */\nasync function targetOrganization(\n db: ClientSQLiteDatabase,\n target: WriterProjectionTarget,\n objects: readonly { readonly kind: string; readonly id: string }[],\n): Promise<string | null> {\n const containerIds = objects\n .filter((object) => object.kind === \"container\")\n .map((object) => object.id);\n const listed =\n containerIds.length === 0\n ? []\n : await db\n .select({ organizationId: containers.organizationId })\n .from(containers)\n .where(inArray(containers.id, containerIds));\n const verifiedContainers =\n containerIds.length === 0\n ? []\n : await db\n .select({ organizationId: bundles.organizationId })\n .from(bundles)\n .where(\n and(\n eq(bundles.objectKind, \"container\"),\n inArray(bundles.objectId, containerIds),\n ),\n );\n const verifiedTarget = await db\n .select({ organizationId: bundles.organizationId })\n .from(bundles)\n .where(\n and(eq(bundles.objectKind, target.kind), eq(bundles.objectId, target.id)),\n );\n const discovered =\n target.kind === \"document\"\n ? await db\n .select({ organizationId: documentDiscoveryHeads.organizationId })\n .from(documentDiscoveryHeads)\n .where(eq(documentDiscoveryHeads.documentId, target.id))\n : [];\n const sources = [listed, verifiedContainers, verifiedTarget, discovered];\n const organizations = new Set(\n sources.flat().map((row) => row.organizationId),\n );\n const [organizationId] = organizations;\n return organizations.size === 1 && organizationId ? organizationId : null;\n}\n\ninterface StoredBundleRow {\n readonly bundleDigest: string;\n readonly bundleJson: string;\n readonly manifestHash: string;\n}\n\nconst storedColumns = {\n bundleDigest: bundles.bundleDigest,\n bundleJson: bundles.bundleJson,\n manifestHash: bundles.manifestHash,\n};\n\nasync function readStoredBundle(\n row: StoredBundleRow,\n): Promise<AccessManifestBundleWireResponse | null> {\n if ((await sha256Hex(row.bundleJson)) !== row.bundleDigest) return null;\n try {\n const bundle: unknown = JSON.parse(row.bundleJson);\n return isAccessManifestBundleWireResponse(bundle) &&\n bundle.manifestHash === row.manifestHash\n ? bundle\n : null;\n } catch {\n return null;\n }\n}\n\nfunction objectRows(\n db: ClientSQLiteDatabase,\n organizationId: string,\n object: { readonly kind: string; readonly id: string },\n limit: number,\n) {\n return db\n .select(storedColumns)\n .from(bundles)\n .where(\n and(\n eq(bundles.organizationId, organizationId),\n eq(bundles.objectKind, object.kind),\n eq(bundles.objectId, object.id),\n ),\n )\n .orderBy(desc(bundles.epoch))\n .limit(limit);\n}\n\nfunction recentRows(\n db: ClientSQLiteDatabase,\n organizationId: string,\n limit: number,\n) {\n return db\n .select(storedColumns)\n .from(bundles)\n .where(eq(bundles.organizationId, organizationId))\n .orderBy(desc(bundles.verifiedAt), asc(bundles.manifestHash))\n .limit(limit);\n}\n\n/**\n * Verified bundles of the target's object and of the containers its\n * projection walks, newest epochs first; then the most recently verified rows\n * of its organization, since a just-verified parent is the likeliest part of\n * a new child's projection. Citations never leave the target's organization:\n * an object whose organization the device cannot place cites nothing.\n * Damaged rows are dropped.\n */\nexport async function loadVerifiedManifestCitations(\n execSql: ExecSql,\n target: WriterProjectionTarget,\n limit = KNOWN_MANIFEST_HINT_LIMIT,\n): Promise<AccessManifestBundleWireResponse[]> {\n await ensureSqlTables(execSql, [\n ...verifiedManifestBundleTables,\n ...containerTables,\n ...documentContainerProjectionTables,\n ]);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const cited = new Map<string, AccessManifestBundleWireResponse>();\n const damaged: string[] = [];\n const collect = async (rows: readonly StoredBundleRow[]) => {\n for (const row of rows) {\n if (cited.size >= limit || cited.has(row.manifestHash)) continue;\n const bundle = await readStoredBundle(row);\n if (bundle) cited.set(row.manifestHash, bundle);\n else damaged.push(row.manifestHash);\n }\n };\n const objects = await citedObjects(db, target);\n const organizationId = await targetOrganization(db, target, objects);\n if (organizationId === null) return [];\n for (const object of objects)\n if (cited.size < limit)\n await collect(\n await objectRows(db, organizationId, object, limit - cited.size),\n );\n if (cited.size < limit)\n await collect(await recentRows(db, organizationId, limit + cited.size));\n if (damaged.length > 0)\n await getClientSQLitePersistenceRuntime(execSql)\n .runMutation((handle) =>\n handle\n .delete(bundles)\n .where(inArray(bundles.manifestHash, damaged))\n .run(),\n )\n .catch(() => undefined);\n return [...cited.values()];\n}\n\n/** Cites from whichever database the signed-in identity currently uses. */\nexport function createVerifiedManifestCitations(\n resolveExecSql: () => ExecSql | null,\n): VerifiedManifestCitations {\n return {\n cite: async (target) => {\n const execSql = resolveExecSql();\n return execSql ? loadVerifiedManifestCitations(execSql, target) : [];\n },\n };\n}\n"]}
|
|
@@ -34,6 +34,11 @@ export declare function appendDocumentHistoryUpdates(execSql: ExecSql, scope: Do
|
|
|
34
34
|
* silently ignored.
|
|
35
35
|
*/
|
|
36
36
|
export declare function loadDocumentHistoryRestoreState(execSql: ExecSql, scope: DocumentScope): Promise<DocumentHistoryRestoreState | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Size of the tail compaction can shrink. Queued rows are excluded: they stay
|
|
39
|
+
* until settlement, so counting them would re-export full history on every
|
|
40
|
+
* persist while offline edits wait to sync.
|
|
41
|
+
*/
|
|
37
42
|
export declare function readDocumentHistoryTailSize(execSql: ExecSql, scope: DocumentScope): Promise<{
|
|
38
43
|
byteLength: number;
|
|
39
44
|
hasCheckpoint: boolean;
|
|
@@ -44,9 +49,10 @@ interface DocumentHistoryTailEntry {
|
|
|
44
49
|
readonly updateData: string;
|
|
45
50
|
}
|
|
46
51
|
/**
|
|
47
|
-
* The current tail rows WITH their payloads, so a compactor can
|
|
48
|
-
* row's ops are covered by the snapshot it is about to install
|
|
49
|
-
* pane's append may carry ops this pane's document has not merged
|
|
52
|
+
* The current compactable tail rows WITH their payloads, so a compactor can
|
|
53
|
+
* prove each row's ops are covered by the snapshot it is about to install
|
|
54
|
+
* (another pane's append may carry ops this pane's document has not merged
|
|
55
|
+
* yet). Queued rows are never candidates.
|
|
50
56
|
*/
|
|
51
57
|
export declare function listDocumentHistoryTailEntries(execSql: ExecSql, scope: DocumentScope): Promise<DocumentHistoryTailEntry[]>;
|
|
52
58
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { base64ToBytes } from "../../internal/encoding/index.js";
|
|
2
2
|
import { createDocument, exportFullHistoryIdentity, importSnapshot, satisfiesVersionVector, } from "../../internal/loro/index.js";
|
|
3
|
-
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
3
|
+
import { and, eq, inArray, isNull, ne, sql } from "drizzle-orm";
|
|
4
4
|
import { documentHistoryCheckpoints, documentHistoryUpdates, documentTables, } from "./schema.js";
|
|
5
5
|
import { getClientSQLitePersistenceRuntime } from "./sqlitePersistenceRuntime.js";
|
|
6
6
|
import { ensureSqlTables } from "./sqlSchema.js";
|
|
@@ -13,6 +13,21 @@ import { ensureSqlTables } from "./sqlSchema.js";
|
|
|
13
13
|
*/
|
|
14
14
|
export const DOCUMENT_HISTORY_COMPACTION_MAX_ROWS = 200;
|
|
15
15
|
export const DOCUMENT_HISTORY_COMPACTION_MAX_BYTES = 512 * 1024;
|
|
16
|
+
/**
|
|
17
|
+
* Rows of the update log that are history tail: every row except an "outbox"
|
|
18
|
+
* row, which only waits to be sent (see `documentHistoryUpdates`).
|
|
19
|
+
*/
|
|
20
|
+
function tailInScope(scope) {
|
|
21
|
+
return and(eq(documentHistoryUpdates.appKind, scope.appKind), eq(documentHistoryUpdates.localId, scope.localId), ne(documentHistoryUpdates.origin, "outbox"));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Tail rows compaction may fold into a checkpoint and delete: a queued row is
|
|
25
|
+
* the only durable copy of an unsent update, so it waits for settlement even
|
|
26
|
+
* when a checkpoint already covers its operations.
|
|
27
|
+
*/
|
|
28
|
+
function compactableTailInScope(scope) {
|
|
29
|
+
return and(tailInScope(scope), isNull(documentHistoryUpdates.updateId));
|
|
30
|
+
}
|
|
16
31
|
export async function appendDocumentHistoryUpdates(execSql, scope, updates, origin) {
|
|
17
32
|
if (updates.length === 0) {
|
|
18
33
|
return;
|
|
@@ -59,7 +74,7 @@ export async function loadDocumentHistoryRestoreState(execSql, scope) {
|
|
|
59
74
|
updateData: documentHistoryUpdates.updateData,
|
|
60
75
|
})
|
|
61
76
|
.from(documentHistoryUpdates)
|
|
62
|
-
.where(
|
|
77
|
+
.where(tailInScope(scope))
|
|
63
78
|
// Insertion order: same-millisecond appends make createdAt tie and a
|
|
64
79
|
// random-uuid tiebreak would replay out of order. importUpdates tolerates
|
|
65
80
|
// out-of-order batches, but replay should still be deterministic.
|
|
@@ -83,6 +98,11 @@ export async function loadDocumentHistoryRestoreState(execSql, scope) {
|
|
|
83
98
|
})),
|
|
84
99
|
};
|
|
85
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Size of the tail compaction can shrink. Queued rows are excluded: they stay
|
|
103
|
+
* until settlement, so counting them would re-export full history on every
|
|
104
|
+
* persist while offline edits wait to sync.
|
|
105
|
+
*/
|
|
86
106
|
export async function readDocumentHistoryTailSize(execSql, scope) {
|
|
87
107
|
await ensureSqlTables(execSql, documentTables);
|
|
88
108
|
const { db } = getClientSQLitePersistenceRuntime(execSql);
|
|
@@ -92,7 +112,7 @@ export async function readDocumentHistoryTailSize(execSql, scope) {
|
|
|
92
112
|
rowCount: sql `count(*)`,
|
|
93
113
|
})
|
|
94
114
|
.from(documentHistoryUpdates)
|
|
95
|
-
.where(
|
|
115
|
+
.where(compactableTailInScope(scope));
|
|
96
116
|
const [checkpoint] = await db
|
|
97
117
|
.select({ localId: documentHistoryCheckpoints.localId })
|
|
98
118
|
.from(documentHistoryCheckpoints)
|
|
@@ -105,9 +125,10 @@ export async function readDocumentHistoryTailSize(execSql, scope) {
|
|
|
105
125
|
};
|
|
106
126
|
}
|
|
107
127
|
/**
|
|
108
|
-
* The current tail rows WITH their payloads, so a compactor can
|
|
109
|
-
* row's ops are covered by the snapshot it is about to install
|
|
110
|
-
* pane's append may carry ops this pane's document has not merged
|
|
128
|
+
* The current compactable tail rows WITH their payloads, so a compactor can
|
|
129
|
+
* prove each row's ops are covered by the snapshot it is about to install
|
|
130
|
+
* (another pane's append may carry ops this pane's document has not merged
|
|
131
|
+
* yet). Queued rows are never candidates.
|
|
111
132
|
*/
|
|
112
133
|
export async function listDocumentHistoryTailEntries(execSql, scope) {
|
|
113
134
|
await ensureSqlTables(execSql, documentTables);
|
|
@@ -118,7 +139,7 @@ export async function listDocumentHistoryTailEntries(execSql, scope) {
|
|
|
118
139
|
updateData: documentHistoryUpdates.updateData,
|
|
119
140
|
})
|
|
120
141
|
.from(documentHistoryUpdates)
|
|
121
|
-
.where(
|
|
142
|
+
.where(compactableTailInScope(scope));
|
|
122
143
|
return rows.flatMap((row) => row.id === null ? [] : [{ id: row.id, updateData: row.updateData }]);
|
|
123
144
|
}
|
|
124
145
|
const HISTORY_ROW_CHUNK = 400;
|
|
@@ -162,9 +183,11 @@ export async function replaceDocumentHistoryCheckpoint(execSql, scope, input) {
|
|
|
162
183
|
continue;
|
|
163
184
|
}
|
|
164
185
|
for (let index = 0; index < input.coveredTailIds.length; index += HISTORY_ROW_CHUNK) {
|
|
186
|
+
// Never a queued row, even if a caller names one: its operations may
|
|
187
|
+
// be in the checkpoint, but it is still the unsent update itself.
|
|
165
188
|
await tx
|
|
166
189
|
.delete(documentHistoryUpdates)
|
|
167
|
-
.where(and(
|
|
190
|
+
.where(and(compactableTailInScope(scope), inArray(documentHistoryUpdates.id, input.coveredTailIds.slice(index, index + HISTORY_ROW_CHUNK))));
|
|
168
191
|
}
|
|
169
192
|
return true;
|
|
170
193
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentHistoryPersistence.js","sourceRoot":"","sources":["documentHistoryPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { base64ToBytes } from \"@tearleads/encoding\";\nimport {\n createDocument,\n exportFullHistoryIdentity,\n importSnapshot,\n satisfiesVersionVector,\n} from \"@tearleads/loro\";\nimport { and, eq, inArray, sql } from \"drizzle-orm\";\nimport type { DocumentScope } from \"./documentPersistenceTypes\";\nimport {\n documentHistoryCheckpoints,\n documentHistoryUpdates,\n documentTables,\n} from \"./schema\";\nimport { getClientSQLitePersistenceRuntime } from \"./sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"./sqlSchema\";\n\n/**\n * Compaction thresholds: once the tail carries this many rows or bytes, the\n * next persist of a full-history-capable document exports a fresh checkpoint\n * and clears the tail. Small enough to keep restore replay short, large\n * enough that a full-history export (O(history)) happens once per burst of\n * edits instead of per keystroke.\n */\nexport const DOCUMENT_HISTORY_COMPACTION_MAX_ROWS = 200;\nexport const DOCUMENT_HISTORY_COMPACTION_MAX_BYTES = 512 * 1024;\n\n/**\n * Row provenance: \"local\" for updates authored on this device, \"remote\" for\n * decrypted pulled updates. Restores advance the outgoing-delta marker across\n * remote rows (already server-side); local rows never advance it.\n */\ntype DocumentHistoryUpdateOrigin = \"local\" | \"remote\";\n\ninterface DocumentHistoryTailUpdate {\n readonly origin: DocumentHistoryUpdateOrigin;\n readonly updateData: string;\n}\n\ninterface DocumentHistoryRestoreState {\n readonly snapshot: string;\n readonly tailUpdates: readonly DocumentHistoryTailUpdate[];\n}\n\nexport async function appendDocumentHistoryUpdates(\n execSql: ExecSql,\n scope: DocumentScope,\n updates: readonly string[],\n origin: DocumentHistoryUpdateOrigin,\n): Promise<void> {\n if (updates.length === 0) {\n return;\n }\n await ensureSqlTables(execSql, documentTables);\n const createdAt = new Date().toISOString();\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n // Chunked: a catch-up pull can carry thousands of updates, and a single\n // multi-row insert would exceed SQLite's bound-variable limit.\n for (let index = 0; index < updates.length; index += HISTORY_ROW_CHUNK) {\n await db.insert(documentHistoryUpdates).values(\n updates.slice(index, index + HISTORY_ROW_CHUNK).map((updateData) => ({\n id: crypto.randomUUID(),\n appKind: scope.appKind,\n localId: scope.localId,\n updateData,\n origin,\n createdAt,\n })),\n );\n }\n });\n}\n\n/**\n * The checkpoint + ordered tail for a scope, or null when NEITHER exists (a\n * discovered shell that has not hydrated content yet; the caller starts the\n * document empty and the remote pull supplies the content). A tail WITHOUT a\n * checkpoint — a crash between the first enqueued edit's tail append and the\n * birth-checkpoint seed — restores as tail-only (`snapshot: \"\"`): those tail\n * rows can be the only durable copy of a local edit, so they must never be\n * silently ignored.\n */\nexport async function loadDocumentHistoryRestoreState(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<DocumentHistoryRestoreState | null> {\n await ensureSqlTables(execSql, documentTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n // Read the TAIL before the (full) checkpoint: if another pane compacts\n // between the two reads, this order yields old-tail + new-checkpoint — a\n // safe superset (the new checkpoint subsumes the old tail, and replay is\n // idempotent by op identity). The reverse order could yield old-checkpoint\n // + already-emptied tail, a torn read that silently drops the compacted\n // ops for the whole session.\n const tail = await db\n .select({\n id: documentHistoryUpdates.id,\n origin: documentHistoryUpdates.origin,\n updateData: documentHistoryUpdates.updateData,\n })\n .from(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, scope.appKind),\n eq(documentHistoryUpdates.localId, scope.localId),\n ),\n )\n // Insertion order: same-millisecond appends make createdAt tie and a\n // random-uuid tiebreak would replay out of order. importUpdates tolerates\n // out-of-order batches, but replay should still be deterministic.\n .orderBy(sql`rowid`);\n\n const [checkpoint] = await db\n .select({ snapshot: documentHistoryCheckpoints.snapshot })\n .from(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n ),\n )\n .limit(1);\n if (!checkpoint && tail.length === 0) {\n return null;\n }\n\n return {\n snapshot: checkpoint?.snapshot ?? \"\",\n tailUpdates: tail.map((row) => ({\n // Fail toward re-sending: an unrecognized origin never advances the\n // marker, which is the recoverable direction (a redundant re-submit\n // is idempotent; a skipped local op would be orphaned from sync).\n origin:\n row.origin === \"remote\" ? (\"remote\" as const) : (\"local\" as const),\n updateData: row.updateData,\n })),\n };\n}\n\nexport async function readDocumentHistoryTailSize(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<{ byteLength: number; hasCheckpoint: boolean; rowCount: number }> {\n await ensureSqlTables(execSql, documentTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [totals] = await db\n .select({\n byteLength: sql<number>`coalesce(sum(length(${documentHistoryUpdates.updateData})), 0)`,\n rowCount: sql<number>`count(*)`,\n })\n .from(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, scope.appKind),\n eq(documentHistoryUpdates.localId, scope.localId),\n ),\n );\n const [checkpoint] = await db\n .select({ localId: documentHistoryCheckpoints.localId })\n .from(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n ),\n )\n .limit(1);\n return {\n byteLength: Number(totals?.byteLength ?? 0),\n hasCheckpoint: checkpoint !== undefined,\n rowCount: Number(totals?.rowCount ?? 0),\n };\n}\n\ninterface DocumentHistoryTailEntry {\n readonly id: string;\n readonly updateData: string;\n}\n\n/**\n * The current tail rows WITH their payloads, so a compactor can prove each\n * row's ops are covered by the snapshot it is about to install (another\n * pane's append may carry ops this pane's document has not merged yet).\n */\nexport async function listDocumentHistoryTailEntries(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<DocumentHistoryTailEntry[]> {\n await ensureSqlTables(execSql, documentTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n id: documentHistoryUpdates.id,\n updateData: documentHistoryUpdates.updateData,\n })\n .from(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, scope.appKind),\n eq(documentHistoryUpdates.localId, scope.localId),\n ),\n );\n return rows.flatMap((row) =>\n row.id === null ? [] : [{ id: row.id, updateData: row.updateData }],\n );\n}\n\nconst HISTORY_ROW_CHUNK = 400;\n\n/**\n * Replace the scope's checkpoint with a fresh full-history snapshot and\n * delete ONLY the tail rows the caller captured before exporting it. Every\n * append path writes its tail row after the ops are already in the live\n * document, so any row captured before the export is provably covered by\n * the snapshot — while a row appended concurrently (between export and this\n * delete) survives for the next compaction instead of being lost.\n */\nexport async function replaceDocumentHistoryCheckpoint(\n execSql: ExecSql,\n scope: DocumentScope,\n input: {\n coveredTailIds: readonly string[];\n endVersionVector: string;\n /**\n * Skip the monotonic version gate. Only for document CREATION, where any\n * existing checkpoint under this localId is the orphan of a failed prior\n * attempt (checkpoint written, record row never committed) — a retry's\n * fresh document need not dominate that orphan to replace it.\n */\n force?: boolean;\n snapshot: string;\n /**\n * Re-checked after acquiring the serialized mutation slot (like the\n * record-save path): a store reset/recreation that happened while this\n * call waited in the queue must abort the write, or a stale compactor\n * could install the OLD document's checkpoint over the new identity's.\n */\n stillCurrent?: () => boolean;\n },\n): Promise<boolean> {\n await ensureSqlTables(execSql, documentTables);\n // Sequential (non-transactional) so callers already inside a transaction\n // can nest this. Crash between the statements leaves the new checkpoint\n // plus covered tail rows — a safe superset, since tail replay is\n // idempotent by op identity. The replacement itself is a compare-and-swap\n // on the stored revision, so the version gate cannot be bypassed by a\n // concurrent compactor between the read and the write: whoever loses the\n // CAS re-reads and re-evaluates the gate against the winner's checkpoint.\n return getClientSQLitePersistenceRuntime(execSql).runMutation(async (tx) => {\n if (input.stillCurrent?.() === false) {\n return false;\n }\n for (let attempt = 0; attempt < CHECKPOINT_CAS_ATTEMPTS; attempt += 1) {\n let replaced: boolean;\n try {\n replaced = await tryReplaceCheckpointOnce(tx, scope, input);\n } catch (error) {\n if (error instanceof CheckpointGateRejected) {\n // A fresher checkpoint dominates this candidate: no write, and\n // the caller's covered rows must stay for the covering pane.\n return false;\n }\n throw error;\n }\n if (!replaced) {\n // CAS lost to a concurrent writer — re-read and re-evaluate the\n // gate against the winner's checkpoint.\n continue;\n }\n for (\n let index = 0;\n index < input.coveredTailIds.length;\n index += HISTORY_ROW_CHUNK\n ) {\n await tx\n .delete(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, scope.appKind),\n eq(documentHistoryUpdates.localId, scope.localId),\n inArray(\n documentHistoryUpdates.id,\n input.coveredTailIds.slice(index, index + HISTORY_ROW_CHUNK),\n ),\n ),\n );\n }\n return true;\n }\n // Exhaustion must not read as durable success: creation and rebuild\n // callers persist record rows assuming the checkpoint landed.\n throw new Error(\n \"Document history checkpoint replacement lost every CAS attempt\",\n );\n });\n}\n\nconst CHECKPOINT_CAS_ATTEMPTS = 16;\n\ntype MutationDb = Parameters<\n Parameters<\n ReturnType<typeof getClientSQLitePersistenceRuntime>[\"runMutation\"]\n >[0]\n>[0];\n\n/**\n * One compare-and-swap attempt. Returns true when this caller's checkpoint\n * became the stored one, false when a concurrent writer won the CAS (the\n * caller re-reads and retries), and throws CheckpointGateRejected when a\n * fresher stored checkpoint dominates the candidate.\n */\nasync function tryReplaceCheckpointOnce(\n tx: MutationDb,\n scope: DocumentScope,\n input: {\n endVersionVector: string;\n force?: boolean;\n snapshot: string;\n },\n): Promise<boolean> {\n const updatedAt = new Date().toISOString();\n const revision = crypto.randomUUID();\n const [stored] = await tx\n .select({\n endVersionVector: documentHistoryCheckpoints.endVersionVector,\n revision: documentHistoryCheckpoints.revision,\n snapshot: documentHistoryCheckpoints.snapshot,\n })\n .from(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n ),\n )\n .limit(1);\n\n if (!stored) {\n const inserted = await tx\n .insert(documentHistoryCheckpoints)\n .values({\n appKind: scope.appKind,\n localId: scope.localId,\n snapshot: input.snapshot,\n endVersionVector: input.endVersionVector,\n revision,\n updatedAt,\n })\n .onConflictDoNothing({\n target: [\n documentHistoryCheckpoints.appKind,\n documentHistoryCheckpoints.localId,\n ],\n })\n .returning({ localId: documentHistoryCheckpoints.localId });\n // Conflict means a concurrent writer inserted first — retry the gate.\n return inserted.length > 0;\n }\n\n // A stale compactor (a pane whose document has not merged another pane's\n // ops) must never replace a checkpoint it does not cover: the covered\n // pane may already have deleted its tail rows, so regressing the\n // checkpoint would leave those ops with no durable copy at all. Vector\n // dominance proves counter coverage; the retained-prefix identity also\n // proves the candidate carries the exact stored operations at those\n // counters rather than a same-frontier fork.\n if (input.force !== true) {\n if (\n !satisfiesVersionVector(input.endVersionVector, stored.endVersionVector)\n ) {\n throw new CheckpointGateRejected();\n }\n if (\n input.snapshot !== stored.snapshot &&\n !(await candidateContainsStoredCheckpointHistory({\n candidateSnapshot: input.snapshot,\n storedEndVersion: stored.endVersionVector,\n storedSnapshot: stored.snapshot,\n }))\n ) {\n throw new CheckpointGateRejected();\n }\n }\n\n const replaced = await tx\n .update(documentHistoryCheckpoints)\n .set({\n snapshot: input.snapshot,\n endVersionVector: input.endVersionVector,\n revision,\n updatedAt,\n })\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n eq(documentHistoryCheckpoints.revision, stored.revision),\n ),\n )\n .returning({ localId: documentHistoryCheckpoints.localId });\n return replaced.length > 0;\n}\n\nasync function candidateContainsStoredCheckpointHistory(input: {\n candidateSnapshot: string;\n storedEndVersion: string;\n storedSnapshot: string;\n}): Promise<boolean> {\n const [candidateDocument, storedDocument] = await Promise.all([\n createDocument(\"checkpoint-candidate-history-gate\"),\n createDocument(\"checkpoint-stored-history-gate\"),\n ]);\n try {\n importSnapshot(candidateDocument, base64ToBytes(input.candidateSnapshot));\n importSnapshot(storedDocument, base64ToBytes(input.storedSnapshot));\n return (\n exportFullHistoryIdentity(candidateDocument, input.storedEndVersion) ===\n exportFullHistoryIdentity(storedDocument)\n );\n } finally {\n candidateDocument.free();\n storedDocument.free();\n }\n}\n\nclass CheckpointGateRejected extends Error {\n constructor() {\n super(\"checkpoint gate rejected\");\n this.name = \"CheckpointGateRejected\";\n }\n}\n\nexport async function deleteDocumentHistory(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<void> {\n await ensureSqlTables(execSql, documentTables);\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (tx) => {\n await tx\n .delete(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n ),\n );\n await tx\n .delete(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, scope.appKind),\n eq(documentHistoryUpdates.localId, scope.localId),\n ),\n );\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"documentHistoryPersistence.js","sourceRoot":"","sources":["documentHistoryPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { base64ToBytes } from \"@tearleads/encoding\";\nimport {\n createDocument,\n exportFullHistoryIdentity,\n importSnapshot,\n satisfiesVersionVector,\n} from \"@tearleads/loro\";\nimport { and, eq, inArray, isNull, ne, sql } from \"drizzle-orm\";\nimport type { DocumentScope } from \"./documentPersistenceTypes\";\nimport {\n documentHistoryCheckpoints,\n documentHistoryUpdates,\n documentTables,\n} from \"./schema\";\nimport { getClientSQLitePersistenceRuntime } from \"./sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"./sqlSchema\";\n\n/**\n * Compaction thresholds: once the tail carries this many rows or bytes, the\n * next persist of a full-history-capable document exports a fresh checkpoint\n * and clears the tail. Small enough to keep restore replay short, large\n * enough that a full-history export (O(history)) happens once per burst of\n * edits instead of per keystroke.\n */\nexport const DOCUMENT_HISTORY_COMPACTION_MAX_ROWS = 200;\nexport const DOCUMENT_HISTORY_COMPACTION_MAX_BYTES = 512 * 1024;\n\n/**\n * Row provenance: \"local\" for updates authored on this device, \"remote\" for\n * decrypted pulled updates. Restores advance the outgoing-delta marker across\n * remote rows (already server-side); local rows never advance it.\n */\ntype DocumentHistoryUpdateOrigin = \"local\" | \"remote\";\n\n/**\n * Rows of the update log that are history tail: every row except an \"outbox\"\n * row, which only waits to be sent (see `documentHistoryUpdates`).\n */\nfunction tailInScope(scope: DocumentScope) {\n return and(\n eq(documentHistoryUpdates.appKind, scope.appKind),\n eq(documentHistoryUpdates.localId, scope.localId),\n ne(documentHistoryUpdates.origin, \"outbox\"),\n );\n}\n\n/**\n * Tail rows compaction may fold into a checkpoint and delete: a queued row is\n * the only durable copy of an unsent update, so it waits for settlement even\n * when a checkpoint already covers its operations.\n */\nfunction compactableTailInScope(scope: DocumentScope) {\n return and(tailInScope(scope), isNull(documentHistoryUpdates.updateId));\n}\n\ninterface DocumentHistoryTailUpdate {\n readonly origin: DocumentHistoryUpdateOrigin;\n readonly updateData: string;\n}\n\ninterface DocumentHistoryRestoreState {\n readonly snapshot: string;\n readonly tailUpdates: readonly DocumentHistoryTailUpdate[];\n}\n\nexport async function appendDocumentHistoryUpdates(\n execSql: ExecSql,\n scope: DocumentScope,\n updates: readonly string[],\n origin: DocumentHistoryUpdateOrigin,\n): Promise<void> {\n if (updates.length === 0) {\n return;\n }\n await ensureSqlTables(execSql, documentTables);\n const createdAt = new Date().toISOString();\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n // Chunked: a catch-up pull can carry thousands of updates, and a single\n // multi-row insert would exceed SQLite's bound-variable limit.\n for (let index = 0; index < updates.length; index += HISTORY_ROW_CHUNK) {\n await db.insert(documentHistoryUpdates).values(\n updates.slice(index, index + HISTORY_ROW_CHUNK).map((updateData) => ({\n id: crypto.randomUUID(),\n appKind: scope.appKind,\n localId: scope.localId,\n updateData,\n origin,\n createdAt,\n })),\n );\n }\n });\n}\n\n/**\n * The checkpoint + ordered tail for a scope, or null when NEITHER exists (a\n * discovered shell that has not hydrated content yet; the caller starts the\n * document empty and the remote pull supplies the content). A tail WITHOUT a\n * checkpoint — a crash between the first enqueued edit's tail append and the\n * birth-checkpoint seed — restores as tail-only (`snapshot: \"\"`): those tail\n * rows can be the only durable copy of a local edit, so they must never be\n * silently ignored.\n */\nexport async function loadDocumentHistoryRestoreState(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<DocumentHistoryRestoreState | null> {\n await ensureSqlTables(execSql, documentTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n // Read the TAIL before the (full) checkpoint: if another pane compacts\n // between the two reads, this order yields old-tail + new-checkpoint — a\n // safe superset (the new checkpoint subsumes the old tail, and replay is\n // idempotent by op identity). The reverse order could yield old-checkpoint\n // + already-emptied tail, a torn read that silently drops the compacted\n // ops for the whole session.\n const tail = await db\n .select({\n id: documentHistoryUpdates.id,\n origin: documentHistoryUpdates.origin,\n updateData: documentHistoryUpdates.updateData,\n })\n .from(documentHistoryUpdates)\n .where(tailInScope(scope))\n // Insertion order: same-millisecond appends make createdAt tie and a\n // random-uuid tiebreak would replay out of order. importUpdates tolerates\n // out-of-order batches, but replay should still be deterministic.\n .orderBy(sql`rowid`);\n\n const [checkpoint] = await db\n .select({ snapshot: documentHistoryCheckpoints.snapshot })\n .from(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n ),\n )\n .limit(1);\n if (!checkpoint && tail.length === 0) {\n return null;\n }\n\n return {\n snapshot: checkpoint?.snapshot ?? \"\",\n tailUpdates: tail.map((row) => ({\n // Fail toward re-sending: an unrecognized origin never advances the\n // marker, which is the recoverable direction (a redundant re-submit\n // is idempotent; a skipped local op would be orphaned from sync).\n origin:\n row.origin === \"remote\" ? (\"remote\" as const) : (\"local\" as const),\n updateData: row.updateData,\n })),\n };\n}\n\n/**\n * Size of the tail compaction can shrink. Queued rows are excluded: they stay\n * until settlement, so counting them would re-export full history on every\n * persist while offline edits wait to sync.\n */\nexport async function readDocumentHistoryTailSize(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<{ byteLength: number; hasCheckpoint: boolean; rowCount: number }> {\n await ensureSqlTables(execSql, documentTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [totals] = await db\n .select({\n byteLength: sql<number>`coalesce(sum(length(${documentHistoryUpdates.updateData})), 0)`,\n rowCount: sql<number>`count(*)`,\n })\n .from(documentHistoryUpdates)\n .where(compactableTailInScope(scope));\n const [checkpoint] = await db\n .select({ localId: documentHistoryCheckpoints.localId })\n .from(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n ),\n )\n .limit(1);\n return {\n byteLength: Number(totals?.byteLength ?? 0),\n hasCheckpoint: checkpoint !== undefined,\n rowCount: Number(totals?.rowCount ?? 0),\n };\n}\n\ninterface DocumentHistoryTailEntry {\n readonly id: string;\n readonly updateData: string;\n}\n\n/**\n * The current compactable tail rows WITH their payloads, so a compactor can\n * prove each row's ops are covered by the snapshot it is about to install\n * (another pane's append may carry ops this pane's document has not merged\n * yet). Queued rows are never candidates.\n */\nexport async function listDocumentHistoryTailEntries(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<DocumentHistoryTailEntry[]> {\n await ensureSqlTables(execSql, documentTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n id: documentHistoryUpdates.id,\n updateData: documentHistoryUpdates.updateData,\n })\n .from(documentHistoryUpdates)\n .where(compactableTailInScope(scope));\n return rows.flatMap((row) =>\n row.id === null ? [] : [{ id: row.id, updateData: row.updateData }],\n );\n}\n\nconst HISTORY_ROW_CHUNK = 400;\n\n/**\n * Replace the scope's checkpoint with a fresh full-history snapshot and\n * delete ONLY the tail rows the caller captured before exporting it. Every\n * append path writes its tail row after the ops are already in the live\n * document, so any row captured before the export is provably covered by\n * the snapshot — while a row appended concurrently (between export and this\n * delete) survives for the next compaction instead of being lost.\n */\nexport async function replaceDocumentHistoryCheckpoint(\n execSql: ExecSql,\n scope: DocumentScope,\n input: {\n coveredTailIds: readonly string[];\n endVersionVector: string;\n /**\n * Skip the monotonic version gate. Only for document CREATION, where any\n * existing checkpoint under this localId is the orphan of a failed prior\n * attempt (checkpoint written, record row never committed) — a retry's\n * fresh document need not dominate that orphan to replace it.\n */\n force?: boolean;\n snapshot: string;\n /**\n * Re-checked after acquiring the serialized mutation slot (like the\n * record-save path): a store reset/recreation that happened while this\n * call waited in the queue must abort the write, or a stale compactor\n * could install the OLD document's checkpoint over the new identity's.\n */\n stillCurrent?: () => boolean;\n },\n): Promise<boolean> {\n await ensureSqlTables(execSql, documentTables);\n // Sequential (non-transactional) so callers already inside a transaction\n // can nest this. Crash between the statements leaves the new checkpoint\n // plus covered tail rows — a safe superset, since tail replay is\n // idempotent by op identity. The replacement itself is a compare-and-swap\n // on the stored revision, so the version gate cannot be bypassed by a\n // concurrent compactor between the read and the write: whoever loses the\n // CAS re-reads and re-evaluates the gate against the winner's checkpoint.\n return getClientSQLitePersistenceRuntime(execSql).runMutation(async (tx) => {\n if (input.stillCurrent?.() === false) {\n return false;\n }\n for (let attempt = 0; attempt < CHECKPOINT_CAS_ATTEMPTS; attempt += 1) {\n let replaced: boolean;\n try {\n replaced = await tryReplaceCheckpointOnce(tx, scope, input);\n } catch (error) {\n if (error instanceof CheckpointGateRejected) {\n // A fresher checkpoint dominates this candidate: no write, and\n // the caller's covered rows must stay for the covering pane.\n return false;\n }\n throw error;\n }\n if (!replaced) {\n // CAS lost to a concurrent writer — re-read and re-evaluate the\n // gate against the winner's checkpoint.\n continue;\n }\n for (\n let index = 0;\n index < input.coveredTailIds.length;\n index += HISTORY_ROW_CHUNK\n ) {\n // Never a queued row, even if a caller names one: its operations may\n // be in the checkpoint, but it is still the unsent update itself.\n await tx\n .delete(documentHistoryUpdates)\n .where(\n and(\n compactableTailInScope(scope),\n inArray(\n documentHistoryUpdates.id,\n input.coveredTailIds.slice(index, index + HISTORY_ROW_CHUNK),\n ),\n ),\n );\n }\n return true;\n }\n // Exhaustion must not read as durable success: creation and rebuild\n // callers persist record rows assuming the checkpoint landed.\n throw new Error(\n \"Document history checkpoint replacement lost every CAS attempt\",\n );\n });\n}\n\nconst CHECKPOINT_CAS_ATTEMPTS = 16;\n\ntype MutationDb = Parameters<\n Parameters<\n ReturnType<typeof getClientSQLitePersistenceRuntime>[\"runMutation\"]\n >[0]\n>[0];\n\n/**\n * One compare-and-swap attempt. Returns true when this caller's checkpoint\n * became the stored one, false when a concurrent writer won the CAS (the\n * caller re-reads and retries), and throws CheckpointGateRejected when a\n * fresher stored checkpoint dominates the candidate.\n */\nasync function tryReplaceCheckpointOnce(\n tx: MutationDb,\n scope: DocumentScope,\n input: {\n endVersionVector: string;\n force?: boolean;\n snapshot: string;\n },\n): Promise<boolean> {\n const updatedAt = new Date().toISOString();\n const revision = crypto.randomUUID();\n const [stored] = await tx\n .select({\n endVersionVector: documentHistoryCheckpoints.endVersionVector,\n revision: documentHistoryCheckpoints.revision,\n snapshot: documentHistoryCheckpoints.snapshot,\n })\n .from(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n ),\n )\n .limit(1);\n\n if (!stored) {\n const inserted = await tx\n .insert(documentHistoryCheckpoints)\n .values({\n appKind: scope.appKind,\n localId: scope.localId,\n snapshot: input.snapshot,\n endVersionVector: input.endVersionVector,\n revision,\n updatedAt,\n })\n .onConflictDoNothing({\n target: [\n documentHistoryCheckpoints.appKind,\n documentHistoryCheckpoints.localId,\n ],\n })\n .returning({ localId: documentHistoryCheckpoints.localId });\n // Conflict means a concurrent writer inserted first — retry the gate.\n return inserted.length > 0;\n }\n\n // A stale compactor (a pane whose document has not merged another pane's\n // ops) must never replace a checkpoint it does not cover: the covered\n // pane may already have deleted its tail rows, so regressing the\n // checkpoint would leave those ops with no durable copy at all. Vector\n // dominance proves counter coverage; the retained-prefix identity also\n // proves the candidate carries the exact stored operations at those\n // counters rather than a same-frontier fork.\n if (input.force !== true) {\n if (\n !satisfiesVersionVector(input.endVersionVector, stored.endVersionVector)\n ) {\n throw new CheckpointGateRejected();\n }\n if (\n input.snapshot !== stored.snapshot &&\n !(await candidateContainsStoredCheckpointHistory({\n candidateSnapshot: input.snapshot,\n storedEndVersion: stored.endVersionVector,\n storedSnapshot: stored.snapshot,\n }))\n ) {\n throw new CheckpointGateRejected();\n }\n }\n\n const replaced = await tx\n .update(documentHistoryCheckpoints)\n .set({\n snapshot: input.snapshot,\n endVersionVector: input.endVersionVector,\n revision,\n updatedAt,\n })\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n eq(documentHistoryCheckpoints.revision, stored.revision),\n ),\n )\n .returning({ localId: documentHistoryCheckpoints.localId });\n return replaced.length > 0;\n}\n\nasync function candidateContainsStoredCheckpointHistory(input: {\n candidateSnapshot: string;\n storedEndVersion: string;\n storedSnapshot: string;\n}): Promise<boolean> {\n const [candidateDocument, storedDocument] = await Promise.all([\n createDocument(\"checkpoint-candidate-history-gate\"),\n createDocument(\"checkpoint-stored-history-gate\"),\n ]);\n try {\n importSnapshot(candidateDocument, base64ToBytes(input.candidateSnapshot));\n importSnapshot(storedDocument, base64ToBytes(input.storedSnapshot));\n return (\n exportFullHistoryIdentity(candidateDocument, input.storedEndVersion) ===\n exportFullHistoryIdentity(storedDocument)\n );\n } finally {\n candidateDocument.free();\n storedDocument.free();\n }\n}\n\nclass CheckpointGateRejected extends Error {\n constructor() {\n super(\"checkpoint gate rejected\");\n this.name = \"CheckpointGateRejected\";\n }\n}\n\nexport async function deleteDocumentHistory(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<void> {\n await ensureSqlTables(execSql, documentTables);\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (tx) => {\n await tx\n .delete(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, scope.appKind),\n eq(documentHistoryCheckpoints.localId, scope.localId),\n ),\n );\n await tx\n .delete(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, scope.appKind),\n eq(documentHistoryUpdates.localId, scope.localId),\n ),\n );\n });\n}\n"]}
|
|
@@ -1,14 +1,48 @@
|
|
|
1
1
|
import type { DocumentScope, PendingUpdateFields, PendingUpdateRecord } from "./documentPersistenceTypes.js";
|
|
2
2
|
import type { ClientSQLiteTransactionScope } from "./sqlitePersistenceRuntime.js";
|
|
3
3
|
import type { ExecSql } from "./sqlSchema.js";
|
|
4
|
+
/**
|
|
5
|
+
* Queued updates are the rows of the local update log that carry an
|
|
6
|
+
* `updateId`. A tail row (origin "local") stays in the log after settlement as
|
|
7
|
+
* history until compaction; an "outbox" row has no tail role and settlement
|
|
8
|
+
* deletes it.
|
|
9
|
+
*/
|
|
10
|
+
declare const OUTBOX_ORIGIN = "outbox";
|
|
11
|
+
type QueueStatement = Pick<ClientSQLiteTransactionScope, "delete" | "update">;
|
|
12
|
+
type QueueRead = Pick<ClientSQLiteTransactionScope, "selectDistinct">;
|
|
13
|
+
/** The ids among `localIds` (one app kind) with at least one queued update. */
|
|
14
|
+
export declare function listLocalIdsWithQueuedUpdates(db: QueueRead, appKind: string, localIds: readonly string[]): Promise<string[]>;
|
|
15
|
+
export declare function hasQueuedDocumentUpdates(db: QueueRead, scope: DocumentScope): Promise<boolean>;
|
|
4
16
|
export declare function listDocumentPendingUpdates(execSql: ExecSql, scope: DocumentScope): Promise<PendingUpdateRecord[]>;
|
|
17
|
+
/** One log row for a queued update; `origin` decides its tail role. */
|
|
18
|
+
export declare function queuedUpdateRow(input: {
|
|
19
|
+
createdAt: string;
|
|
20
|
+
origin: "local" | typeof OUTBOX_ORIGIN;
|
|
21
|
+
pendingUpdate: PendingUpdateFields;
|
|
22
|
+
scope: DocumentScope;
|
|
23
|
+
updateId: string;
|
|
24
|
+
}): {
|
|
25
|
+
id: `${string}-${string}-${string}-${string}-${string}`;
|
|
26
|
+
appKind: string;
|
|
27
|
+
localId: string;
|
|
28
|
+
updateData: string;
|
|
29
|
+
origin: "local" | "outbox";
|
|
30
|
+
createdAt: string;
|
|
31
|
+
updateId: string;
|
|
32
|
+
partialStartVersionVector: string;
|
|
33
|
+
partialEndVersionVector: string;
|
|
34
|
+
sourceVersionVector: string | null;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Queue an update that has no history-tail role: container metadata (its
|
|
38
|
+
* content is its checkpoint) and full-state re-uploads after a remote reset.
|
|
39
|
+
*/
|
|
5
40
|
export declare function enqueueDocumentPendingUpdate(execSql: ExecSql, scope: DocumentScope, pendingUpdate: PendingUpdateFields): Promise<string>;
|
|
6
41
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* as covered and never send it.
|
|
42
|
+
* Append a locally authored update to the history tail AND queue it, as one
|
|
43
|
+
* row in one transaction. A crash can therefore never leave the op restorable
|
|
44
|
+
* from history while missing from the outgoing queue, where a null-marker
|
|
45
|
+
* restore would classify it as covered and never send it.
|
|
12
46
|
*/
|
|
13
47
|
export declare function enqueueDocumentPendingUpdateWithHistory(execSql: ExecSql, scope: DocumentScope, pendingUpdate: PendingUpdateFields, options?: {
|
|
14
48
|
expectedDocumentId: string | null;
|
|
@@ -21,7 +55,7 @@ export declare function insertDocumentPendingUpdateWithHistoryInTransaction(inpu
|
|
|
21
55
|
tx: ClientSQLiteTransactionScope;
|
|
22
56
|
}): Promise<string>;
|
|
23
57
|
/**
|
|
24
|
-
* Assign a fresh id to a
|
|
58
|
+
* Assign a fresh id to a queued update, keeping every other column. A lost
|
|
25
59
|
* sync ack leaves the server holding the update under the original id with
|
|
26
60
|
* ciphertext a rebuilt submission can never reproduce (each rebuild encrypts
|
|
27
61
|
* with a fresh IV), so the id is permanently poisoned: every resubmission
|
|
@@ -32,8 +66,8 @@ export declare function insertDocumentPendingUpdateWithHistoryInTransaction(inpu
|
|
|
32
66
|
* Returns null when no row carries the id, so callers never report progress
|
|
33
67
|
* for a row that lives in some other queue (or nowhere). The UPDATE's own
|
|
34
68
|
* RETURNING row is the progress signal — a pre-flight SELECT would race a
|
|
35
|
-
* second tab sharing the SQLite file, which can settle
|
|
36
|
-
*
|
|
69
|
+
* second tab sharing the SQLite file, which can settle the row between the
|
|
70
|
+
* read and the write and turn "rekeyed" into a spurious re-arm.
|
|
37
71
|
*
|
|
38
72
|
* The persisted per-row bound is enforced here as well: a row already at
|
|
39
73
|
* MAX_PENDING_UPDATE_REKEYS is left untouched and reported as no progress.
|
|
@@ -43,7 +77,7 @@ export declare function insertDocumentPendingUpdateWithHistoryInTransaction(inpu
|
|
|
43
77
|
*/
|
|
44
78
|
export declare const MAX_PENDING_UPDATE_REKEYS = 5;
|
|
45
79
|
/**
|
|
46
|
-
* Reset the durable re-key budget for every
|
|
80
|
+
* Reset the durable re-key budget for every queued update in a scope. The
|
|
47
81
|
* cap exists to stop a server that keeps conflicting fresh ids from driving
|
|
48
82
|
* a network-speed loop — but a budget burned during an unrelated outage
|
|
49
83
|
* (e.g. yesterday's stale-bundle deadlock 409ing every submit) should not be
|
|
@@ -52,5 +86,18 @@ export declare const MAX_PENDING_UPDATE_REKEYS = 5;
|
|
|
52
86
|
*/
|
|
53
87
|
export declare function resetDocumentPendingUpdateRekeyBudget(execSql: ExecSql, scope: DocumentScope): Promise<void>;
|
|
54
88
|
export declare function rekeyDocumentPendingUpdate(execSql: ExecSql, id: string): Promise<string | null>;
|
|
55
|
-
|
|
56
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Settle queued updates: an outbox row is deleted, and a tail row stops being
|
|
91
|
+
* queued but stays as history until compaction folds it into a checkpoint.
|
|
92
|
+
*/
|
|
93
|
+
export declare function settleQueuedDocumentUpdatesInTransaction(tx: QueueStatement, scope: DocumentScope, updateIds: readonly string[]): Promise<void>;
|
|
94
|
+
/** Settle one queued update by id, whatever its scope. */
|
|
95
|
+
export declare function settleDocumentPendingUpdate(execSql: ExecSql, id: string): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Stop queueing every update in the given scopes: outbox rows are dropped,
|
|
98
|
+
* and local tail rows keep their content as history (it is already in the
|
|
99
|
+
* document).
|
|
100
|
+
*/
|
|
101
|
+
export declare function unqueueDocumentScopesInTransaction(tx: QueueStatement, scopes: readonly DocumentScope[]): Promise<void>;
|
|
102
|
+
export declare function unqueueDocumentPendingUpdates(execSql: ExecSql, scope: DocumentScope): Promise<void>;
|
|
103
|
+
export {};
|