@tearleads/client-sdk 0.1.22 → 0.1.24
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/organizations/organizationGroupHistory.js +4 -7
- package/client/organizations/organizationGroupHistory.js.map +1 -1
- package/client/organizations/organizationReadModels.js +2 -86
- package/client/organizations/organizationReadModels.js.map +1 -1
- package/client/organizations/principalMutations.js +53 -133
- package/client/organizations/principalMutations.js.map +1 -1
- package/client/principalMutationDispatch.js +2 -2
- package/client/principalMutationDispatch.js.map +1 -1
- package/data/keyingProjectionVerification/principalPolicyVerification.js +5 -5
- package/data/keyingProjectionVerification/principalPolicyVerification.js.map +1 -1
- package/data/keyingProjectionVerification/resolvedPrincipalPolicy.js +21 -7
- package/data/keyingProjectionVerification/resolvedPrincipalPolicy.js.map +1 -1
- package/data/keyingProjectionVerification/types.d.ts +1 -8
- package/data/keyingProjectionVerification/types.js +0 -9
- package/data/keyingProjectionVerification/types.js.map +1 -1
- package/data/keyingProjectionVerification.d.ts +1 -1
- package/data/keyingProjectionVerification.js.map +1 -1
- package/data/persistence/documents/documentsPersistence.js +7 -0
- package/data/persistence/documents/documentsPersistence.js.map +1 -1
- package/data/persistence/documents/orphanBlobPersistence.d.ts +13 -0
- package/data/persistence/documents/orphanBlobPersistence.js +2 -0
- package/data/persistence/documents/orphanBlobPersistence.js.map +1 -0
- package/data/persistence/documents/types.d.ts +2 -0
- package/data/persistence/documents/types.js.map +1 -1
- package/data/persistence/principalHistoryPrefixPersistence.d.ts +5 -1
- package/data/persistence/principalHistoryPrefixPersistence.js +22 -5
- package/data/persistence/principalHistoryPrefixPersistence.js.map +1 -1
- package/data/principals/principalMutationJournalShape.d.ts +1 -1
- package/data/principals/principalMutationJournalShape.js +2 -2
- package/data/principals/principalMutationJournalShape.js.map +1 -1
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/stores/documents/documentStore/attachmentHydration.js +2 -2
- package/stores/documents/documentStore/attachmentHydration.js.map +1 -1
- package/stores/documents/documentStore/attachments.js +7 -5
- package/stores/documents/documentStore/attachments.js.map +1 -1
- package/stores/documents/documentStore/discard.js +3 -2
- package/stores/documents/documentStore/discard.js.map +1 -1
- package/stores/documents/documentStore/orphanMaintenance.js +3 -5
- package/stores/documents/documentStore/orphanMaintenance.js.map +1 -1
- package/stores/documents/documentStore/persistence.js +3 -4
- package/stores/documents/documentStore/persistence.js.map +1 -1
- package/stores/documents/documentStore/syncDetachedAttachments.js +3 -2
- package/stores/documents/documentStore/syncDetachedAttachments.js.map +1 -1
- package/stores/documents/documentStore/syncRequest.js +3 -4
- package/stores/documents/documentStore/syncRequest.js.map +1 -1
- package/workflows/container-contents/container-state/groupGrantVerification.js +23 -74
- package/workflows/container-contents/container-state/groupGrantVerification.js.map +1 -1
- package/workflows/container-contents/container-state/groupShareEpoch.js +6 -23
- package/workflows/container-contents/container-state/groupShareEpoch.js.map +1 -1
- package/workflows/container-contents/container-state/remote.js +5 -3
- package/workflows/container-contents/container-state/remote.js.map +1 -1
- package/workflows/container-contents/container-state/types.js.map +1 -1
- package/workflows/container-contents/documentLinks.d.ts +1 -1
- package/workflows/container-contents/documentLinks.js.map +1 -1
- package/workflows/container-contents/documentPurge.d.ts +1 -1
- package/workflows/container-contents/documentPurge.js +4 -4
- package/workflows/container-contents/documentPurge.js.map +1 -1
- package/workflows/container-contents/metadataRemoteSync.d.ts +1 -1
- package/workflows/container-contents/metadataRemoteSync.js.map +1 -1
- package/workflows/container-contents/remoteHydration/types.d.ts +2 -2
- package/workflows/container-contents/remoteHydration/types.js.map +1 -1
- package/workflows/container-contents/remoteHydration/verifiedDestination.js +6 -7
- package/workflows/container-contents/remoteHydration/verifiedDestination.js.map +1 -1
- package/workflows/containers/child/runtimeCurrentGroupShare.js +1 -1
- package/workflows/containers/child/runtimeCurrentGroupShare.js.map +1 -1
- package/workflows/documents/orphanBlobReclaimState.js +20 -0
- package/workflows/documents/orphanBlobReclaimState.js.map +1 -0
- package/workflows/documents/orphanBlobReclaims.d.ts +3 -2
- package/workflows/documents/orphanBlobReclaims.js +49 -47
- package/workflows/documents/orphanBlobReclaims.js.map +1 -1
- package/workflows/organizations/groupMetadataRuntime.js +11 -8
- package/workflows/organizations/groupMetadataRuntime.js.map +1 -1
- package/workflows/organizations/organizationGroupNames.js +8 -55
- package/workflows/organizations/organizationGroupNames.js.map +1 -1
- package/workflows/organizations/principalOperationReceipt.js +1 -1
- package/workflows/organizations/principalOperationReceipt.js.map +1 -1
- package/workflows/organizations/runtimeGroupMetadataAuthority.js +16 -0
- package/workflows/organizations/runtimeGroupMetadataAuthority.js.map +1 -0
- package/workflows/principals/runtimePolicyWarmer.js +29 -27
- package/workflows/principals/runtimePolicyWarmer.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discard.js","sourceRoot":"","sources":["discard.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"discard.js","sourceRoot":"","sources":["discard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAA2B,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,KAAyB,EACzB,kBAA0B;IAE1B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,yEAAyE;IACzE,0EAA0E;IAC1E,wEAAwE;IACxE,2EAA2E;IAC3E,kDAAkD;IAClD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5E,IACE,CAAC,MAAM,EAAE,UAAU;QACnB,MAAM,CAAC,UAAU,KAAK,kBAAkB;QACxC,CAAC,MAAM,CAAC,WAAW,EACnB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,uEAAuE;IACvE,sEAAsE;IACtE,yEAAyE;IACzE,sEAAsE;IACtE,yBAAyB;IACzB,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC;IACjB,KAAK,CAAC,oBAAoB,IAAI,CAAC,CAAC;IAChC,2EAA2E;IAC3E,mEAAmE;IACnE,mEAAmE;IACnE,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,+DAA+D;IAC/D,MAAM,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAE9C,OAAO,kBAAkB,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;QAC1C,IAAI,KAAkE,CAAC;QACvE,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,+BAA+B,CAAC;gBAC5C,kBAAkB,EAAE,OAAO,CAAC,KAAK,CAAC,kBAAkB;gBACpD,OAAO;gBACP,kBAAkB;gBAClB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,wEAAwE;YACxE,qEAAqE;YACrE,gEAAgE;YAChE,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,0CAA0C,KAAK,CAAC,OAAO,yCAAyC,CACjG,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACrB,qEAAqE;YACrE,mEAAmE;YACnE,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,mEAAmE;QACnE,6DAA6D;QAC7D,KAAK,4BAA4B,CAAC,KAAK,CAAC,CAAC;QACzC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,iDAAiD,KAAK,CAAC,OAAO,8BAA8B,CAC7F,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { discardPersistedDocumentToShell } from \"../../../workflows/documents\";\nimport { chainIdentityWrite } from \"./identityWriteChain\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport { type DocumentStoreState, resetDocumentStore } from \"./state\";\n\n/**\n * User-initiated escape hatch for a document whose queued local writes can no\n * longer sync (e.g. an outgoing queue whose update ids conflict forever):\n * convert the locally persisted document to the freshly-discovered-share\n * shell (documentId kept, empty snapshot, queue/history/failure rows\n * dropped) so the store re-hydrates the server copy. The shell matters twice\n * over: priming's candidate scan reads the documents rows themselves, so a\n * fully deleted record would never be restored after a restart, and\n * re-initialization needs the record to hydrate instead of eagerly creating\n * a blank document. discardPersistedDocumentToShell owns the row-level\n * sequence — including refusing local-only, unlinked, and move-pending\n * documents under its serialized mutation — and converts the record in\n * place, so no step leaves the document row absent.\n *\n * The caller restarts initialization after this settles (success or not);\n * the reset store then hydrates from whatever rows the sequence left. That\n * restart must happen OUTSIDE the identity-chained task — initialization\n * chains identity writes of its own, so starting it there would deadlock.\n */\nexport async function discardDocumentStoreLocalState(\n state: DocumentStoreState,\n expectedDocumentId: string,\n): Promise<boolean> {\n const runtime = state.runtime;\n if (runtime.infra.dbStatus !== \"ready\") {\n return false;\n }\n\n // Cheap eligibility probe before disturbing the live store: an obviously\n // ineligible document (local-only, unlinked, or one whose identity is not\n // the one the caller asked to discard) returns without stopping writes.\n // The workflow re-checks under its serialized mutation, so a status change\n // between here and there is still refused safely.\n const execSql = runtime.infra.execSql;\n const record = await state.persistence.loadDocument(execSql, state.localId);\n if (\n !record?.documentId ||\n record.documentId !== expectedDocumentId ||\n !record.containerId\n ) {\n return false;\n }\n\n // Stop local writes before draining: dropping the live doc rejects new\n // mutations at queue time, and the generation bump invalidates queued\n // write tasks that have not started (and in-flight attachment passes and\n // initializations, which re-check the generation inside their writes'\n // serialized mutations).\n state.doc = null;\n state.localWriteGeneration += 1;\n // Drain OUTSIDE the identity chain. Write-chain tasks can themselves enter\n // the identity chain (the stale-heal recovery installs its rebuilt\n // document that way), so awaiting the write chain while holding an\n // identity-chain slot would deadlock: the drained task would queue behind\n // this one and each would wait on the other forever. Out here the drain is\n // a plain await; whatever the drained tasks chain onto the identity chain\n // runs before the teardown task below and is superseded by it.\n await state.writeChain.catch(() => undefined);\n\n return chainIdentityWrite(state, async () => {\n let shell: Awaited<ReturnType<typeof discardPersistedDocumentToShell>>;\n try {\n shell = await discardPersistedDocumentToShell({\n documentProjectors: runtime.infra.documentProjectors,\n execSql,\n expectedDocumentId,\n localId: state.localId,\n persistence: state.persistence,\n });\n } catch {\n // The conversion is in-place, so an interrupted sequence still leaves a\n // loadable record (old or shell). Reset so re-initialization reloads\n // whatever survived instead of running on the dropped live doc.\n resetDocumentStore(state);\n runtime.util.log(\n `Documents: discard failed for document ${state.localId}; store reset to reload persisted state`,\n );\n return false;\n }\n if (!shell.discarded) {\n // Refused under the lock (a move intent, or an identity change since\n // the probe): rows are untouched; reset so the store reloads them.\n resetDocumentStore(state);\n return false;\n }\n\n // The conversion queued the bytes its dropped rows pointed to; the\n // reclaim deletes each only once no other row references it.\n void runDocumentOrphanMaintenance(state);\n resetDocumentStore(state);\n runtime.util.log(\n `Documents: discarded local edits for document ${state.localId}; re-pulling the server copy`,\n );\n return true;\n });\n}\n"]}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/**
|
|
1
|
+
import { reclaimDocumentOrphanBlobs } from "../../../workflows/documents/index.js";
|
|
2
|
+
/** Reclaim through the adapter that owns the attachment references. */
|
|
3
3
|
export async function runDocumentOrphanMaintenance(state) {
|
|
4
|
-
|
|
5
|
-
await reclaimDocumentOrphanBlobs(state.runtime);
|
|
6
|
-
}
|
|
4
|
+
await reclaimDocumentOrphanBlobs(state.runtime, state.persistence);
|
|
7
5
|
}
|
|
8
6
|
//# sourceMappingURL=orphanMaintenance.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orphanMaintenance.js","sourceRoot":"","sources":["orphanMaintenance.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"orphanMaintenance.js","sourceRoot":"","sources":["orphanMaintenance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAG1E,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAyB;IAEzB,MAAM,0BAA0B,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;AACrE,CAAC","sourcesContent":["import { reclaimDocumentOrphanBlobs } from \"../../../workflows/documents\";\nimport type { DocumentStoreState } from \"./state\";\n\n/** Reclaim through the adapter that owns the attachment references. */\nexport async function runDocumentOrphanMaintenance(\n state: DocumentStoreState,\n): Promise<void> {\n await reclaimDocumentOrphanBlobs(state.runtime, state.persistence);\n}\n"]}
|
|
@@ -3,9 +3,10 @@ import { normalizeEffectiveAccessLevel } from "../../../data/accessLevel.js";
|
|
|
3
3
|
import { DEFAULT_DOCUMENT_KIND } from "../../../data/documents/documentConstants.js";
|
|
4
4
|
import { projectStoredDocumentState, } from "../../../data/documents/documentKinds.js";
|
|
5
5
|
import { errorMessage } from "../../../data/errorMessage.js";
|
|
6
|
-
import {
|
|
6
|
+
import { enqueuePendingDocumentUpdate, listPendingDocumentUpdates, persistDocumentState, runSerializedSqlMutation, } from "../../../workflows/documents/index.js";
|
|
7
7
|
import { maybeCompactDocumentHistory } from "./documentHistoryCompaction.js";
|
|
8
8
|
import { importDurableDocumentHistory, installDurableDocumentReload, } from "./durableDocumentReload.js";
|
|
9
|
+
import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
|
|
9
10
|
import { markDocumentStoreRemoved, setReadySnapshot, } from "./state.js";
|
|
10
11
|
import { createFreshPeerStoredDocument } from "./storedDocument.js";
|
|
11
12
|
import { isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent, } from "./syncGeneration.js";
|
|
@@ -100,9 +101,7 @@ export async function saveDocumentRecord(state, currentDoc, patch = {}, options
|
|
|
100
101
|
if (isCurrent()) {
|
|
101
102
|
markDocumentStoreRemoved(state);
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
-
void reclaimDocumentOrphanBlobs(state.runtime);
|
|
105
|
-
}
|
|
104
|
+
void runDocumentOrphanMaintenance(state);
|
|
106
105
|
return null;
|
|
107
106
|
}
|
|
108
107
|
const { record: nextRecord, updatedAt } = persistedDocumentState;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { encodeVersionVector, exportUpdatesSince } from \"@tearleads/loro\";\nimport { normalizeEffectiveAccessLevel } from \"../../../data/accessLevel\";\nimport { DEFAULT_DOCUMENT_KIND } from \"../../../data/documents/documentConstants\";\nimport {\n type DocumentProjectorRegistry,\n projectStoredDocumentState,\n} from \"../../../data/documents/documentKinds\";\nimport type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { errorMessage } from \"../../../data/errorMessage\";\n\nimport {\n type DocumentRecord,\n defaultDocumentsPersistence,\n type ExecSql,\n enqueuePendingDocumentUpdate,\n listPendingDocumentUpdates,\n type PendingUpdateRecord,\n persistDocumentState,\n reclaimDocumentOrphanBlobs,\n runSerializedSqlMutation,\n} from \"../../../workflows/documents\";\nimport { maybeCompactDocumentHistory } from \"./documentHistoryCompaction\";\nimport {\n importDurableDocumentHistory,\n installDurableDocumentReload,\n} from \"./durableDocumentReload\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n markDocumentStoreRemoved,\n type PersistedDocumentRecord,\n type SaveDocumentRecordOptions,\n setReadySnapshot,\n} from \"./state\";\nimport { createFreshPeerStoredDocument } from \"./storedDocument\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport function documentSummaryFromRecord(\n record: DocumentRecord,\n updatedAt: string,\n documentProjectors: DocumentProjectorRegistry,\n): DocumentSummary {\n return {\n accessStateHash: record.accessStateHash ?? null,\n effectiveAccessLevel: normalizeEffectiveAccessLevel(\n record.effectiveAccessLevel,\n ),\n id: record.id,\n containerId: record.containerId,\n documentKind: record.documentKind ?? DEFAULT_DOCUMENT_KIND,\n documentId: record.documentId,\n title:\n record.title ??\n projectStoredDocumentState(\n {\n documentKind: record.documentKind ?? DEFAULT_DOCUMENT_KIND,\n structuredFields: {},\n text: record.text,\n },\n documentProjectors,\n ).title,\n updatedAt,\n };\n}\n\nfunction toPersistedDocumentRecord(\n persisted: NonNullable<Awaited<ReturnType<typeof persistDocumentState>>>,\n): PersistedDocumentRecord {\n const { record, updatedAt } = persisted;\n return {\n ...(persisted.creationSuperseded\n ? { creationSuperseded: true as const }\n : {}),\n historyRestoreState: persisted.historyRestoreState,\n ...(persisted.pullContinuationSuperseded\n ? { pullContinuationSuperseded: true as const }\n : {}),\n record,\n ...(persisted.syncIdentitySuperseded\n ? { syncIdentitySuperseded: true as const }\n : {}),\n ...(updatedAt !== undefined ? { updatedAt } : {}),\n };\n}\n\n// Nullable without a generation too: an update-persist refuses (returns\n// null) when the durable row was deleted while the persist was queued — the\n// resurrect guard in persistDocumentState — so callers must stop follow-up\n// state changes on null.\nexport async function saveDocumentRecord(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n patch: Partial<DocumentRecord> = {},\n options: SaveDocumentRecordOptions = {},\n expectedGeneration?: DocumentStoreSyncGeneration,\n commitSideEffect?: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<PersistedDocumentRecord | null> {\n const isCurrent = () =>\n (!expectedGeneration ||\n isSyncGenerationCurrent(state, expectedGeneration)) &&\n options.stillCurrent?.() !== false;\n const createsPlacement = state.record === null;\n const previousDocumentId = state.record?.documentId ?? null;\n const pendingBaseVersion =\n options.pendingBaseVersionOverride === undefined\n ? state.pendingBaseVersion\n : options.pendingBaseVersionOverride;\n const persistenceInput = {\n acceptedPendingUpdateIds: options.acceptedPendingUpdateIds,\n attachmentRemoval: options.attachmentRemoval,\n attachmentStaging: options.attachmentStaging,\n clearSyncFailure: options.clearSyncFailure,\n ...(commitSideEffect ? { commitSideEffect } : {}),\n containerId: state.runtime.state.containerId,\n currentDoc,\n currentRecord: state.record,\n documentProjectors: state.runtime.infra.documentProjectors,\n execSql: state.runtime.infra.execSql,\n expectedSyncState: options.expectedSyncState,\n historyCheckpoint: options.historyCheckpoint,\n historyUpdateOrigin: options.historyUpdateOrigin,\n historyUpdates: options.historyUpdates,\n pendingUpdate: options.pendingUpdate,\n localId: state.localId,\n // Persist the durable outgoing-delta marker with every snapshot write so a\n // restart restores it (see initializeDocumentStore). A device-first\n // deferRemoteSync write leaves it BEHIND the snapshot version on purpose;\n // capturing state.pendingBaseVersion here is what lets the next edit\n // re-derive that deferred op after a restart instead of dropping it.\n patch: { ...patch, pendingBaseVersion },\n persistence: state.persistence,\n };\n const persistedDocumentState =\n expectedGeneration || options.stillCurrent\n ? await persistDocumentState({\n ...persistenceInput,\n // persistDocumentState rechecks after its own pre-save awaits and then\n // immediately claims the executor mutation queue. A reset before that\n // point aborts; a reset afterward cannot let replacement writes overtake.\n canStartDurableMutation: isCurrent,\n })\n : await persistDocumentState(persistenceInput);\n if (!persistedDocumentState) {\n // With a stale generation the teardown that invalidated it owns the\n // state. With a current (or absent) generation, the refusal means the\n // durable row was deleted by ANOTHER subsystem while this persist was\n // queued (the resurrect guard): this store is a zombie — clear it so\n // callers that ignore the result stop advancing state or scheduling\n // sync against a document that no longer exists.\n if (isCurrent()) {\n markDocumentStoreRemoved(state);\n }\n if (state.persistence === defaultDocumentsPersistence) {\n void reclaimDocumentOrphanBlobs(state.runtime);\n }\n return null;\n }\n const { record: nextRecord, updatedAt } = persistedDocumentState;\n if (!isCurrent()) {\n return null;\n }\n\n state.record = persistedDocumentState.record;\n state.pullContinuation = nextRecord.pullContinuation ?? null;\n if (persistedDocumentState.pullContinuationSuperseded) {\n state.pendingBaseVersion =\n nextRecord.pendingBaseVersion ?? nextRecord.snapshotEndVersion;\n }\n if (previousDocumentId !== nextRecord.documentId) {\n state.effects.registerDocumentIdentity(\n state.runtime.state.domainScope,\n nextRecord.id,\n nextRecord.documentId,\n );\n }\n if (updatedAt !== undefined) {\n state.effects.emitPersistedDocument(\n state.runtime.state.domainScope,\n documentSummaryFromRecord(\n nextRecord,\n updatedAt,\n state.runtime.infra.documentProjectors,\n ),\n createsPlacement ||\n Object.hasOwn(patch, \"containerId\") ||\n previousDocumentId !== nextRecord.documentId,\n );\n }\n return toPersistedDocumentRecord(persistedDocumentState);\n}\n\nasync function reloadSupersededDocumentState(\n state: DocumentStoreState,\n isCurrent: () => boolean,\n historyRestoreState: PersistedDocumentRecord[\"historyRestoreState\"],\n previousDocumentId: string | null,\n durableRecord: DocumentRecord,\n): Promise<DocumentState | null> {\n if (historyRestoreState === undefined) {\n throw new Error(\"Superseded document state omitted its durable history\");\n }\n const replacementDoc = await createFreshPeerStoredDocument();\n importDurableDocumentHistory(replacementDoc, historyRestoreState);\n if (!isCurrent()) {\n return null;\n }\n installDurableDocumentReload({\n durableRecord,\n previousDocumentId,\n preserveQueuedWritesWhenIdentityMatches: true,\n registerIdentityChange: false,\n replacementDoc,\n state,\n });\n return replacementDoc;\n}\n\nexport async function persistDocument(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n patch: Partial<DocumentRecord> = {},\n options: SaveDocumentRecordOptions = {},\n expectedGeneration?: DocumentStoreSyncGeneration,\n commitSideEffect?: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<PersistedDocumentRecord | null> {\n const isCurrent = () =>\n (!expectedGeneration ||\n isSyncGenerationCurrent(state, expectedGeneration)) &&\n options.stillCurrent?.() !== false;\n const previousDocumentId = state.record?.documentId ?? null;\n const persistedRecord = await saveDocumentRecord(\n state,\n currentDoc,\n patch,\n options,\n expectedGeneration,\n commitSideEffect,\n );\n return publishPersistedDocument(\n state,\n currentDoc,\n persistedRecord,\n previousDocumentId,\n options,\n isCurrent,\n );\n}\n\n/** Publish only after the durable claim succeeds and its live context remains. */\nexport async function publishPersistedDocument(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n persistedRecord: PersistedDocumentRecord | null,\n previousDocumentId: string | null,\n options: SaveDocumentRecordOptions,\n isCurrent: () => boolean,\n): Promise<PersistedDocumentRecord | null> {\n if (!persistedRecord) return null;\n if (!isCurrent()) return null;\n\n if (\n persistedRecord.syncIdentitySuperseded ||\n persistedRecord.pullContinuationSuperseded\n ) {\n // Reload the winning durable checkpoint/tail on every CAS loss, including\n // another pane's history or security-context changes. A rejected sync\n // candidate remains isolated from the live document and editor.\n const replacementDoc = await reloadSupersededDocumentState(\n state,\n isCurrent,\n persistedRecord.historyRestoreState,\n previousDocumentId,\n persistedRecord.record,\n );\n if (!replacementDoc) return null;\n return persistedRecord;\n }\n\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n options.preserveSnapshotText && !persistedRecord.pullContinuationSuperseded\n ? state.snapshot.text\n : persistedRecord.record.text,\n options.preserveSnapshotStructuredFields &&\n !persistedRecord.pullContinuationSuperseded\n ? state.snapshot.structuredFields\n : undefined,\n );\n // Compaction never fails the persist that triggered it: an unexpected\n // compaction error simply keeps the tail growing until a later pass\n // succeeds, and the content stays durable either way.\n try {\n await maybeCompactDocumentHistory(state, currentDoc, options.stillCurrent);\n } catch (error) {\n state.runtime.util.log(\n `Documents: history compaction skipped: ${errorMessage(error)}`,\n );\n }\n return persistedRecord;\n}\n\nexport async function listPendingUpdates(\n state: DocumentStoreState,\n): Promise<PendingUpdateRecord[]> {\n return listPendingDocumentUpdates({\n execSql: state.runtime.infra.execSql,\n localId: state.localId,\n persistence: state.persistence,\n });\n}\n\n/**\n * Export the outgoing delta for the current edit. The base is `pendingBaseVersion`\n * (the version through which ops are already durably enqueued or synced), NOT the\n * live doc version, so any delta orphaned by a prior failed enqueue is folded back\n * in here. In the success path the marker equals the pre-edit version, so this is\n * identical to exporting \"since the last edit\".\n *\n * Initialization sets `pendingBaseVersion` whenever it sets `doc`, and every\n * caller checks `doc` first, so the marker is non-null here. Assert that rather\n * than falling back to the live version: callers invoke this AFTER mutating the\n * doc, so a live-version fallback would export an empty (post-mutation) delta and\n * silently drop the edit.\n */\nexport function pendingDeltaSinceBase(\n state: DocumentStoreState,\n doc: DocumentState,\n): Uint8Array {\n if (state.pendingBaseVersion === null) {\n throw new Error(\n \"pendingDeltaSinceBase requires an initialized pendingBaseVersion\",\n );\n }\n return exportUpdatesSince(doc, state.pendingBaseVersion);\n}\n\n/**\n * Advance the durable marker to the doc's current version. Call this ONLY after\n * an edit's delta has been durably enqueued and persisted, or after remote\n * updates have been imported (those ops are already on the server).\n */\nexport function advancePendingBaseVersion(\n state: DocumentStoreState,\n doc: DocumentState,\n): void {\n state.pendingBaseVersion = encodeVersionVector(doc);\n}\n\n/**\n * Runs a durable write either ungated — no generation: the caller serializes\n * with teardown via the write-chain drain, and the persistence implementation\n * may legitimately park inside the write WITHOUT holding the global mutation\n * mutex — or inside the serialized mutation with an in-mutex currency check:\n * a teardown (reset/discard) that already ran was ordered strictly before the\n * mutation claim and invalidated the generation, so a stale caller can no\n * longer slip its write in after the teardown's wipe; a write that wins the\n * ordering instead lands before the teardown and is wiped by it. Returns\n * whether the write ran.\n */\nexport async function withGenerationGuardedMutation(\n state: DocumentStoreState,\n expectedGeneration: DocumentStoreSyncGeneration | undefined,\n write: (execSql: ExecSql) => Promise<void>,\n): Promise<boolean> {\n if (!expectedGeneration) {\n await write(state.runtime.infra.execSql);\n return true;\n }\n\n let ran = false;\n await runSerializedSqlMutation(\n state.runtime.infra.execSql,\n async (lockedExecSql) => {\n if (!isSyncGenerationCurrent(state, expectedGeneration)) {\n return;\n }\n await write(lockedExecSql);\n ran = true;\n },\n );\n return ran;\n}\n\nexport async function enqueuePendingUpdate(\n state: DocumentStoreState,\n update: Uint8Array,\n sourceVersionVector?: string | null,\n expectedGeneration?: DocumentStoreSyncGeneration,\n): Promise<boolean> {\n const expectedDocumentId = state.record?.documentId;\n const expectedRecoveryGeneration = state.record?.recoveryGeneration ?? 0;\n if (expectedDocumentId === undefined) {\n return false;\n }\n let enqueued = false;\n const ran = await withGenerationGuardedMutation(\n state,\n expectedGeneration,\n async (execSql) => {\n enqueued = await enqueuePendingDocumentUpdate({\n execSql,\n expectedDocumentId,\n expectedRecoveryGeneration,\n localId: state.localId,\n persistence: state.persistence,\n ...(sourceVersionVector === undefined ? {} : { sourceVersionVector }),\n update,\n });\n },\n );\n return ran && enqueued;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { encodeVersionVector, exportUpdatesSince } from \"@tearleads/loro\";\nimport { normalizeEffectiveAccessLevel } from \"../../../data/accessLevel\";\nimport { DEFAULT_DOCUMENT_KIND } from \"../../../data/documents/documentConstants\";\nimport {\n type DocumentProjectorRegistry,\n projectStoredDocumentState,\n} from \"../../../data/documents/documentKinds\";\nimport type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { errorMessage } from \"../../../data/errorMessage\";\nimport {\n type DocumentRecord,\n type ExecSql,\n enqueuePendingDocumentUpdate,\n listPendingDocumentUpdates,\n type PendingUpdateRecord,\n persistDocumentState,\n runSerializedSqlMutation,\n} from \"../../../workflows/documents\";\nimport { maybeCompactDocumentHistory } from \"./documentHistoryCompaction\";\nimport {\n importDurableDocumentHistory,\n installDurableDocumentReload,\n} from \"./durableDocumentReload\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n markDocumentStoreRemoved,\n type PersistedDocumentRecord,\n type SaveDocumentRecordOptions,\n setReadySnapshot,\n} from \"./state\";\nimport { createFreshPeerStoredDocument } from \"./storedDocument\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport function documentSummaryFromRecord(\n record: DocumentRecord,\n updatedAt: string,\n documentProjectors: DocumentProjectorRegistry,\n): DocumentSummary {\n return {\n accessStateHash: record.accessStateHash ?? null,\n effectiveAccessLevel: normalizeEffectiveAccessLevel(\n record.effectiveAccessLevel,\n ),\n id: record.id,\n containerId: record.containerId,\n documentKind: record.documentKind ?? DEFAULT_DOCUMENT_KIND,\n documentId: record.documentId,\n title:\n record.title ??\n projectStoredDocumentState(\n {\n documentKind: record.documentKind ?? DEFAULT_DOCUMENT_KIND,\n structuredFields: {},\n text: record.text,\n },\n documentProjectors,\n ).title,\n updatedAt,\n };\n}\n\nfunction toPersistedDocumentRecord(\n persisted: NonNullable<Awaited<ReturnType<typeof persistDocumentState>>>,\n): PersistedDocumentRecord {\n const { record, updatedAt } = persisted;\n return {\n ...(persisted.creationSuperseded\n ? { creationSuperseded: true as const }\n : {}),\n historyRestoreState: persisted.historyRestoreState,\n ...(persisted.pullContinuationSuperseded\n ? { pullContinuationSuperseded: true as const }\n : {}),\n record,\n ...(persisted.syncIdentitySuperseded\n ? { syncIdentitySuperseded: true as const }\n : {}),\n ...(updatedAt !== undefined ? { updatedAt } : {}),\n };\n}\n\n// Nullable without a generation too: an update-persist refuses (returns\n// null) when the durable row was deleted while the persist was queued — the\n// resurrect guard in persistDocumentState — so callers must stop follow-up\n// state changes on null.\nexport async function saveDocumentRecord(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n patch: Partial<DocumentRecord> = {},\n options: SaveDocumentRecordOptions = {},\n expectedGeneration?: DocumentStoreSyncGeneration,\n commitSideEffect?: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<PersistedDocumentRecord | null> {\n const isCurrent = () =>\n (!expectedGeneration ||\n isSyncGenerationCurrent(state, expectedGeneration)) &&\n options.stillCurrent?.() !== false;\n const createsPlacement = state.record === null;\n const previousDocumentId = state.record?.documentId ?? null;\n const pendingBaseVersion =\n options.pendingBaseVersionOverride === undefined\n ? state.pendingBaseVersion\n : options.pendingBaseVersionOverride;\n const persistenceInput = {\n acceptedPendingUpdateIds: options.acceptedPendingUpdateIds,\n attachmentRemoval: options.attachmentRemoval,\n attachmentStaging: options.attachmentStaging,\n clearSyncFailure: options.clearSyncFailure,\n ...(commitSideEffect ? { commitSideEffect } : {}),\n containerId: state.runtime.state.containerId,\n currentDoc,\n currentRecord: state.record,\n documentProjectors: state.runtime.infra.documentProjectors,\n execSql: state.runtime.infra.execSql,\n expectedSyncState: options.expectedSyncState,\n historyCheckpoint: options.historyCheckpoint,\n historyUpdateOrigin: options.historyUpdateOrigin,\n historyUpdates: options.historyUpdates,\n pendingUpdate: options.pendingUpdate,\n localId: state.localId,\n // Persist the durable outgoing-delta marker with every snapshot write so a\n // restart restores it (see initializeDocumentStore). A device-first\n // deferRemoteSync write leaves it BEHIND the snapshot version on purpose;\n // capturing state.pendingBaseVersion here is what lets the next edit\n // re-derive that deferred op after a restart instead of dropping it.\n patch: { ...patch, pendingBaseVersion },\n persistence: state.persistence,\n };\n const persistedDocumentState =\n expectedGeneration || options.stillCurrent\n ? await persistDocumentState({\n ...persistenceInput,\n // persistDocumentState rechecks after its own pre-save awaits and then\n // immediately claims the executor mutation queue. A reset before that\n // point aborts; a reset afterward cannot let replacement writes overtake.\n canStartDurableMutation: isCurrent,\n })\n : await persistDocumentState(persistenceInput);\n if (!persistedDocumentState) {\n // With a stale generation the teardown that invalidated it owns the\n // state. With a current (or absent) generation, the refusal means the\n // durable row was deleted by ANOTHER subsystem while this persist was\n // queued (the resurrect guard): this store is a zombie — clear it so\n // callers that ignore the result stop advancing state or scheduling\n // sync against a document that no longer exists.\n if (isCurrent()) {\n markDocumentStoreRemoved(state);\n }\n void runDocumentOrphanMaintenance(state);\n return null;\n }\n const { record: nextRecord, updatedAt } = persistedDocumentState;\n if (!isCurrent()) {\n return null;\n }\n\n state.record = persistedDocumentState.record;\n state.pullContinuation = nextRecord.pullContinuation ?? null;\n if (persistedDocumentState.pullContinuationSuperseded) {\n state.pendingBaseVersion =\n nextRecord.pendingBaseVersion ?? nextRecord.snapshotEndVersion;\n }\n if (previousDocumentId !== nextRecord.documentId) {\n state.effects.registerDocumentIdentity(\n state.runtime.state.domainScope,\n nextRecord.id,\n nextRecord.documentId,\n );\n }\n if (updatedAt !== undefined) {\n state.effects.emitPersistedDocument(\n state.runtime.state.domainScope,\n documentSummaryFromRecord(\n nextRecord,\n updatedAt,\n state.runtime.infra.documentProjectors,\n ),\n createsPlacement ||\n Object.hasOwn(patch, \"containerId\") ||\n previousDocumentId !== nextRecord.documentId,\n );\n }\n return toPersistedDocumentRecord(persistedDocumentState);\n}\n\nasync function reloadSupersededDocumentState(\n state: DocumentStoreState,\n isCurrent: () => boolean,\n historyRestoreState: PersistedDocumentRecord[\"historyRestoreState\"],\n previousDocumentId: string | null,\n durableRecord: DocumentRecord,\n): Promise<DocumentState | null> {\n if (historyRestoreState === undefined) {\n throw new Error(\"Superseded document state omitted its durable history\");\n }\n const replacementDoc = await createFreshPeerStoredDocument();\n importDurableDocumentHistory(replacementDoc, historyRestoreState);\n if (!isCurrent()) {\n return null;\n }\n installDurableDocumentReload({\n durableRecord,\n previousDocumentId,\n preserveQueuedWritesWhenIdentityMatches: true,\n registerIdentityChange: false,\n replacementDoc,\n state,\n });\n return replacementDoc;\n}\n\nexport async function persistDocument(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n patch: Partial<DocumentRecord> = {},\n options: SaveDocumentRecordOptions = {},\n expectedGeneration?: DocumentStoreSyncGeneration,\n commitSideEffect?: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<PersistedDocumentRecord | null> {\n const isCurrent = () =>\n (!expectedGeneration ||\n isSyncGenerationCurrent(state, expectedGeneration)) &&\n options.stillCurrent?.() !== false;\n const previousDocumentId = state.record?.documentId ?? null;\n const persistedRecord = await saveDocumentRecord(\n state,\n currentDoc,\n patch,\n options,\n expectedGeneration,\n commitSideEffect,\n );\n return publishPersistedDocument(\n state,\n currentDoc,\n persistedRecord,\n previousDocumentId,\n options,\n isCurrent,\n );\n}\n\n/** Publish only after the durable claim succeeds and its live context remains. */\nexport async function publishPersistedDocument(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n persistedRecord: PersistedDocumentRecord | null,\n previousDocumentId: string | null,\n options: SaveDocumentRecordOptions,\n isCurrent: () => boolean,\n): Promise<PersistedDocumentRecord | null> {\n if (!persistedRecord) return null;\n if (!isCurrent()) return null;\n\n if (\n persistedRecord.syncIdentitySuperseded ||\n persistedRecord.pullContinuationSuperseded\n ) {\n // Reload the winning durable checkpoint/tail on every CAS loss, including\n // another pane's history or security-context changes. A rejected sync\n // candidate remains isolated from the live document and editor.\n const replacementDoc = await reloadSupersededDocumentState(\n state,\n isCurrent,\n persistedRecord.historyRestoreState,\n previousDocumentId,\n persistedRecord.record,\n );\n if (!replacementDoc) return null;\n return persistedRecord;\n }\n\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n options.preserveSnapshotText && !persistedRecord.pullContinuationSuperseded\n ? state.snapshot.text\n : persistedRecord.record.text,\n options.preserveSnapshotStructuredFields &&\n !persistedRecord.pullContinuationSuperseded\n ? state.snapshot.structuredFields\n : undefined,\n );\n // Compaction never fails the persist that triggered it: an unexpected\n // compaction error simply keeps the tail growing until a later pass\n // succeeds, and the content stays durable either way.\n try {\n await maybeCompactDocumentHistory(state, currentDoc, options.stillCurrent);\n } catch (error) {\n state.runtime.util.log(\n `Documents: history compaction skipped: ${errorMessage(error)}`,\n );\n }\n return persistedRecord;\n}\n\nexport async function listPendingUpdates(\n state: DocumentStoreState,\n): Promise<PendingUpdateRecord[]> {\n return listPendingDocumentUpdates({\n execSql: state.runtime.infra.execSql,\n localId: state.localId,\n persistence: state.persistence,\n });\n}\n\n/**\n * Export the outgoing delta for the current edit. The base is `pendingBaseVersion`\n * (the version through which ops are already durably enqueued or synced), NOT the\n * live doc version, so any delta orphaned by a prior failed enqueue is folded back\n * in here. In the success path the marker equals the pre-edit version, so this is\n * identical to exporting \"since the last edit\".\n *\n * Initialization sets `pendingBaseVersion` whenever it sets `doc`, and every\n * caller checks `doc` first, so the marker is non-null here. Assert that rather\n * than falling back to the live version: callers invoke this AFTER mutating the\n * doc, so a live-version fallback would export an empty (post-mutation) delta and\n * silently drop the edit.\n */\nexport function pendingDeltaSinceBase(\n state: DocumentStoreState,\n doc: DocumentState,\n): Uint8Array {\n if (state.pendingBaseVersion === null) {\n throw new Error(\n \"pendingDeltaSinceBase requires an initialized pendingBaseVersion\",\n );\n }\n return exportUpdatesSince(doc, state.pendingBaseVersion);\n}\n\n/**\n * Advance the durable marker to the doc's current version. Call this ONLY after\n * an edit's delta has been durably enqueued and persisted, or after remote\n * updates have been imported (those ops are already on the server).\n */\nexport function advancePendingBaseVersion(\n state: DocumentStoreState,\n doc: DocumentState,\n): void {\n state.pendingBaseVersion = encodeVersionVector(doc);\n}\n\n/**\n * Runs a durable write either ungated — no generation: the caller serializes\n * with teardown via the write-chain drain, and the persistence implementation\n * may legitimately park inside the write WITHOUT holding the global mutation\n * mutex — or inside the serialized mutation with an in-mutex currency check:\n * a teardown (reset/discard) that already ran was ordered strictly before the\n * mutation claim and invalidated the generation, so a stale caller can no\n * longer slip its write in after the teardown's wipe; a write that wins the\n * ordering instead lands before the teardown and is wiped by it. Returns\n * whether the write ran.\n */\nexport async function withGenerationGuardedMutation(\n state: DocumentStoreState,\n expectedGeneration: DocumentStoreSyncGeneration | undefined,\n write: (execSql: ExecSql) => Promise<void>,\n): Promise<boolean> {\n if (!expectedGeneration) {\n await write(state.runtime.infra.execSql);\n return true;\n }\n\n let ran = false;\n await runSerializedSqlMutation(\n state.runtime.infra.execSql,\n async (lockedExecSql) => {\n if (!isSyncGenerationCurrent(state, expectedGeneration)) {\n return;\n }\n await write(lockedExecSql);\n ran = true;\n },\n );\n return ran;\n}\n\nexport async function enqueuePendingUpdate(\n state: DocumentStoreState,\n update: Uint8Array,\n sourceVersionVector?: string | null,\n expectedGeneration?: DocumentStoreSyncGeneration,\n): Promise<boolean> {\n const expectedDocumentId = state.record?.documentId;\n const expectedRecoveryGeneration = state.record?.recoveryGeneration ?? 0;\n if (expectedDocumentId === undefined) {\n return false;\n }\n let enqueued = false;\n const ran = await withGenerationGuardedMutation(\n state,\n expectedGeneration,\n async (execSql) => {\n enqueued = await enqueuePendingDocumentUpdate({\n execSql,\n expectedDocumentId,\n expectedRecoveryGeneration,\n localId: state.localId,\n persistence: state.persistence,\n ...(sourceVersionVector === undefined ? {} : { sourceVersionVector }),\n update,\n });\n },\n );\n return ran && enqueued;\n}\n"]}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getDocumentAttachments } from "../../../data/documents/documentContent.js";
|
|
2
2
|
import { detachDocumentAttachment } from "../../../workflows/blobs/detach.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveDocumentCreateAuthor, } from "../../../workflows/documents/index.js";
|
|
4
4
|
import { createRuntimePrincipalPolicyWarmer } from "../../../workflows/principals/runtimePolicyWarmer.js";
|
|
5
5
|
import { deleteLocalAttachmentRecord } from "./attachmentPersistence.js";
|
|
6
|
+
import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
|
|
6
7
|
import { isDocumentStoreSyncGenerationCurrent, } from "./syncGeneration.js";
|
|
7
8
|
import { documentSyncContextMatches } from "./syncUpdateImport.js";
|
|
8
9
|
import { installDocumentWriterProjection } from "./writerProjectionGeneration.js";
|
|
@@ -71,7 +72,7 @@ async function cleanupDetachedAttachmentMarker(state, currentDoc, requestRecord,
|
|
|
71
72
|
}
|
|
72
73
|
await deleteLocalAttachmentRecord(state, marker.slotId, marker.storageKey, currentDoc);
|
|
73
74
|
// Deleting the row queued its bytes for the reference-checked reclaim.
|
|
74
|
-
void
|
|
75
|
+
void runDocumentOrphanMaintenance(state);
|
|
75
76
|
return detachedSyncContextIsCurrent(state, generation, requestRecord);
|
|
76
77
|
}
|
|
77
78
|
async function syncDetachedAttachmentBinding(input) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncDetachedAttachments.js","sourceRoot":"","sources":["syncDetachedAttachments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAEL,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,kCAAkC,EAAE,MAAM,mDAAmD,CAAC;AACvG,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAMtE,OAAO,EAEL,oCAAoC,GACrC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAO/E,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,KAAyB,EACzB,UAA0B,EAC1B,UAAuC;IAEvC,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC;IACzC,IACE,CAAC,UAAU;QACX,CAAC,4BAA4B,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,EAC5D,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,eAAe,GAAG,6BAA6B,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACzE,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO;IACT,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QAC3B,MAAM,gCAAgC,CACpC,KAAK,EACL,UAAU,EACV,UAAU,EACV,eAAe,EACf,UAAU,CACX,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,gBAAgB,GAAG,UAAU,CAAC,UAAU,CAAC;IAC/C,MAAM,cAAc,GAClB,MAAM,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,uBAAuB,CAAC,gBAAgB,CAAC,CAAC;IAC1E,IACE,CAAC,cAAc;QACf,CAAC,4BAA4B,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,EAC5D,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,qBAAqB,GAAG,IAAI,GAAG,CACnC,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAC3D,CAAC;IAEF,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;QACrC,IAAI,CAAC,4BAA4B,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC;YACjE,OAAO;QACT,CAAC;QACD,MAAM,aAAa,GAAG,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,QAAQ,GAAG,MAAM,6BAA6B,CAAC;gBACnD,OAAO,EAAE,aAAa;gBACtB,UAAU;gBACV,gBAAgB;gBAChB,aAAa,EAAE,UAAU;gBACzB,KAAK;aACN,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,+BAA+B,CACnD,KAAK,EACL,UAAU,EACV,UAAU,EACV,MAAM,EACN,UAAU,CACX,CAAC;QACF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;QACT,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,4BAA4B,CACnC,KAAyB,EACzB,UAAuC,EACvC,aAA6B;IAE7B,OAAO,CACL,oCAAoC,CAAC,KAAK,EAAE,UAAU,CAAC;QACvD,0BAA0B,CACxB,KAAK,CAAC,MAAM,EACZ,aAAa,EACb,aAAa,CAAC,UAAU,CACzB,CACF,CAAC;AACJ,CAAC;AAED,SAAS,6BAA6B,CACpC,KAAyB,EACzB,UAAyB;IAEzB,MAAM,cAAc,GAAG,IAAI,GAAG,CAC5B,sBAAsB,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAC1E,CAAC;IAEF,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC,OAAO,CAC/D,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,EAAE,CACvB,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAC7D,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,gCAAgC,CAC7C,KAAyB,EACzB,UAAyB,EACzB,aAA6B,EAC7B,OAA4C,EAC5C,UAAuC;IAEvC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IACE,CAAC,CAAC,MAAM,+BAA+B,CACrC,KAAK,EACL,UAAU,EACV,aAAa,EACb,MAAM,EACN,UAAU,CACX,CAAC,EACF,CAAC;YACD,OAAO;QACT,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,+BAA+B,CAC5C,KAAyB,EACzB,UAAyB,EACzB,aAA6B,EAC7B,MAAgC,EAChC,UAAuC;IAEvC,IAAI,CAAC,4BAA4B,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,CAAC,EAAE,CAAC;QACpE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,2BAA2B,CAC/B,KAAK,EACL,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,UAAU,EACjB,UAAU,CACX,CAAC;IACF,uEAAuE;IACvE,KAAK,0BAA0B,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,4BAA4B,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;AACxE,CAAC;AAED,KAAK,UAAU,6BAA6B,CAAC,KAM5C;IACC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC5D,MAAM,gBAAgB,GAAG,GAAG,EAAE,CAC5B,4BAA4B,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;IACjE,IAAI,CAAC,gBAAgB,EAAE;QAAE,OAAO,KAAK,CAAC;IAEtC,MAAM,MAAM,GAAG,2BAA2B,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CACpB,iFAAiF,CAClF,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,gBAAgB,GACpB,KAAK,CAAC,gBAAgB,EAAE,UAAU,KAAK,KAAK,CAAC,gBAAgB;QAC3D,CAAC,CAAC,KAAK,CAAC,gBAAgB;QACxB,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,0BAA0B,GAAG,KAAK,CAAC,0BAA0B,CAAC;IACpE,IAAI,iBAAiB,GAAG,KAAK,CAAC;IAC9B,MAAM,eAAe,GAAG;QACtB,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS;QAClC,MAAM;QACN,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,UAAU,EAAE,KAAK,CAAC,gBAAgB;QAClC,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,mBAAmB,EAAE,CAAC,cAAsB,EAAE,EAAE;YAC9C,iBAAiB;gBACf,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC;YACpE,OAAO,iBAAiB,CAAC;QAC3B,CAAC;QACD,wBAAwB,EAAE,UAAU,CAAC,wBAAwB;QAC7D,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,YAAY,EAAE,gBAAgB;QAC9B,+BAA+B,EAAE,kCAAkC,CACjE,KAAK,CAAC,OAAO,CACd;KACF,CAAC;IACF,IAAI,QAAQ,GAAG,MAAM,wBAAwB,CAAC;QAC5C,GAAG,eAAe;QAClB,gBAAgB,EAAE,gBAAgB,IAAI,SAAS;KAChD,CAAC,CAAC;IACH,IAAI,CAAC,gBAAgB,EAAE;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,CAAC,QAAQ,IAAI,gBAAgB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACxD,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC9B,QAAQ,GAAG,MAAM,wBAAwB,CAAC,eAAe,CAAC,CAAC;QAC3D,IAAI,CAAC,gBAAgB,EAAE;YAAE,OAAO,KAAK,CAAC;IACxC,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,KAAK,CAAC;IACf,CAAC;IAED,+BAA+B,CAC7B,KAAK,EACL,QAAQ,CAAC,gBAAgB,EACzB,0BAA0B,CAC3B,CAAC;IACF,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CACpB,uBAAuB,OAAO,CAAC,MAAM,kBAAkB,KAAK,CAAC,gBAAgB,GAAG,CACjF,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import { getDocumentAttachments } from \"../../../data/documents/documentContent\";\nimport { detachDocumentAttachment } from \"../../../workflows/blobs/detach\";\nimport {\n type DocumentRecord,\n reclaimDocumentOrphanBlobs,\n resolveDocumentCreateAuthor,\n} from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { deleteLocalAttachmentRecord } from \"./attachmentPersistence\";\nimport type {\n DocumentAttachmentBinding,\n DocumentState,\n DocumentStoreState,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\nimport { documentSyncContextMatches } from \"./syncUpdateImport\";\nimport { installDocumentWriterProjection } from \"./writerProjectionGeneration\";\n\ninterface DetachedAttachmentMarker {\n slotId: string;\n storageKey: string;\n}\n\nexport async function syncDetachedAttachmentBindings(\n state: DocumentStoreState,\n nextRecord: DocumentRecord,\n generation: DocumentStoreSyncGeneration,\n): Promise<void> {\n const currentDoc = generation.currentDoc;\n if (\n !currentDoc ||\n !detachedSyncContextIsCurrent(state, generation, nextRecord)\n ) {\n return;\n }\n\n const detachedMarkers = listDetachedAttachmentMarkers(state, currentDoc);\n if (detachedMarkers.length === 0) {\n return;\n }\n\n if (!nextRecord.documentId) {\n await cleanupDetachedAttachmentMarkers(\n state,\n currentDoc,\n nextRecord,\n detachedMarkers,\n generation,\n );\n return;\n }\n\n const remoteDocumentId = nextRecord.documentId;\n const remoteBindings =\n await state.runtime.apiClient.listDocumentAttachments(remoteDocumentId);\n if (\n !remoteBindings ||\n !detachedSyncContextIsCurrent(state, generation, nextRecord)\n ) {\n return;\n }\n\n const activeBindingBySlotId = new Map(\n remoteBindings.map((binding) => [binding.slotId, binding]),\n );\n\n for (const marker of detachedMarkers) {\n if (!detachedSyncContextIsCurrent(state, generation, nextRecord)) {\n return;\n }\n const activeBinding = activeBindingBySlotId.get(marker.slotId);\n if (activeBinding) {\n const detached = await syncDetachedAttachmentBinding({\n binding: activeBinding,\n generation,\n remoteDocumentId,\n requestRecord: nextRecord,\n state,\n });\n if (!detached) {\n return;\n }\n }\n\n const cleaned = await cleanupDetachedAttachmentMarker(\n state,\n currentDoc,\n nextRecord,\n marker,\n generation,\n );\n if (!cleaned) {\n return;\n }\n }\n}\n\nfunction detachedSyncContextIsCurrent(\n state: DocumentStoreState,\n generation: DocumentStoreSyncGeneration,\n requestRecord: DocumentRecord,\n): boolean {\n return (\n isDocumentStoreSyncGenerationCurrent(state, generation) &&\n documentSyncContextMatches(\n state.record,\n requestRecord,\n requestRecord.documentId,\n )\n );\n}\n\nfunction listDetachedAttachmentMarkers(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n): DetachedAttachmentMarker[] {\n const currentSlotIds = new Set(\n getDocumentAttachments(currentDoc).map((attachment) => attachment.slotId),\n );\n\n return Object.entries(state.attachmentStorageKeyBySlotId).flatMap(\n ([slotId, storageKey]) =>\n currentSlotIds.has(slotId) ? [] : [{ slotId, storageKey }],\n );\n}\n\nasync function cleanupDetachedAttachmentMarkers(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n requestRecord: DocumentRecord,\n markers: readonly DetachedAttachmentMarker[],\n generation: DocumentStoreSyncGeneration,\n): Promise<void> {\n for (const marker of markers) {\n if (\n !(await cleanupDetachedAttachmentMarker(\n state,\n currentDoc,\n requestRecord,\n marker,\n generation,\n ))\n ) {\n return;\n }\n }\n}\n\nasync function cleanupDetachedAttachmentMarker(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n requestRecord: DocumentRecord,\n marker: DetachedAttachmentMarker,\n generation: DocumentStoreSyncGeneration,\n): Promise<boolean> {\n if (!detachedSyncContextIsCurrent(state, generation, requestRecord)) {\n return false;\n }\n await deleteLocalAttachmentRecord(\n state,\n marker.slotId,\n marker.storageKey,\n currentDoc,\n );\n // Deleting the row queued its bytes for the reference-checked reclaim.\n void reclaimDocumentOrphanBlobs(state.runtime);\n return detachedSyncContextIsCurrent(state, generation, requestRecord);\n}\n\nasync function syncDetachedAttachmentBinding(input: {\n binding: DocumentAttachmentBinding;\n generation: DocumentStoreSyncGeneration;\n remoteDocumentId: string;\n requestRecord: DocumentRecord;\n state: DocumentStoreState;\n}): Promise<boolean> {\n const { binding, generation, requestRecord, state } = input;\n const contextIsCurrent = () =>\n detachedSyncContextIsCurrent(state, generation, requestRecord);\n if (!contextIsCurrent()) return false;\n\n const author = resolveDocumentCreateAuthor(state.runtime);\n if (!author) {\n state.runtime.util.log(\n \"Documents: skipped attachment detach because the writer context is unavailable.\",\n );\n return false;\n }\n\n const writerProjection =\n state.writerProjection?.documentId === input.remoteDocumentId\n ? state.writerProjection\n : null;\n const writerProjectionGeneration = state.writerProjectionGeneration;\n let remoteSyncBlocked = false;\n const baseDetachInput = {\n apiClient: state.runtime.apiClient,\n author,\n bindingId: binding.bindingId,\n blobId: binding.blobId,\n documentId: input.remoteDocumentId,\n execSql: generation.execSql,\n isRemoteSyncBlocked: (organizationId: string) => {\n remoteSyncBlocked =\n state.runtime.util.isRemoteSyncBlocked?.(organizationId) ?? false;\n return remoteSyncBlocked;\n },\n resolveProjectionUserKey: generation.resolveProjectionUserKey,\n slotId: binding.slotId,\n stillCurrent: contextIsCurrent,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n state.runtime,\n ),\n };\n let detached = await detachDocumentAttachment({\n ...baseDetachInput,\n writerProjection: writerProjection ?? undefined,\n });\n if (!contextIsCurrent()) return false;\n if (!detached && writerProjection && !remoteSyncBlocked) {\n state.writerProjection = null;\n detached = await detachDocumentAttachment(baseDetachInput);\n if (!contextIsCurrent()) return false;\n }\n if (!detached) {\n return false;\n }\n\n installDocumentWriterProjection(\n state,\n detached.writerProjection,\n writerProjectionGeneration,\n );\n state.runtime.util.log(\n `Detached attachment ${binding.slotId} from document ${input.remoteDocumentId}.`,\n );\n return true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"syncDetachedAttachments.js","sourceRoot":"","sources":["syncDetachedAttachments.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDocumentAttachments } from \"../../../data/documents/documentContent\";\nimport { detachDocumentAttachment } from \"../../../workflows/blobs/detach\";\nimport {\n type DocumentRecord,\n resolveDocumentCreateAuthor,\n} from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { deleteLocalAttachmentRecord } from \"./attachmentPersistence\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport type {\n DocumentAttachmentBinding,\n DocumentState,\n DocumentStoreState,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\nimport { documentSyncContextMatches } from \"./syncUpdateImport\";\nimport { installDocumentWriterProjection } from \"./writerProjectionGeneration\";\n\ninterface DetachedAttachmentMarker {\n slotId: string;\n storageKey: string;\n}\n\nexport async function syncDetachedAttachmentBindings(\n state: DocumentStoreState,\n nextRecord: DocumentRecord,\n generation: DocumentStoreSyncGeneration,\n): Promise<void> {\n const currentDoc = generation.currentDoc;\n if (\n !currentDoc ||\n !detachedSyncContextIsCurrent(state, generation, nextRecord)\n ) {\n return;\n }\n\n const detachedMarkers = listDetachedAttachmentMarkers(state, currentDoc);\n if (detachedMarkers.length === 0) {\n return;\n }\n\n if (!nextRecord.documentId) {\n await cleanupDetachedAttachmentMarkers(\n state,\n currentDoc,\n nextRecord,\n detachedMarkers,\n generation,\n );\n return;\n }\n\n const remoteDocumentId = nextRecord.documentId;\n const remoteBindings =\n await state.runtime.apiClient.listDocumentAttachments(remoteDocumentId);\n if (\n !remoteBindings ||\n !detachedSyncContextIsCurrent(state, generation, nextRecord)\n ) {\n return;\n }\n\n const activeBindingBySlotId = new Map(\n remoteBindings.map((binding) => [binding.slotId, binding]),\n );\n\n for (const marker of detachedMarkers) {\n if (!detachedSyncContextIsCurrent(state, generation, nextRecord)) {\n return;\n }\n const activeBinding = activeBindingBySlotId.get(marker.slotId);\n if (activeBinding) {\n const detached = await syncDetachedAttachmentBinding({\n binding: activeBinding,\n generation,\n remoteDocumentId,\n requestRecord: nextRecord,\n state,\n });\n if (!detached) {\n return;\n }\n }\n\n const cleaned = await cleanupDetachedAttachmentMarker(\n state,\n currentDoc,\n nextRecord,\n marker,\n generation,\n );\n if (!cleaned) {\n return;\n }\n }\n}\n\nfunction detachedSyncContextIsCurrent(\n state: DocumentStoreState,\n generation: DocumentStoreSyncGeneration,\n requestRecord: DocumentRecord,\n): boolean {\n return (\n isDocumentStoreSyncGenerationCurrent(state, generation) &&\n documentSyncContextMatches(\n state.record,\n requestRecord,\n requestRecord.documentId,\n )\n );\n}\n\nfunction listDetachedAttachmentMarkers(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n): DetachedAttachmentMarker[] {\n const currentSlotIds = new Set(\n getDocumentAttachments(currentDoc).map((attachment) => attachment.slotId),\n );\n\n return Object.entries(state.attachmentStorageKeyBySlotId).flatMap(\n ([slotId, storageKey]) =>\n currentSlotIds.has(slotId) ? [] : [{ slotId, storageKey }],\n );\n}\n\nasync function cleanupDetachedAttachmentMarkers(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n requestRecord: DocumentRecord,\n markers: readonly DetachedAttachmentMarker[],\n generation: DocumentStoreSyncGeneration,\n): Promise<void> {\n for (const marker of markers) {\n if (\n !(await cleanupDetachedAttachmentMarker(\n state,\n currentDoc,\n requestRecord,\n marker,\n generation,\n ))\n ) {\n return;\n }\n }\n}\n\nasync function cleanupDetachedAttachmentMarker(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n requestRecord: DocumentRecord,\n marker: DetachedAttachmentMarker,\n generation: DocumentStoreSyncGeneration,\n): Promise<boolean> {\n if (!detachedSyncContextIsCurrent(state, generation, requestRecord)) {\n return false;\n }\n await deleteLocalAttachmentRecord(\n state,\n marker.slotId,\n marker.storageKey,\n currentDoc,\n );\n // Deleting the row queued its bytes for the reference-checked reclaim.\n void runDocumentOrphanMaintenance(state);\n return detachedSyncContextIsCurrent(state, generation, requestRecord);\n}\n\nasync function syncDetachedAttachmentBinding(input: {\n binding: DocumentAttachmentBinding;\n generation: DocumentStoreSyncGeneration;\n remoteDocumentId: string;\n requestRecord: DocumentRecord;\n state: DocumentStoreState;\n}): Promise<boolean> {\n const { binding, generation, requestRecord, state } = input;\n const contextIsCurrent = () =>\n detachedSyncContextIsCurrent(state, generation, requestRecord);\n if (!contextIsCurrent()) return false;\n\n const author = resolveDocumentCreateAuthor(state.runtime);\n if (!author) {\n state.runtime.util.log(\n \"Documents: skipped attachment detach because the writer context is unavailable.\",\n );\n return false;\n }\n\n const writerProjection =\n state.writerProjection?.documentId === input.remoteDocumentId\n ? state.writerProjection\n : null;\n const writerProjectionGeneration = state.writerProjectionGeneration;\n let remoteSyncBlocked = false;\n const baseDetachInput = {\n apiClient: state.runtime.apiClient,\n author,\n bindingId: binding.bindingId,\n blobId: binding.blobId,\n documentId: input.remoteDocumentId,\n execSql: generation.execSql,\n isRemoteSyncBlocked: (organizationId: string) => {\n remoteSyncBlocked =\n state.runtime.util.isRemoteSyncBlocked?.(organizationId) ?? false;\n return remoteSyncBlocked;\n },\n resolveProjectionUserKey: generation.resolveProjectionUserKey,\n slotId: binding.slotId,\n stillCurrent: contextIsCurrent,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n state.runtime,\n ),\n };\n let detached = await detachDocumentAttachment({\n ...baseDetachInput,\n writerProjection: writerProjection ?? undefined,\n });\n if (!contextIsCurrent()) return false;\n if (!detached && writerProjection && !remoteSyncBlocked) {\n state.writerProjection = null;\n detached = await detachDocumentAttachment(baseDetachInput);\n if (!contextIsCurrent()) return false;\n }\n if (!detached) {\n return false;\n }\n\n installDocumentWriterProjection(\n state,\n detached.writerProjection,\n writerProjectionGeneration,\n );\n state.runtime.util.log(\n `Detached attachment ${binding.slotId} from document ${input.remoteDocumentId}.`,\n );\n return true;\n}\n"]}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { encodeVersionVector, exportFullHistorySnapshot, } from "../../../internal/loro/index.js";
|
|
2
|
-
import { createDocumentWriterPublicKeyResolver,
|
|
2
|
+
import { createDocumentWriterPublicKeyResolver, deletePersistedDocument, resolveDocumentCreateAuthor, syncRemoteDocument, validateDocumentSyncUpdateImports, } from "../../../workflows/documents/index.js";
|
|
3
3
|
import { createRuntimePrincipalPolicyWarmer } from "../../../workflows/principals/runtimePolicyWarmer.js";
|
|
4
4
|
import { requestDocumentStoreSync } from "../registry.js";
|
|
5
5
|
import { parkForAncestorRepair } from "./ancestorRepairPark.js";
|
|
6
6
|
import { chainIdentityWrite } from "./identityWriteChain.js";
|
|
7
|
+
import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
|
|
7
8
|
import { invalidateDocumentStorePullContinuation } from "./pullContinuationInvalidation.js";
|
|
8
9
|
import { markDocumentStoreRemoved, } from "./state.js";
|
|
9
10
|
import { isDocumentStoreSyncGenerationCurrent, } from "./syncGeneration.js";
|
|
@@ -46,9 +47,7 @@ export async function deleteUpstreamDeletedDocument(state, generation, requestRe
|
|
|
46
47
|
if (!deletionStarted || !removedInMutation) {
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
-
void reclaimDocumentOrphanBlobs(state.runtime);
|
|
51
|
-
}
|
|
50
|
+
void runDocumentOrphanMaintenance(state);
|
|
52
51
|
state.runtime.util.log(`Documents: removed local document ${state.localId} after remote deletion.`);
|
|
53
52
|
});
|
|
54
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncRequest.js","sourceRoot":"","sources":["syncRequest.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n createDocumentWriterPublicKeyResolver,\n type DocumentRecord,\n defaultDocumentsPersistence,\n deletePersistedDocument,\n type ExecSql,\n type PendingUpdateRecord,\n reclaimDocumentOrphanBlobs,\n resolveDocumentCreateAuthor,\n syncRemoteDocument,\n validateDocumentSyncUpdateImports,\n} from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport { parkForAncestorRepair } from \"./ancestorRepairPark\";\nimport { chainIdentityWrite } from \"./identityWriteChain\";\nimport { invalidateDocumentStorePullContinuation } from \"./pullContinuationInvalidation\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n type DocumentSyncAttempt,\n type EncapsulationKeyPair,\n markDocumentStoreRemoved,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\nimport {\n documentIncomingUpdateIsolationFailureHandler,\n documentRevalidationFailureHandler,\n documentTerminalSubmitFailureHandler,\n} from \"./syncShared\";\nimport { documentSyncContextMatches } from \"./syncUpdateImport\";\n\n/**\n * Destroying queued local edits here is settled policy, not an oversight\n * (docs/sync-edge-cases.md row 1): deletion is a privacy operation, so an\n * authoritative remote delete removes the document and its unsynced edits\n * everywhere with no preservation copy. Quarantine/export were rejected —\n * they retain or re-upload content the user deliberately destroyed.\n */\nexport async function deleteUpstreamDeletedDocument(\n state: DocumentStoreState,\n generation: DocumentStoreSyncGeneration,\n requestRecord: DocumentRecord,\n remoteDocumentId: string,\n commitPurgeProof?: (transactionExecSql: ExecSql) => Promise<void>,\n) {\n await chainIdentityWrite(state, async () => {\n if (\n !isDocumentStoreSyncGenerationCurrent(state, generation) ||\n !documentSyncContextMatches(state.record, requestRecord, remoteDocumentId)\n ) {\n return;\n }\n\n const canDeleteCapturedDocument = () =>\n isDocumentStoreSyncGenerationCurrent(state, generation) &&\n documentSyncContextMatches(state.record, requestRecord, remoteDocumentId);\n let removedInMutation = false;\n const deletionStarted = await deletePersistedDocument({\n beforeDeleteInTransaction: commitPurgeProof,\n canStartDurableMutation: canDeleteCapturedDocument,\n documentProjectors: state.runtime.infra.documentProjectors,\n execSql: generation.execSql,\n expectedRecord: requestRecord,\n localId: requestRecord.id,\n // Invalidate the store generation INSIDE the deletion mutation: a\n // terminal failure handler queued behind it then observes the stale\n // generation and cannot resurrect a failure row the deletes removed.\n onDeletedInMutation: () => {\n if (canDeleteCapturedDocument()) {\n markDocumentStoreRemoved(state);\n removedInMutation = true;\n }\n },\n persistence: state.persistence,\n });\n if (!deletionStarted || !removedInMutation) {\n return;\n }\n if (state.persistence === defaultDocumentsPersistence) {\n void reclaimDocumentOrphanBlobs(state.runtime);\n }\n state.runtime.util.log(\n `Documents: removed local document ${state.localId} after remote deletion.`,\n );\n });\n}\n\nfunction createDocumentSyncAttempt(input: {\n currentRecord: DocumentRecord;\n outgoingUpdateCount: number;\n requestedPullContinuation: DocumentStoreState[\"pullContinuation\"];\n synced: DocumentSyncAttempt[\"synced\"];\n writerProjectionGeneration: number;\n}): DocumentSyncAttempt {\n return {\n consumedPullContinuation:\n input.synced.plan.request.pullCursor === undefined\n ? null\n : input.requestedPullContinuation,\n outgoingUpdateCount: input.outgoingUpdateCount,\n requestRecord: input.currentRecord,\n synced: input.synced,\n writerProjectionGeneration: input.writerProjectionGeneration,\n };\n}\n\ninterface RequestRemoteDocumentSyncInput {\n allowRecoveryBaseline?: boolean | undefined;\n currentDoc: DocumentState;\n currentRecord: DocumentRecord;\n encapsulationKeyPair: EncapsulationKeyPair;\n generation: DocumentStoreSyncGeneration;\n onOutgoingUpdatesMaterialized?:\n | ((updateIds: readonly string[]) => void)\n | undefined;\n pendingUpdates: PendingUpdateRecord[];\n queuedUpdateCount?: number | undefined;\n state: DocumentStoreState;\n unavailableWriterLogMessage: string;\n}\n\nfunction runRemoteDocumentSync(\n input: RequestRemoteDocumentSyncInput,\n author: NonNullable<ReturnType<typeof resolveDocumentCreateAuthor>>,\n documentId: string,\n requestedPullContinuation: DocumentStoreState[\"pullContinuation\"],\n) {\n const { currentDoc, currentRecord, generation, pendingUpdates, state } =\n input;\n const runtime = state.runtime;\n const writerProjectionGenerationAtStart = state.writerProjectionGeneration;\n state.awaitingAncestorRepair = false;\n return syncRemoteDocument({\n apiClient: runtime.apiClient,\n author,\n // Heal a stale content-key bundle from this full-history snapshot. Rotation\n // settlement disables this: it may submit only the ordinary local stream.\n ...(input.allowRecoveryBaseline === false\n ? {}\n : {\n buildRotationSnapshot: async () =>\n exportFullHistorySnapshot(currentDoc),\n }),\n documentId,\n execSql: runtime.infra.execSql,\n isRemoteSyncBlocked: runtime.util.isRemoteSyncBlocked,\n localVersionVector: encodeVersionVector(currentDoc),\n minLsn: currentRecord.lastCommitLsn ?? undefined,\n onRemoteDocumentDeleted: ({ commitPurgeProof, documentId }) =>\n deleteUpstreamDeletedDocument(\n state,\n generation,\n currentRecord,\n documentId,\n commitPurgeProof,\n ),\n onSyncAbandoned: (reason) => {\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return;\n const hintLandedMidPass = parkForAncestorRepair(\n state,\n reason,\n writerProjectionGenerationAtStart,\n );\n if (hintLandedMidPass) requestDocumentStoreSync(state);\n },\n onSyncTrace: (line) => runtime.util.log(`Documents: ${line}`),\n onReadOnlyProjectionFailure: documentRevalidationFailureHandler(\n state,\n generation,\n ),\n onIncomingUpdateIsolationFailure:\n documentIncomingUpdateIsolationFailureHandler(state, generation),\n onOutgoingUpdatesMaterialized: input.onOutgoingUpdatesMaterialized,\n onPullContinuationInvalidated: (continuation) =>\n invalidateDocumentStorePullContinuation({\n continuation,\n currentRecord,\n generation,\n state,\n }),\n onTerminalSubmitFailure: documentTerminalSubmitFailureHandler(\n state,\n generation,\n ),\n pendingUpdates,\n persistedState: currentRecord,\n pullContinuation: requestedPullContinuation ?? undefined,\n rekeyPendingUpdate: state.persistence.rekeyPendingUpdate,\n resolveProjectionUserKey: state.resolveProjectionUserKey,\n resolveWriterPublicKey: createDocumentWriterPublicKeyResolver({\n logPrefix: \"Documents\",\n runtime,\n writerKeyLabel: \"writer key\",\n }),\n stillCurrent: () => isDocumentStoreSyncGenerationCurrent(state, generation),\n targetSecretKey: input.encapsulationKeyPair.secretKey,\n validateIncomingUpdates: (result) =>\n validateDocumentSyncUpdateImports({\n currentDocument: currentDoc,\n decryptedUpdates: result.decryptedUpdates,\n responseUpdates: result.response.updates,\n }),\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n writerProjection:\n state.writerProjection?.documentId === currentRecord.documentId\n ? state.writerProjection\n : undefined,\n });\n}\n\nexport async function requestRemoteDocumentSync(\n input: RequestRemoteDocumentSyncInput,\n): Promise<DocumentSyncAttempt | null> {\n const {\n currentRecord,\n generation,\n pendingUpdates,\n state,\n unavailableWriterLogMessage,\n } = input;\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return null;\n // Captured before the network round trip: a hint that lands meanwhile\n // must keep the response's projection out of the cache.\n const writerProjectionGeneration = state.writerProjectionGeneration;\n\n const runtime = state.runtime;\n if (!currentRecord.documentId) return null;\n\n const requestedPullContinuation = state.pullContinuation;\n\n const author = resolveDocumentCreateAuthor(runtime);\n if (!author) {\n runtime.util.log(unavailableWriterLogMessage);\n return null;\n }\n\n const synced = await runRemoteDocumentSync(\n input,\n author,\n currentRecord.documentId,\n requestedPullContinuation,\n );\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return null;\n if (!synced) return null;\n\n const outgoingUpdateCount = input.queuedUpdateCount ?? pendingUpdates.length;\n return createDocumentSyncAttempt({\n currentRecord,\n outgoingUpdateCount,\n requestedPullContinuation,\n synced,\n writerProjectionGeneration,\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"syncRequest.js","sourceRoot":"","sources":["syncRequest.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n createDocumentWriterPublicKeyResolver,\n type DocumentRecord,\n deletePersistedDocument,\n type ExecSql,\n type PendingUpdateRecord,\n resolveDocumentCreateAuthor,\n syncRemoteDocument,\n validateDocumentSyncUpdateImports,\n} from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport { parkForAncestorRepair } from \"./ancestorRepairPark\";\nimport { chainIdentityWrite } from \"./identityWriteChain\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport { invalidateDocumentStorePullContinuation } from \"./pullContinuationInvalidation\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n type DocumentSyncAttempt,\n type EncapsulationKeyPair,\n markDocumentStoreRemoved,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\nimport {\n documentIncomingUpdateIsolationFailureHandler,\n documentRevalidationFailureHandler,\n documentTerminalSubmitFailureHandler,\n} from \"./syncShared\";\nimport { documentSyncContextMatches } from \"./syncUpdateImport\";\n\n/**\n * Destroying queued local edits here is settled policy, not an oversight\n * (docs/sync-edge-cases.md row 1): deletion is a privacy operation, so an\n * authoritative remote delete removes the document and its unsynced edits\n * everywhere with no preservation copy. Quarantine/export were rejected —\n * they retain or re-upload content the user deliberately destroyed.\n */\nexport async function deleteUpstreamDeletedDocument(\n state: DocumentStoreState,\n generation: DocumentStoreSyncGeneration,\n requestRecord: DocumentRecord,\n remoteDocumentId: string,\n commitPurgeProof?: (transactionExecSql: ExecSql) => Promise<void>,\n) {\n await chainIdentityWrite(state, async () => {\n if (\n !isDocumentStoreSyncGenerationCurrent(state, generation) ||\n !documentSyncContextMatches(state.record, requestRecord, remoteDocumentId)\n ) {\n return;\n }\n\n const canDeleteCapturedDocument = () =>\n isDocumentStoreSyncGenerationCurrent(state, generation) &&\n documentSyncContextMatches(state.record, requestRecord, remoteDocumentId);\n let removedInMutation = false;\n const deletionStarted = await deletePersistedDocument({\n beforeDeleteInTransaction: commitPurgeProof,\n canStartDurableMutation: canDeleteCapturedDocument,\n documentProjectors: state.runtime.infra.documentProjectors,\n execSql: generation.execSql,\n expectedRecord: requestRecord,\n localId: requestRecord.id,\n // Invalidate the store generation INSIDE the deletion mutation: a\n // terminal failure handler queued behind it then observes the stale\n // generation and cannot resurrect a failure row the deletes removed.\n onDeletedInMutation: () => {\n if (canDeleteCapturedDocument()) {\n markDocumentStoreRemoved(state);\n removedInMutation = true;\n }\n },\n persistence: state.persistence,\n });\n if (!deletionStarted || !removedInMutation) {\n return;\n }\n void runDocumentOrphanMaintenance(state);\n state.runtime.util.log(\n `Documents: removed local document ${state.localId} after remote deletion.`,\n );\n });\n}\n\nfunction createDocumentSyncAttempt(input: {\n currentRecord: DocumentRecord;\n outgoingUpdateCount: number;\n requestedPullContinuation: DocumentStoreState[\"pullContinuation\"];\n synced: DocumentSyncAttempt[\"synced\"];\n writerProjectionGeneration: number;\n}): DocumentSyncAttempt {\n return {\n consumedPullContinuation:\n input.synced.plan.request.pullCursor === undefined\n ? null\n : input.requestedPullContinuation,\n outgoingUpdateCount: input.outgoingUpdateCount,\n requestRecord: input.currentRecord,\n synced: input.synced,\n writerProjectionGeneration: input.writerProjectionGeneration,\n };\n}\n\ninterface RequestRemoteDocumentSyncInput {\n allowRecoveryBaseline?: boolean | undefined;\n currentDoc: DocumentState;\n currentRecord: DocumentRecord;\n encapsulationKeyPair: EncapsulationKeyPair;\n generation: DocumentStoreSyncGeneration;\n onOutgoingUpdatesMaterialized?:\n | ((updateIds: readonly string[]) => void)\n | undefined;\n pendingUpdates: PendingUpdateRecord[];\n queuedUpdateCount?: number | undefined;\n state: DocumentStoreState;\n unavailableWriterLogMessage: string;\n}\n\nfunction runRemoteDocumentSync(\n input: RequestRemoteDocumentSyncInput,\n author: NonNullable<ReturnType<typeof resolveDocumentCreateAuthor>>,\n documentId: string,\n requestedPullContinuation: DocumentStoreState[\"pullContinuation\"],\n) {\n const { currentDoc, currentRecord, generation, pendingUpdates, state } =\n input;\n const runtime = state.runtime;\n const writerProjectionGenerationAtStart = state.writerProjectionGeneration;\n state.awaitingAncestorRepair = false;\n return syncRemoteDocument({\n apiClient: runtime.apiClient,\n author,\n // Heal a stale content-key bundle from this full-history snapshot. Rotation\n // settlement disables this: it may submit only the ordinary local stream.\n ...(input.allowRecoveryBaseline === false\n ? {}\n : {\n buildRotationSnapshot: async () =>\n exportFullHistorySnapshot(currentDoc),\n }),\n documentId,\n execSql: runtime.infra.execSql,\n isRemoteSyncBlocked: runtime.util.isRemoteSyncBlocked,\n localVersionVector: encodeVersionVector(currentDoc),\n minLsn: currentRecord.lastCommitLsn ?? undefined,\n onRemoteDocumentDeleted: ({ commitPurgeProof, documentId }) =>\n deleteUpstreamDeletedDocument(\n state,\n generation,\n currentRecord,\n documentId,\n commitPurgeProof,\n ),\n onSyncAbandoned: (reason) => {\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return;\n const hintLandedMidPass = parkForAncestorRepair(\n state,\n reason,\n writerProjectionGenerationAtStart,\n );\n if (hintLandedMidPass) requestDocumentStoreSync(state);\n },\n onSyncTrace: (line) => runtime.util.log(`Documents: ${line}`),\n onReadOnlyProjectionFailure: documentRevalidationFailureHandler(\n state,\n generation,\n ),\n onIncomingUpdateIsolationFailure:\n documentIncomingUpdateIsolationFailureHandler(state, generation),\n onOutgoingUpdatesMaterialized: input.onOutgoingUpdatesMaterialized,\n onPullContinuationInvalidated: (continuation) =>\n invalidateDocumentStorePullContinuation({\n continuation,\n currentRecord,\n generation,\n state,\n }),\n onTerminalSubmitFailure: documentTerminalSubmitFailureHandler(\n state,\n generation,\n ),\n pendingUpdates,\n persistedState: currentRecord,\n pullContinuation: requestedPullContinuation ?? undefined,\n rekeyPendingUpdate: state.persistence.rekeyPendingUpdate,\n resolveProjectionUserKey: state.resolveProjectionUserKey,\n resolveWriterPublicKey: createDocumentWriterPublicKeyResolver({\n logPrefix: \"Documents\",\n runtime,\n writerKeyLabel: \"writer key\",\n }),\n stillCurrent: () => isDocumentStoreSyncGenerationCurrent(state, generation),\n targetSecretKey: input.encapsulationKeyPair.secretKey,\n validateIncomingUpdates: (result) =>\n validateDocumentSyncUpdateImports({\n currentDocument: currentDoc,\n decryptedUpdates: result.decryptedUpdates,\n responseUpdates: result.response.updates,\n }),\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n writerProjection:\n state.writerProjection?.documentId === currentRecord.documentId\n ? state.writerProjection\n : undefined,\n });\n}\n\nexport async function requestRemoteDocumentSync(\n input: RequestRemoteDocumentSyncInput,\n): Promise<DocumentSyncAttempt | null> {\n const {\n currentRecord,\n generation,\n pendingUpdates,\n state,\n unavailableWriterLogMessage,\n } = input;\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return null;\n // Captured before the network round trip: a hint that lands meanwhile\n // must keep the response's projection out of the cache.\n const writerProjectionGeneration = state.writerProjectionGeneration;\n\n const runtime = state.runtime;\n if (!currentRecord.documentId) return null;\n\n const requestedPullContinuation = state.pullContinuation;\n\n const author = resolveDocumentCreateAuthor(runtime);\n if (!author) {\n runtime.util.log(unavailableWriterLogMessage);\n return null;\n }\n\n const synced = await runRemoteDocumentSync(\n input,\n author,\n currentRecord.documentId,\n requestedPullContinuation,\n );\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return null;\n if (!synced) return null;\n\n const outgoingUpdateCount = input.queuedUpdateCount ?? pendingUpdates.length;\n return createDocumentSyncAttempt({\n currentRecord,\n outgoingUpdateCount,\n requestedPullContinuation,\n synced,\n writerProjectionGeneration,\n });\n}\n"]}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { getTargetContainerContext, readContainerState, } from "../../../data/containers/shared/projection.js";
|
|
2
2
|
import { nullOnProjectionVerificationCancellation, verifyContainerWriterProjection, } from "../../../data/keyingProjectionVerification.js";
|
|
3
|
+
import { ProjectionDependencyUnavailableError } from "../../../data/keyingProjectionVerification/dependencyUnavailable.js";
|
|
3
4
|
import { principalPolicyCacheForVerifiedPolicies } from "../../../data/keyingProjectionVerification/principalPolicyCache.js";
|
|
4
|
-
import {
|
|
5
|
-
import { principalPolicyBundleContainsReference } from "../../../data/principalPolicyStates.js";
|
|
6
|
-
import { advanceVerifiedSharePolicies, loadVerifiedGroupSharePrincipalPolicy, referencedPrincipalHeadFromPolicy, } from "../../containers/index.js";
|
|
5
|
+
import { referencedPrincipalHeadFromPolicy } from "../../containers/index.js";
|
|
7
6
|
import { createRuntimeCurrentSharePrincipalPolicy } from "../../containers/child/currentSharePrincipalPolicy.js";
|
|
8
7
|
import { createRuntimePrincipalPolicyWarmer } from "../../principals/runtimePolicyWarmer.js";
|
|
9
8
|
import { loadContainerWriterProjectionForState } from "./projectionCache.js";
|
|
@@ -40,81 +39,31 @@ async function containerStateHasCurrentGroupGrantInternal(input) {
|
|
|
40
39
|
return false;
|
|
41
40
|
}
|
|
42
41
|
const readCurrent = createRuntimeCurrentSharePrincipalPolicy(input.runtime);
|
|
43
|
-
if (readCurrent)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
groupId: input.groupId,
|
|
47
|
-
organizationId: input.expectedOrganizationId,
|
|
48
|
-
stillCurrent: input.stillCurrent ?? (() => true),
|
|
49
|
-
}, async ({ checkpointPolicies, policy, stillCurrent }) => {
|
|
50
|
-
await verifyContainerWriterProjection({
|
|
51
|
-
execSql: input.runtime.infra.execSql,
|
|
52
|
-
principalPolicyCache: principalPolicyCacheForVerifiedPolicies(checkpointPolicies),
|
|
53
|
-
projection,
|
|
54
|
-
resolveUserKey: input.resolveProjectionUserKey,
|
|
55
|
-
stillCurrent,
|
|
56
|
-
warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(input.runtime),
|
|
57
|
-
});
|
|
58
|
-
return projectionHasCurrentGroupGrant({
|
|
59
|
-
accessLevel: input.accessLevel,
|
|
60
|
-
currentHead: referencedPrincipalHeadFromPolicy(policy),
|
|
61
|
-
expectedContainerId: input.expectedContainerId,
|
|
62
|
-
expectedOrganizationId: input.expectedOrganizationId,
|
|
63
|
-
groupId: input.groupId,
|
|
64
|
-
projection,
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
return verifyLegacyCurrentGroupGrant(input, projection);
|
|
68
|
-
}
|
|
69
|
-
async function verifyLegacyCurrentGroupGrant(input, projection) {
|
|
70
|
-
const { bundle, checkpointPolicies, dependencyBundles, policy } = await loadVerifiedGroupSharePrincipalPolicy({
|
|
71
|
-
apiClient: input.runtime.apiClient,
|
|
72
|
-
execSql: input.runtime.infra.execSql,
|
|
42
|
+
if (!readCurrent)
|
|
43
|
+
throw new ProjectionDependencyUnavailableError("Group sharing requires private paged recovery");
|
|
44
|
+
return readCurrent({
|
|
73
45
|
expectedGroupHead: input.expectedGroupHead,
|
|
74
46
|
groupId: input.groupId,
|
|
75
47
|
organizationId: input.expectedOrganizationId,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
principalPolicyCache: principalPolicyCacheForVerifiedPolicies(checkpointPolicies),
|
|
95
|
-
projection,
|
|
96
|
-
resolveUserKey: input.resolveProjectionUserKey,
|
|
97
|
-
stillCurrent: input.stillCurrent,
|
|
98
|
-
warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(input.runtime),
|
|
99
|
-
});
|
|
100
|
-
const isCurrent = projectionHasCurrentGroupGrant({
|
|
101
|
-
accessLevel: input.accessLevel,
|
|
102
|
-
currentHead,
|
|
103
|
-
expectedContainerId: input.expectedContainerId,
|
|
104
|
-
expectedOrganizationId: input.expectedOrganizationId,
|
|
105
|
-
groupId: input.groupId,
|
|
106
|
-
projection,
|
|
48
|
+
stillCurrent: input.stillCurrent ?? (() => true),
|
|
49
|
+
}, async ({ checkpointPolicies, policy, stillCurrent }) => {
|
|
50
|
+
await verifyContainerWriterProjection({
|
|
51
|
+
execSql: input.runtime.infra.execSql,
|
|
52
|
+
principalPolicyCache: principalPolicyCacheForVerifiedPolicies(checkpointPolicies),
|
|
53
|
+
projection,
|
|
54
|
+
resolveUserKey: input.resolveProjectionUserKey,
|
|
55
|
+
stillCurrent,
|
|
56
|
+
warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(input.runtime),
|
|
57
|
+
});
|
|
58
|
+
return projectionHasCurrentGroupGrant({
|
|
59
|
+
accessLevel: input.accessLevel,
|
|
60
|
+
currentHead: referencedPrincipalHeadFromPolicy(policy),
|
|
61
|
+
expectedContainerId: input.expectedContainerId,
|
|
62
|
+
expectedOrganizationId: input.expectedOrganizationId,
|
|
63
|
+
groupId: input.groupId,
|
|
64
|
+
projection,
|
|
65
|
+
});
|
|
107
66
|
});
|
|
108
|
-
if (isCurrent) {
|
|
109
|
-
try {
|
|
110
|
-
await savePrincipalPolicyBundle(input.runtime.infra.execSql, bundle, new Date().toISOString(), input.expectedOrganizationId, { stillCurrent: input.stillCurrent });
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// The cryptographically verified root grant is already safe. A local
|
|
114
|
-
// cache failure must not keep it pending or trigger duplicate re-wraps.
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return input.stillCurrent?.() !== false && isCurrent;
|
|
118
67
|
}
|
|
119
68
|
export async function containerStateHasCurrentGroupGrant(input) {
|
|
120
69
|
return ((await nullOnProjectionVerificationCancellation(() => containerStateHasCurrentGroupGrantInternal(input))) ?? false);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groupGrantVerification.js","sourceRoot":"","sources":["groupGrantVerification.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReferencedPrincipalHead } from \"@tearleads/crypto\";\nimport {\n getTargetContainerContext,\n readContainerState,\n} from \"../../../data/containers/shared/projection\";\nimport {\n nullOnProjectionVerificationCancellation,\n type ProjectionUserKeyResolver,\n verifyContainerWriterProjection,\n} from \"../../../data/keyingProjectionVerification\";\nimport { principalPolicyCacheForVerifiedPolicies } from \"../../../data/keyingProjectionVerification/principalPolicyCache\";\nimport { savePrincipalPolicyBundle } from \"../../../data/persistence/principalPolicyPersistence\";\nimport { principalPolicyBundleContainsReference } from \"../../../data/principalPolicyStates\";\nimport {\n advanceVerifiedSharePolicies,\n loadVerifiedGroupSharePrincipalPolicy,\n referencedPrincipalHeadFromPolicy,\n} from \"../../containers\";\nimport { createRuntimeCurrentSharePrincipalPolicy } from \"../../containers/child/currentSharePrincipalPolicy\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../principals/runtimePolicyWarmer\";\nimport type { ContainerState } from \"../remoteHydration\";\nimport { loadContainerWriterProjectionForState } from \"./projectionCache\";\nimport type { ContainerWorkflowRuntime } from \"./types\";\n\ntype GroupGrantAccessLevel = \"read\" | \"write\" | \"admin\";\n\nfunction projectionHasCurrentGroupGrant(input: {\n accessLevel: GroupGrantAccessLevel;\n expectedContainerId: string;\n expectedOrganizationId: string;\n groupId: string;\n currentHead: ReferencedPrincipalHead;\n projection: NonNullable<\n Awaited<ReturnType<typeof loadContainerWriterProjectionForState>>\n >;\n}): boolean {\n const state = readContainerState(\n getTargetContainerContext(input.projection).manifest,\n );\n return (\n state.containerId === input.expectedContainerId &&\n state.organizationId === input.expectedOrganizationId &&\n state.directGrants.some(\n (grant) =>\n grant.subjectType === \"group\" &&\n grant.subjectId === input.groupId &&\n grant.accessLevel === input.accessLevel,\n ) &&\n state.referencedPrincipalHeads.some(\n (head) =>\n head.principalType === input.currentHead.principalType &&\n head.principalId === input.currentHead.principalId &&\n head.version === input.currentHead.version &&\n head.keyEpoch === input.currentHead.keyEpoch &&\n head.stateHash === input.currentHead.stateHash &&\n head.keyFingerprint === input.currentHead.keyFingerprint,\n )\n );\n}\n\nasync function containerStateHasCurrentGroupGrantInternal(input: {\n accessLevel: GroupGrantAccessLevel;\n containerState: ContainerState;\n expectedContainerId: string;\n expectedGroupHead: ReferencedPrincipalHead;\n expectedOrganizationId: string;\n groupId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<boolean> {\n if (input.stillCurrent?.() === false) return false;\n const projection = await loadContainerWriterProjectionForState({\n containerState: input.containerState,\n runtime: input.runtime,\n });\n if (\n !projection ||\n input.stillCurrent?.() === false ||\n input.expectedGroupHead.principalType !== \"group\" ||\n input.expectedGroupHead.principalId !== input.groupId ||\n input.containerState.container.id !== input.expectedContainerId ||\n input.containerState.container.organizationId !==\n input.expectedOrganizationId ||\n projection.containerId !== input.expectedContainerId ||\n projection.organizationId !== input.expectedOrganizationId\n ) {\n return false;\n }\n\n const readCurrent = createRuntimeCurrentSharePrincipalPolicy(input.runtime);\n if (readCurrent)\n return readCurrent(\n {\n expectedGroupHead: input.expectedGroupHead,\n groupId: input.groupId,\n organizationId: input.expectedOrganizationId,\n stillCurrent: input.stillCurrent ?? (() => true),\n },\n async ({ checkpointPolicies, policy, stillCurrent }) => {\n await verifyContainerWriterProjection({\n execSql: input.runtime.infra.execSql,\n principalPolicyCache:\n principalPolicyCacheForVerifiedPolicies(checkpointPolicies),\n projection,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n return projectionHasCurrentGroupGrant({\n accessLevel: input.accessLevel,\n currentHead: referencedPrincipalHeadFromPolicy(policy),\n expectedContainerId: input.expectedContainerId,\n expectedOrganizationId: input.expectedOrganizationId,\n groupId: input.groupId,\n projection,\n });\n },\n );\n\n return verifyLegacyCurrentGroupGrant(input, projection);\n}\n\nasync function verifyLegacyCurrentGroupGrant(\n input: Parameters<typeof containerStateHasCurrentGroupGrantInternal>[0],\n projection: Parameters<\n typeof projectionHasCurrentGroupGrant\n >[0][\"projection\"],\n): Promise<boolean> {\n const { bundle, checkpointPolicies, dependencyBundles, policy } =\n await loadVerifiedGroupSharePrincipalPolicy({\n apiClient: input.runtime.apiClient,\n execSql: input.runtime.infra.execSql,\n expectedGroupHead: input.expectedGroupHead,\n groupId: input.groupId,\n organizationId: input.expectedOrganizationId,\n resolveTrustedUserIdentity: input.runtime.resolveTrustedUserIdentity,\n stillCurrent: input.stillCurrent,\n });\n if (input.stillCurrent?.() === false) return false;\n if (\n !principalPolicyBundleContainsReference(bundle, input.expectedGroupHead)\n ) {\n return false;\n }\n const currentHead = referencedPrincipalHeadFromPolicy(policy);\n await advanceVerifiedSharePolicies(\n input.runtime.infra.execSql,\n {\n checkpointPolicies,\n dependencyBundles,\n organizationId: input.expectedOrganizationId,\n },\n input.stillCurrent,\n );\n if (input.stillCurrent?.() === false) return false;\n await verifyContainerWriterProjection({\n execSql: input.runtime.infra.execSql,\n principalPolicyCache:\n principalPolicyCacheForVerifiedPolicies(checkpointPolicies),\n projection,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n const isCurrent = projectionHasCurrentGroupGrant({\n accessLevel: input.accessLevel,\n currentHead,\n expectedContainerId: input.expectedContainerId,\n expectedOrganizationId: input.expectedOrganizationId,\n groupId: input.groupId,\n projection,\n });\n if (isCurrent) {\n try {\n await savePrincipalPolicyBundle(\n input.runtime.infra.execSql,\n bundle,\n new Date().toISOString(),\n input.expectedOrganizationId,\n { stillCurrent: input.stillCurrent },\n );\n } catch {\n // The cryptographically verified root grant is already safe. A local\n // cache failure must not keep it pending or trigger duplicate re-wraps.\n }\n }\n return input.stillCurrent?.() !== false && isCurrent;\n}\n\nexport async function containerStateHasCurrentGroupGrant(\n input: Parameters<typeof containerStateHasCurrentGroupGrantInternal>[0],\n): Promise<boolean> {\n return (\n (await nullOnProjectionVerificationCancellation(() =>\n containerStateHasCurrentGroupGrantInternal(input),\n )) ?? false\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"groupGrantVerification.js","sourceRoot":"","sources":["groupGrantVerification.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReferencedPrincipalHead } from \"@tearleads/crypto\";\nimport {\n getTargetContainerContext,\n readContainerState,\n} from \"../../../data/containers/shared/projection\";\nimport {\n nullOnProjectionVerificationCancellation,\n type ProjectionUserKeyResolver,\n verifyContainerWriterProjection,\n} from \"../../../data/keyingProjectionVerification\";\nimport { ProjectionDependencyUnavailableError } from \"../../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport { principalPolicyCacheForVerifiedPolicies } from \"../../../data/keyingProjectionVerification/principalPolicyCache\";\nimport { referencedPrincipalHeadFromPolicy } from \"../../containers\";\nimport { createRuntimeCurrentSharePrincipalPolicy } from \"../../containers/child/currentSharePrincipalPolicy\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../principals/runtimePolicyWarmer\";\nimport type { ContainerState } from \"../remoteHydration\";\nimport { loadContainerWriterProjectionForState } from \"./projectionCache\";\nimport type { ContainerWorkflowRuntime } from \"./types\";\n\ntype GroupGrantAccessLevel = \"read\" | \"write\" | \"admin\";\n\nfunction projectionHasCurrentGroupGrant(input: {\n accessLevel: GroupGrantAccessLevel;\n expectedContainerId: string;\n expectedOrganizationId: string;\n groupId: string;\n currentHead: ReferencedPrincipalHead;\n projection: NonNullable<\n Awaited<ReturnType<typeof loadContainerWriterProjectionForState>>\n >;\n}): boolean {\n const state = readContainerState(\n getTargetContainerContext(input.projection).manifest,\n );\n return (\n state.containerId === input.expectedContainerId &&\n state.organizationId === input.expectedOrganizationId &&\n state.directGrants.some(\n (grant) =>\n grant.subjectType === \"group\" &&\n grant.subjectId === input.groupId &&\n grant.accessLevel === input.accessLevel,\n ) &&\n state.referencedPrincipalHeads.some(\n (head) =>\n head.principalType === input.currentHead.principalType &&\n head.principalId === input.currentHead.principalId &&\n head.version === input.currentHead.version &&\n head.keyEpoch === input.currentHead.keyEpoch &&\n head.stateHash === input.currentHead.stateHash &&\n head.keyFingerprint === input.currentHead.keyFingerprint,\n )\n );\n}\n\nasync function containerStateHasCurrentGroupGrantInternal(input: {\n accessLevel: GroupGrantAccessLevel;\n containerState: ContainerState;\n expectedContainerId: string;\n expectedGroupHead: ReferencedPrincipalHead;\n expectedOrganizationId: string;\n groupId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<boolean> {\n if (input.stillCurrent?.() === false) return false;\n const projection = await loadContainerWriterProjectionForState({\n containerState: input.containerState,\n runtime: input.runtime,\n });\n if (\n !projection ||\n input.stillCurrent?.() === false ||\n input.expectedGroupHead.principalType !== \"group\" ||\n input.expectedGroupHead.principalId !== input.groupId ||\n input.containerState.container.id !== input.expectedContainerId ||\n input.containerState.container.organizationId !==\n input.expectedOrganizationId ||\n projection.containerId !== input.expectedContainerId ||\n projection.organizationId !== input.expectedOrganizationId\n ) {\n return false;\n }\n\n const readCurrent = createRuntimeCurrentSharePrincipalPolicy(input.runtime);\n if (!readCurrent)\n throw new ProjectionDependencyUnavailableError(\n \"Group sharing requires private paged recovery\",\n );\n return readCurrent(\n {\n expectedGroupHead: input.expectedGroupHead,\n groupId: input.groupId,\n organizationId: input.expectedOrganizationId,\n stillCurrent: input.stillCurrent ?? (() => true),\n },\n async ({ checkpointPolicies, policy, stillCurrent }) => {\n await verifyContainerWriterProjection({\n execSql: input.runtime.infra.execSql,\n principalPolicyCache:\n principalPolicyCacheForVerifiedPolicies(checkpointPolicies),\n projection,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n return projectionHasCurrentGroupGrant({\n accessLevel: input.accessLevel,\n currentHead: referencedPrincipalHeadFromPolicy(policy),\n expectedContainerId: input.expectedContainerId,\n expectedOrganizationId: input.expectedOrganizationId,\n groupId: input.groupId,\n projection,\n });\n },\n );\n}\n\nexport async function containerStateHasCurrentGroupGrant(\n input: Parameters<typeof containerStateHasCurrentGroupGrantInternal>[0],\n): Promise<boolean> {\n return (\n (await nullOnProjectionVerificationCancellation(() =>\n containerStateHasCurrentGroupGrantInternal(input),\n )) ?? false\n );\n}\n"]}
|
|
@@ -1,33 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProjectionDependencyUnavailableError } from "../../../data/keyingProjectionVerification/dependencyUnavailable.js";
|
|
2
2
|
import { createRuntimeCurrentSharePrincipalPolicy } from "../../containers/child/currentSharePrincipalPolicy.js";
|
|
3
|
-
import { createRuntimeGroupMetadataAccess } from "../../organizations/groupMetadataRuntime.js";
|
|
4
3
|
export async function resolveCurrentGroupKeyEpoch(input) {
|
|
5
4
|
if (input.stillCurrent?.() === false)
|
|
6
5
|
return null;
|
|
7
6
|
const readCurrent = createRuntimeCurrentSharePrincipalPolicy(input.runtime);
|
|
8
|
-
if (readCurrent)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
groupId: input.groupId,
|
|
12
|
-
organizationId: input.organizationId,
|
|
13
|
-
stillCurrent: input.stillCurrent ?? (() => true),
|
|
14
|
-
}, async ({ policy }) => policy.keyEpoch);
|
|
15
|
-
const verified = await loadVerifiedGroupSharePrincipalPolicy({
|
|
16
|
-
apiClient: input.runtime.apiClient,
|
|
17
|
-
execSql: input.runtime.infra.execSql,
|
|
7
|
+
if (!readCurrent)
|
|
8
|
+
throw new ProjectionDependencyUnavailableError("Group sharing requires private paged recovery");
|
|
9
|
+
return readCurrent({
|
|
18
10
|
expectedGroupName: input.expectedGroupName,
|
|
19
|
-
readEncryptedName: (bundle) => createRuntimeGroupMetadataAccess(input.runtime, input.organizationId, input.stillCurrent).readName(bundle),
|
|
20
11
|
groupId: input.groupId,
|
|
21
12
|
organizationId: input.organizationId,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
25
|
-
if (input.stillCurrent?.() === false)
|
|
26
|
-
return null;
|
|
27
|
-
// Commit the verification immediately: this read stands alone (no enclosing
|
|
28
|
-
// mutation advances it later), and an unadvanced checkpoint would let a
|
|
29
|
-
// newer same-epoch policy be rolled back on the next fetch.
|
|
30
|
-
await advanceVerifiedSharePolicies(input.runtime.infra.execSql, verified, input.stillCurrent);
|
|
31
|
-
return input.stillCurrent?.() === false ? null : verified.policy.keyEpoch;
|
|
13
|
+
stillCurrent: input.stillCurrent ?? (() => true),
|
|
14
|
+
}, async ({ policy }) => policy.keyEpoch);
|
|
32
15
|
}
|
|
33
16
|
//# sourceMappingURL=groupShareEpoch.js.map
|