@tearleads/client-sdk 0.1.44 → 0.1.46

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.
Files changed (133) hide show
  1. package/client/Tearleads.js +3 -0
  2. package/client/Tearleads.js.map +1 -1
  3. package/client/userIdentities.d.ts +11 -1
  4. package/client/userIdentities.js +26 -8
  5. package/client/userIdentities.js.map +1 -1
  6. package/client/workflowRuntime.d.ts +2 -1
  7. package/client/workflowRuntime.js +1 -0
  8. package/client/workflowRuntime.js.map +1 -1
  9. package/data/persistence/container-contents/containerContentsPersistence.js +2 -3
  10. package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
  11. package/data/persistence/container-contents/containerCreateIntentSettlement.js +16 -16
  12. package/data/persistence/container-contents/containerCreateIntentSettlement.js.map +1 -1
  13. package/data/persistence/container-contents/containerDeletionPersistence.js +5 -11
  14. package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
  15. package/data/persistence/container-contents/containerDocumentReassignment.js +13 -13
  16. package/data/persistence/container-contents/containerDocumentReassignment.js.map +1 -1
  17. package/data/persistence/container-contents/containerHydrationPersistence.js +7 -4
  18. package/data/persistence/container-contents/containerHydrationPersistence.js.map +1 -1
  19. package/data/persistence/container-contents/containerIntentPersistence.js +43 -42
  20. package/data/persistence/container-contents/containerIntentPersistence.js.map +1 -1
  21. package/data/persistence/container-contents/containerMetadataMutationPersistence.js +15 -20
  22. package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
  23. package/data/persistence/container-contents/containerMoveAbandonPersistence.js +1 -6
  24. package/data/persistence/container-contents/containerMoveAbandonPersistence.js.map +1 -1
  25. package/data/persistence/container-contents/containerMoveIntentRows.js +9 -8
  26. package/data/persistence/container-contents/containerMoveIntentRows.js.map +1 -1
  27. package/data/persistence/container-contents/containerReconcilePersistence.js +4 -8
  28. package/data/persistence/container-contents/containerReconcilePersistence.js.map +1 -1
  29. package/data/persistence/container-contents/containerRecoveryPersistence.js +20 -19
  30. package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
  31. package/data/persistence/container-contents/containerRemovalWork.js +10 -10
  32. package/data/persistence/container-contents/containerRemovalWork.js.map +1 -1
  33. package/data/persistence/container-contents/containerStructuralRepair.js +21 -44
  34. package/data/persistence/container-contents/containerStructuralRepair.js.map +1 -1
  35. package/data/persistence/container-contents/documentLinkRemovalRepair.js +12 -11
  36. package/data/persistence/container-contents/documentLinkRemovalRepair.js.map +1 -1
  37. package/data/persistence/container-contents/documentMoveIntentPersistence.js +53 -28
  38. package/data/persistence/container-contents/documentMoveIntentPersistence.js.map +1 -1
  39. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js +9 -8
  40. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js.map +1 -1
  41. package/data/persistence/container-contents/documentPurgePlacement.js +6 -6
  42. package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -1
  43. package/data/persistence/container-contents/dormantContainerMetadata.js +1 -5
  44. package/data/persistence/container-contents/dormantContainerMetadata.js.map +1 -1
  45. package/data/persistence/container-contents/pendingContainerCreate.js +5 -5
  46. package/data/persistence/container-contents/pendingContainerCreate.js.map +1 -1
  47. package/data/persistence/containers/containerPersistence.js +10 -27
  48. package/data/persistence/containers/containerPersistence.js.map +1 -1
  49. package/data/persistence/containers/documentContainerProjectionPersistence.js +2 -2
  50. package/data/persistence/containers/documentContainerProjectionPersistence.js.map +1 -1
  51. package/data/persistence/containers/documentPlacement.js +10 -10
  52. package/data/persistence/containers/documentPlacement.js.map +1 -1
  53. package/data/persistence/documents/internal/discardDocument.js +5 -5
  54. package/data/persistence/documents/internal/discardDocument.js.map +1 -1
  55. package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -9
  56. package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
  57. package/data/persistence/documents/internal/documentMutationPersistence.js +6 -8
  58. package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
  59. package/data/persistence/documents/internal/ensureDocumentsSchema.js +2 -2
  60. package/data/persistence/documents/internal/ensureDocumentsSchema.js.map +1 -1
  61. package/data/persistence/documents/internal/orphanSideRows.js +3 -22
  62. package/data/persistence/documents/internal/orphanSideRows.js.map +1 -1
  63. package/data/persistence/organizations/organizationDataUsagePersistence.js +31 -64
  64. package/data/persistence/organizations/organizationDataUsagePersistence.js.map +1 -1
  65. package/data/persistence/organizations/organizationReadModelPurge.js +1 -5
  66. package/data/persistence/organizations/organizationReadModelPurge.js.map +1 -1
  67. package/data/persistence/principalCurrentAcknowledgementPersistence.js +24 -17
  68. package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
  69. package/data/persistence/principalHistoryIncompleteRetention.js +3 -9
  70. package/data/persistence/principalHistoryIncompleteRetention.js.map +1 -1
  71. package/data/persistence/principalHistoryStagePersistence.d.ts +3 -9
  72. package/data/persistence/principalHistoryStagePersistence.js +15 -25
  73. package/data/persistence/principalHistoryStagePersistence.js.map +1 -1
  74. package/data/persistence/principalHistoryStageRetention.js +16 -15
  75. package/data/persistence/principalHistoryStageRetention.js.map +1 -1
  76. package/data/persistence/principalHistoryUnpublishedRetention.js +6 -13
  77. package/data/persistence/principalHistoryUnpublishedRetention.js.map +1 -1
  78. package/data/sqlite/containerSchema.d.ts +84 -180
  79. package/data/sqlite/containerSchema.js +14 -23
  80. package/data/sqlite/containerSchema.js.map +1 -1
  81. package/data/sqlite/documentPlacementIntentSchema.js +3 -50
  82. package/data/sqlite/documentPlacementIntentSchema.js.map +1 -1
  83. package/data/sqlite/documentSyncFailurePersistence.d.ts +16 -6
  84. package/data/sqlite/documentSyncFailurePersistence.js +44 -54
  85. package/data/sqlite/documentSyncFailurePersistence.js.map +1 -1
  86. package/data/sqlite/{documentPlacementIntentSchema.d.ts → localIntentSchema.d.ts} +162 -88
  87. package/data/sqlite/localIntentSchema.js +90 -0
  88. package/data/sqlite/localIntentSchema.js.map +1 -0
  89. package/data/sqlite/organizationDataUsageSchema.js +11 -17
  90. package/data/sqlite/organizationDataUsageSchema.js.map +1 -1
  91. package/data/sqlite/principalHistoryRetentionSchema.js +0 -15
  92. package/data/sqlite/principalHistoryRetentionSchema.js.map +1 -1
  93. package/data/sqlite/principalHistoryStageSchema.d.ts +44 -2
  94. package/data/sqlite/principalHistoryStageSchema.js +14 -2
  95. package/data/sqlite/principalHistoryStageSchema.js.map +1 -1
  96. package/data/sqlite/schema.d.ts +947 -2112
  97. package/data/sqlite/schema.js +26 -134
  98. package/data/sqlite/schema.js.map +1 -1
  99. package/data/trustedUserIdentity/identityLink.d.ts +12 -0
  100. package/data/trustedUserIdentity/identityLink.js +117 -0
  101. package/data/trustedUserIdentity/identityLink.js.map +1 -0
  102. package/data/trustedUserIdentity/index.d.ts +2 -1
  103. package/data/trustedUserIdentity/index.js +1 -0
  104. package/data/trustedUserIdentity/index.js.map +1 -1
  105. package/data/trustedUserIdentity/service.js +48 -2
  106. package/data/trustedUserIdentity/service.js.map +1 -1
  107. package/data/trustedUserIdentity/types.d.ts +3 -0
  108. package/data/trustedUserIdentity/types.js.map +1 -1
  109. package/index.js +1 -1
  110. package/index.js.map +1 -1
  111. package/package.json +1 -1
  112. package/workflows/container-contents/documentPriming.js +6 -10
  113. package/workflows/container-contents/documentPriming.js.map +1 -1
  114. package/workflows/container-contents/documentQueries/sql.js +11 -10
  115. package/workflows/container-contents/documentQueries/sql.js.map +1 -1
  116. package/workflows/container-contents/documentQueries.js +2 -2
  117. package/workflows/container-contents/documentQueries.js.map +1 -1
  118. package/workflows/container-contents/pendingDownloads.js +2 -5
  119. package/workflows/container-contents/pendingDownloads.js.map +1 -1
  120. package/workflows/container-contents/pendingWrites/deferredTails.js +3 -11
  121. package/workflows/container-contents/pendingWrites/deferredTails.js.map +1 -1
  122. package/workflows/container-contents/pendingWrites/sources.js +56 -80
  123. package/workflows/container-contents/pendingWrites/sources.js.map +1 -1
  124. package/workflows/container-contents/pendingWrites.js +6 -4
  125. package/workflows/container-contents/pendingWrites.js.map +1 -1
  126. package/workflows/container-contents/remoteContainerState.js +2 -1
  127. package/workflows/container-contents/remoteContainerState.js.map +1 -1
  128. package/workflows/sync/remoteReset.js +16 -23
  129. package/workflows/sync/remoteReset.js.map +1 -1
  130. package/workflows/sync/remoteResetPrincipalScope.js +1 -2
  131. package/workflows/sync/remoteResetPrincipalScope.js.map +1 -1
  132. package/workflows/sync/remoteResetScope.js +15 -8
  133. package/workflows/sync/remoteResetScope.js.map +1 -1
@@ -1,10 +1,11 @@
1
1
  import { and, asc, eq, inArray } from "drizzle-orm";
2
2
  import { documentIntentLinkTargets } from "../../sqlite/documentPlacementIntentSchema.js";
3
- import { containerTables, documentMoveIntents, documentMoveIntentTables, documentProjectionTables, } from "../../sqlite/schema.js";
3
+ import { containerTables, documentPlacementIntentTables, documentProjectionTables, inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../sqlite/schema.js";
4
4
  import { getClientSQLitePersistenceRuntime } from "../../sqlite/sqlitePersistenceRuntime.js";
5
5
  import { ensureSqlTables, runSerializedSqlMutation, } from "../../sqlite/sqlSchema.js";
6
6
  import { enqueueDocumentLinkIntent, enqueueDocumentMoveIntent, enqueueDocumentUnlinkIntent, loadDocumentIntentLinkTargets, } from "./documentPlacementIntentEnqueue.js";
7
7
  export const DOCUMENT_MOVE_INTENT_TYPE = "document.move";
8
+ const placementLane = inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement);
8
9
  export const DOCUMENT_LINK_INTENT_TYPE = "document.link";
9
10
  const DOCUMENT_PLACEMENT_INTENT_TYPES = [
10
11
  DOCUMENT_MOVE_INTENT_TYPE,
@@ -51,7 +52,8 @@ function mapDocumentMoveIntentRecord(row) {
51
52
  : DOCUMENT_MOVE_INTENT_TYPE,
52
53
  lastAttemptedAt: row.lastAttemptedAt,
53
54
  lastError: row.lastError,
54
- localId: row.localId,
55
+ // Placement intents always carry their local document row.
56
+ localId: row.localId ?? "",
55
57
  replaceLinkedContainers: Boolean(row.replaceLinkedContainers),
56
58
  sourceContainerId: row.sourceContainerId,
57
59
  syncStatus: parseDocumentMoveIntentSyncStatus(row.syncStatus),
@@ -62,18 +64,31 @@ function mapDocumentMoveIntentRecord(row) {
62
64
  }
63
65
  export const sqlDocumentMoveIntentPersistence = {
64
66
  async ensureSchema(execSql) {
65
- await ensureSqlTables(execSql, documentMoveIntentTables);
67
+ await ensureSqlTables(execSql, documentPlacementIntentTables);
66
68
  },
67
69
  enqueueMoveIntent: enqueueDocumentMoveIntent,
68
70
  enqueueLinkIntent: enqueueDocumentLinkIntent,
69
71
  enqueueUnlinkIntent: enqueueDocumentUnlinkIntent,
70
72
  async listPendingMoveIntents(execSql) {
71
73
  return runSerializedSqlMutation(execSql, async (lockedExecSql) => {
72
- await ensureSqlTables(lockedExecSql, documentMoveIntentTables);
74
+ await ensureSqlTables(lockedExecSql, documentPlacementIntentTables);
73
75
  return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(async (tx) => {
74
76
  const rows = await tx
75
- .select()
76
- .from(documentMoveIntents)
77
+ .select({
78
+ id: localIntents.id,
79
+ documentId: localIntents.subjectId,
80
+ intentType: localIntents.intentType,
81
+ lastAttemptedAt: localIntents.lastAttemptedAt,
82
+ lastError: localIntents.lastError,
83
+ localId: localIntents.localId,
84
+ replaceLinkedContainers: localIntents.replaceLinkedContainers,
85
+ sourceContainerId: localIntents.sourceContainerId,
86
+ syncStatus: localIntents.syncStatus,
87
+ targetContainerId: localIntents.targetContainerId,
88
+ createdAt: localIntents.createdAt,
89
+ updatedAt: localIntents.updatedAt,
90
+ })
91
+ .from(localIntents)
77
92
  // Blocked intents replay too: "blocked" names the reason the last
78
93
  // attempt could not proceed (missing local doc / destination), not a
79
94
  // terminal verdict. The blocking condition can heal after hydration or
@@ -82,8 +97,8 @@ export const sqlDocumentMoveIntentPersistence = {
82
97
  // Unavailable intents never replay: the server proved a cited container
83
98
  // is gone, so every replay would re-issue the same doomed requests
84
99
  // (#2278 #4). Only the tombstone cascade or a re-enqueue revives them.
85
- .where(and(inArray(documentMoveIntents.syncStatus, ["pending", "blocked"]), inArray(documentMoveIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES)))
86
- .orderBy(asc(documentMoveIntents.createdAt));
100
+ .where(and(placementLane, inArray(localIntents.syncStatus, ["pending", "blocked"]), inArray(localIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES)))
101
+ .orderBy(asc(localIntents.createdAt));
87
102
  return Promise.all(rows.map(async (row) => {
88
103
  const targets = await loadDocumentIntentLinkTargets(lockedExecSql, row.id ?? "");
89
104
  const added = targets
@@ -105,12 +120,14 @@ export const sqlDocumentMoveIntentPersistence = {
105
120
  },
106
121
  async markMoveIntentSynced(execSql, input) {
107
122
  return runSerializedSqlMutation(execSql, async (lockedExecSql) => {
108
- const deleted = await lockedExecSql(`DELETE FROM document_move_intents
109
- WHERE document_id = ?
123
+ const deleted = await lockedExecSql(`DELETE FROM local_intents
124
+ WHERE lane = ?
125
+ AND subject_id = ?
110
126
  AND intent_type IN (?, ?)
111
127
  AND updated_at = ?
112
128
  ${input.expectedIntentId ? "AND id = ?" : ""}
113
129
  RETURNING id`, [
130
+ LOCAL_INTENT_LANES.documentPlacement,
114
131
  input.documentId,
115
132
  ...DOCUMENT_PLACEMENT_INTENT_TYPES,
116
133
  input.expectedUpdatedAt,
@@ -129,25 +146,25 @@ export const sqlDocumentMoveIntentPersistence = {
129
146
  await getClientSQLitePersistenceRuntime(execSql).guardedTransaction(async (db) => {
130
147
  const updatedAt = new Date().toISOString();
131
148
  await db
132
- .update(documentMoveIntents)
149
+ .update(localIntents)
133
150
  .set({
134
151
  lastAttemptedAt: updatedAt,
135
152
  lastError: input.message,
136
153
  syncStatus: resolveRecordedSyncStatus(input),
137
154
  updatedAt,
138
155
  })
139
- .where(and(eq(documentMoveIntents.documentId, input.documentId),
156
+ .where(and(placementLane, eq(localIntents.subjectId, input.documentId),
140
157
  // Blocked/denied rows must stay updatable: a retried intent
141
158
  // records its fresh outcome, and a transient failure flips it
142
159
  // back to pending instead of freezing it forever.
143
- inArray(documentMoveIntents.syncStatus, [
160
+ inArray(localIntents.syncStatus, [
144
161
  "pending",
145
162
  "blocked",
146
163
  "denied",
147
- ]), inArray(documentMoveIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES), ...(input.expectedIntentId
148
- ? [eq(documentMoveIntents.id, input.expectedIntentId)]
164
+ ]), inArray(localIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES), ...(input.expectedIntentId
165
+ ? [eq(localIntents.id, input.expectedIntentId)]
149
166
  : []), ...(input.expectedUpdatedAt
150
- ? [eq(documentMoveIntents.updatedAt, input.expectedUpdatedAt)]
167
+ ? [eq(localIntents.updatedAt, input.expectedUpdatedAt)]
151
168
  : [])))
152
169
  .run();
153
170
  }, input.stillCurrent ?? (() => true), { behavior: "immediate" });
@@ -162,15 +179,18 @@ export const sqlDocumentMoveIntentPersistence = {
162
179
  ...documentProjectionTables,
163
180
  ...containerTables,
164
181
  ]);
165
- const rows = await execSql(`SELECT intent.document_id AS document_id
166
- FROM document_move_intents intent
182
+ const rows = await execSql(`SELECT intent.subject_id AS document_id
183
+ FROM local_intents intent
167
184
  ${input?.organizationId ? DENIED_INTENT_ORGANIZATION_JOINS_SQL : ""}
168
- WHERE intent.sync_status = 'denied'
185
+ WHERE intent.lane = ?
186
+ AND intent.sync_status = 'denied'
169
187
  AND intent.intent_type IN (?, ?)
170
188
  ${input?.organizationId ? `AND (${DENIED_INTENT_ORGANIZATION_SQL} = ? OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL)` : ""}
171
- LIMIT 1`, input?.organizationId
172
- ? [...DOCUMENT_PLACEMENT_INTENT_TYPES, input.organizationId]
173
- : DOCUMENT_PLACEMENT_INTENT_TYPES);
189
+ LIMIT 1`, [
190
+ LOCAL_INTENT_LANES.documentPlacement,
191
+ ...DOCUMENT_PLACEMENT_INTENT_TYPES,
192
+ ...(input?.organizationId ? [input.organizationId] : []),
193
+ ]);
174
194
  return rows.length > 0;
175
195
  },
176
196
  /**
@@ -189,23 +209,28 @@ export const sqlDocumentMoveIntentPersistence = {
189
209
  // container joins above: restoring one organization's access must not
190
210
  // un-park moves that another organization still denies. Rows with no
191
211
  // resolvable organization are included (device-first shapes).
192
- await lockedExecSql(`UPDATE document_move_intents
212
+ await lockedExecSql(`UPDATE local_intents
193
213
  SET sync_status = 'pending', updated_at = ?
194
- WHERE sync_status = 'denied'
214
+ WHERE lane = ?
215
+ AND sync_status = 'denied'
195
216
  AND intent_type IN (?, ?)
196
217
  ${input?.localId ? "AND local_id = ?" : ""}
197
218
  ${input?.organizationId
198
219
  ? `AND local_id IN (
199
- SELECT intent.local_id FROM document_move_intents intent
220
+ SELECT intent.local_id FROM local_intents intent
200
221
  ${DENIED_INTENT_ORGANIZATION_JOINS_SQL}
201
- WHERE ${DENIED_INTENT_ORGANIZATION_SQL} = ?
202
- OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL
222
+ WHERE intent.lane = ?
223
+ AND (${DENIED_INTENT_ORGANIZATION_SQL} = ?
224
+ OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL)
203
225
  )`
204
226
  : ""}`, [
205
227
  new Date().toISOString(),
228
+ LOCAL_INTENT_LANES.documentPlacement,
206
229
  ...DOCUMENT_PLACEMENT_INTENT_TYPES,
207
230
  ...(input?.localId ? [input.localId] : []),
208
- ...(input?.organizationId ? [input.organizationId] : []),
231
+ ...(input?.organizationId
232
+ ? [LOCAL_INTENT_LANES.documentPlacement, input.organizationId]
233
+ : []),
209
234
  ]);
210
235
  });
211
236
  },
@@ -1 +1 @@
1
- {"version":3,"file":"documentMoveIntentPersistence.js","sourceRoot":"","sources":["documentMoveIntentPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, asc, eq, inArray } from \"drizzle-orm\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n containerTables,\n documentMoveIntents,\n documentMoveIntentTables,\n documentProjectionTables,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport {\n enqueueDocumentLinkIntent,\n enqueueDocumentMoveIntent,\n enqueueDocumentUnlinkIntent,\n loadDocumentIntentLinkTargets,\n} from \"./documentPlacementIntentEnqueue\";\n\nexport const DOCUMENT_MOVE_INTENT_TYPE = \"document.move\";\nexport const DOCUMENT_LINK_INTENT_TYPE = \"document.link\";\nconst DOCUMENT_PLACEMENT_INTENT_TYPES = [\n DOCUMENT_MOVE_INTENT_TYPE,\n DOCUMENT_LINK_INTENT_TYPE,\n];\n\n// Resolves the organization a parked move belongs to, in confidence order:\n// the move's target container, the document's preserved projection\n// attribution, then the container the document currently sits in. Normal\n// saves routinely leave projection attribution empty (deferred-tail\n// reporting resolves through the same container join), so the container rows\n// carry the common case; rows with no resolvable organization stay in every\n// scope (device-first shapes, matching orphan priming).\nconst DENIED_INTENT_ORGANIZATION_SQL = `COALESCE(\n NULLIF(target_container.organization_id, ''),\n NULLIF(projection.organization_id, ''),\n NULLIF(projection_container.organization_id, '')\n)`;\n\nconst DENIED_INTENT_ORGANIZATION_JOINS_SQL = `\n LEFT JOIN containers target_container\n ON target_container.id = intent.target_container_id\n LEFT JOIN document_projection projection\n ON projection.local_id = intent.local_id\n LEFT JOIN containers projection_container\n ON projection_container.id = projection.container_id`;\n\n/**\n * - `pending`: replays on every structural pass.\n * - `blocked`: the last pass could not proceed locally (missing local\n * document / destination); replays, since hydration can heal it.\n * - `denied`: parked on a 403 until the access-restored replay (row 7).\n * - `unavailable`: terminal — after a projection refresh the destination\n * itself is proven deleted (coded 404 on its own projection probe). No\n * mutation 409 is ever terminal: with the destination live it can only\n * name an ancestor or source, and the next pass's fresh paths outrun it.\n * Container ids never return, so no replay can commit the intent as\n * written; it leaves the replay set until the local container tombstone\n * cascade retargets it or the user re-enqueues the move.\n */\nexport type DocumentMoveIntentSyncStatus =\n | \"pending\"\n | \"blocked\"\n | \"denied\"\n | \"unavailable\";\n\nexport interface DocumentMoveIntentRecord {\n additionalLinkContainerIds?: readonly string[] | undefined;\n removedLinkContainerIds?: readonly string[] | undefined;\n id: string;\n documentId: string;\n intentType:\n | typeof DOCUMENT_MOVE_INTENT_TYPE\n | typeof DOCUMENT_LINK_INTENT_TYPE;\n lastAttemptedAt: string | null;\n lastError: string | null;\n localId: string;\n replaceLinkedContainers: boolean;\n sourceContainerId: string | null;\n syncStatus: DocumentMoveIntentSyncStatus;\n targetContainerId: string;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface SelectedDocumentMoveIntentRecord {\n id: string | null;\n documentId: string;\n intentType: string;\n lastAttemptedAt: string | null;\n lastError: string | null;\n localId: string;\n replaceLinkedContainers: boolean;\n sourceContainerId: string | null;\n syncStatus: string;\n targetContainerId: string;\n createdAt: string;\n updatedAt: string;\n}\n\nfunction parseDocumentMoveIntentSyncStatus(\n value: unknown,\n): DocumentMoveIntentSyncStatus {\n if (value === \"blocked\" || value === \"denied\" || value === \"unavailable\") {\n return value;\n }\n return \"pending\";\n}\n\nfunction resolveRecordedSyncStatus(input: {\n blocked?: boolean | undefined;\n denied?: boolean | undefined;\n unavailable?: boolean | undefined;\n}): DocumentMoveIntentSyncStatus {\n if (input.unavailable) return \"unavailable\";\n if (input.denied) return \"denied\";\n return input.blocked ? \"blocked\" : \"pending\";\n}\n\nfunction mapDocumentMoveIntentRecord(\n row: SelectedDocumentMoveIntentRecord,\n): DocumentMoveIntentRecord {\n return {\n id: String(row.id ?? \"\"),\n documentId: row.documentId,\n intentType:\n row.intentType === DOCUMENT_LINK_INTENT_TYPE\n ? DOCUMENT_LINK_INTENT_TYPE\n : DOCUMENT_MOVE_INTENT_TYPE,\n lastAttemptedAt: row.lastAttemptedAt,\n lastError: row.lastError,\n localId: row.localId,\n replaceLinkedContainers: Boolean(row.replaceLinkedContainers),\n sourceContainerId: row.sourceContainerId,\n syncStatus: parseDocumentMoveIntentSyncStatus(row.syncStatus),\n targetContainerId: row.targetContainerId,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport const sqlDocumentMoveIntentPersistence = {\n async ensureSchema(execSql: ExecSql): Promise<void> {\n await ensureSqlTables(execSql, documentMoveIntentTables);\n },\n\n enqueueMoveIntent: enqueueDocumentMoveIntent,\n enqueueLinkIntent: enqueueDocumentLinkIntent,\n enqueueUnlinkIntent: enqueueDocumentUnlinkIntent,\n\n async listPendingMoveIntents(\n execSql: ExecSql,\n ): Promise<DocumentMoveIntentRecord[]> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureSqlTables(lockedExecSql, documentMoveIntentTables);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const rows = await tx\n .select()\n .from(documentMoveIntents)\n // Blocked intents replay too: \"blocked\" names the reason the last\n // attempt could not proceed (missing local doc / destination), not a\n // terminal verdict. The blocking condition can heal after hydration or\n // recovery, and re-checking is cheap — a still-blocked intent simply\n // re-records its reason without counting as lane progress.\n // Unavailable intents never replay: the server proved a cited container\n // is gone, so every replay would re-issue the same doomed requests\n // (#2278 #4). Only the tombstone cascade or a re-enqueue revives them.\n .where(\n and(\n inArray(documentMoveIntents.syncStatus, [\"pending\", \"blocked\"]),\n inArray(\n documentMoveIntents.intentType,\n DOCUMENT_PLACEMENT_INTENT_TYPES,\n ),\n ),\n )\n .orderBy(asc(documentMoveIntents.createdAt));\n\n return Promise.all(\n rows.map(async (row) => {\n const targets = await loadDocumentIntentLinkTargets(\n lockedExecSql,\n row.id ?? \"\",\n );\n const added = targets\n .filter((target) => target.operation === \"link\")\n .map((target) => target.containerId)\n .sort();\n const removed = targets\n .filter((target) => target.operation === \"unlink\")\n .map((target) => target.containerId)\n .sort();\n return {\n ...mapDocumentMoveIntentRecord(row),\n ...(added.length ? { additionalLinkContainerIds: added } : {}),\n ...(removed.length ? { removedLinkContainerIds: removed } : {}),\n };\n }),\n );\n },\n );\n });\n },\n\n async markMoveIntentSynced(\n execSql: ExecSql,\n input: {\n documentId: string;\n expectedIntentId?: string | undefined;\n expectedUpdatedAt: string;\n },\n ): Promise<boolean> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const deleted = await lockedExecSql(\n `DELETE FROM document_move_intents\n WHERE document_id = ?\n AND intent_type IN (?, ?)\n AND updated_at = ?\n ${input.expectedIntentId ? \"AND id = ?\" : \"\"}\n RETURNING id`,\n [\n input.documentId,\n ...DOCUMENT_PLACEMENT_INTENT_TYPES,\n input.expectedUpdatedAt,\n ...(input.expectedIntentId ? [input.expectedIntentId] : []),\n ],\n );\n for (const { id } of deleted) {\n await getClientSQLitePersistenceRuntime(lockedExecSql)\n .db.delete(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, String(id)))\n .run();\n }\n return deleted.length > 0;\n });\n },\n\n async recordMoveIntentError(\n execSql: ExecSql,\n input: {\n blocked?: boolean | undefined;\n /**\n * A permission denial (403): the intent parks as `denied` — excluded\n * from routine structural replays — until the org-access-restored\n * signal or a manual retry flips it back to pending (edge-case row 7).\n */\n denied?: boolean | undefined;\n documentId: string;\n /** Exact intent revision read by the pass; preferred over wall-clock CAS. */\n expectedIntentId?: string | undefined;\n /**\n * Optimistic concurrency: when given, the error only records against\n * the intent revision the pass read — a re-enqueued move (new\n * updatedAt) is never parked by a stale in-flight failure.\n */\n expectedUpdatedAt?: string | undefined;\n message: string;\n stillCurrent?: (() => boolean) | undefined;\n /**\n * Server proof that a cited container was deleted (coded 404 or\n * `container_unavailable` 409): the intent parks terminally as\n * `unavailable`, outside every replay, until the tombstone cascade\n * retargets it or a re-enqueue replaces it. Outranks `denied`: a\n * restored permission cannot revive a deleted container.\n */\n unavailable?: boolean | undefined;\n },\n ): Promise<void> {\n await getClientSQLitePersistenceRuntime(execSql).guardedTransaction(\n async (db) => {\n const updatedAt = new Date().toISOString();\n await db\n .update(documentMoveIntents)\n .set({\n lastAttemptedAt: updatedAt,\n lastError: input.message,\n syncStatus: resolveRecordedSyncStatus(input),\n updatedAt,\n })\n .where(\n and(\n eq(documentMoveIntents.documentId, input.documentId),\n // Blocked/denied rows must stay updatable: a retried intent\n // records its fresh outcome, and a transient failure flips it\n // back to pending instead of freezing it forever.\n inArray(documentMoveIntents.syncStatus, [\n \"pending\",\n \"blocked\",\n \"denied\",\n ]),\n inArray(\n documentMoveIntents.intentType,\n DOCUMENT_PLACEMENT_INTENT_TYPES,\n ),\n ...(input.expectedIntentId\n ? [eq(documentMoveIntents.id, input.expectedIntentId)]\n : []),\n ...(input.expectedUpdatedAt\n ? [eq(documentMoveIntents.updatedAt, input.expectedUpdatedAt)]\n : []),\n ),\n )\n .run();\n },\n input.stillCurrent ?? (() => true),\n { behavior: \"immediate\" },\n );\n },\n /**\n * Evidence for the org-access-restored re-arm gate: a parked\n * permission-denied move proves a queued write was refused.\n */\n async hasDeniedMoveIntents(\n execSql: ExecSql,\n input?: { organizationId?: string },\n ): Promise<boolean> {\n await sqlDocumentMoveIntentPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, [\n ...documentProjectionTables,\n ...containerTables,\n ]);\n const rows = await execSql(\n `SELECT intent.document_id AS document_id\n FROM document_move_intents intent\n ${input?.organizationId ? DENIED_INTENT_ORGANIZATION_JOINS_SQL : \"\"}\n WHERE intent.sync_status = 'denied'\n AND intent.intent_type IN (?, ?)\n ${input?.organizationId ? `AND (${DENIED_INTENT_ORGANIZATION_SQL} = ? OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL)` : \"\"}\n LIMIT 1`,\n input?.organizationId\n ? [...DOCUMENT_PLACEMENT_INTENT_TYPES, input.organizationId]\n : DOCUMENT_PLACEMENT_INTENT_TYPES,\n );\n return rows.length > 0;\n },\n /**\n * Flip parked permission-denied moves back to pending so the re-armed\n * structural pass replays them (access restored, or a manual retry scoped\n * to one document).\n */\n async resetDeniedMoveIntents(\n execSql: ExecSql,\n input?: { localId?: string; organizationId?: string },\n ): Promise<void> {\n await sqlDocumentMoveIntentPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, [\n ...documentProjectionTables,\n ...containerTables,\n ]);\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n // Organization scoping resolves each intent's organization through the\n // container joins above: restoring one organization's access must not\n // un-park moves that another organization still denies. Rows with no\n // resolvable organization are included (device-first shapes).\n await lockedExecSql(\n `UPDATE document_move_intents\n SET sync_status = 'pending', updated_at = ?\n WHERE sync_status = 'denied'\n AND intent_type IN (?, ?)\n ${input?.localId ? \"AND local_id = ?\" : \"\"}\n ${\n input?.organizationId\n ? `AND local_id IN (\n SELECT intent.local_id FROM document_move_intents intent\n ${DENIED_INTENT_ORGANIZATION_JOINS_SQL}\n WHERE ${DENIED_INTENT_ORGANIZATION_SQL} = ?\n OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL\n )`\n : \"\"\n }`,\n [\n new Date().toISOString(),\n ...DOCUMENT_PLACEMENT_INTENT_TYPES,\n ...(input?.localId ? [input.localId] : []),\n ...(input?.organizationId ? [input.organizationId] : []),\n ],\n );\n });\n },\n};\n"]}
1
+ {"version":3,"file":"documentMoveIntentPersistence.js","sourceRoot":"","sources":["documentMoveIntentPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, asc, eq, inArray } from \"drizzle-orm\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n containerTables,\n documentPlacementIntentTables,\n documentProjectionTables,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport {\n enqueueDocumentLinkIntent,\n enqueueDocumentMoveIntent,\n enqueueDocumentUnlinkIntent,\n loadDocumentIntentLinkTargets,\n} from \"./documentPlacementIntentEnqueue\";\n\nexport const DOCUMENT_MOVE_INTENT_TYPE = \"document.move\";\nconst placementLane = inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement);\nexport const DOCUMENT_LINK_INTENT_TYPE = \"document.link\";\nconst DOCUMENT_PLACEMENT_INTENT_TYPES = [\n DOCUMENT_MOVE_INTENT_TYPE,\n DOCUMENT_LINK_INTENT_TYPE,\n];\n\n// Resolves the organization a parked move belongs to, in confidence order:\n// the move's target container, the document's preserved projection\n// attribution, then the container the document currently sits in. Normal\n// saves routinely leave projection attribution empty (deferred-tail\n// reporting resolves through the same container join), so the container rows\n// carry the common case; rows with no resolvable organization stay in every\n// scope (device-first shapes, matching orphan priming).\nconst DENIED_INTENT_ORGANIZATION_SQL = `COALESCE(\n NULLIF(target_container.organization_id, ''),\n NULLIF(projection.organization_id, ''),\n NULLIF(projection_container.organization_id, '')\n)`;\n\nconst DENIED_INTENT_ORGANIZATION_JOINS_SQL = `\n LEFT JOIN containers target_container\n ON target_container.id = intent.target_container_id\n LEFT JOIN document_projection projection\n ON projection.local_id = intent.local_id\n LEFT JOIN containers projection_container\n ON projection_container.id = projection.container_id`;\n\n/**\n * - `pending`: replays on every structural pass.\n * - `blocked`: the last pass could not proceed locally (missing local\n * document / destination); replays, since hydration can heal it.\n * - `denied`: parked on a 403 until the access-restored replay (row 7).\n * - `unavailable`: terminal — after a projection refresh the destination\n * itself is proven deleted (coded 404 on its own projection probe). No\n * mutation 409 is ever terminal: with the destination live it can only\n * name an ancestor or source, and the next pass's fresh paths outrun it.\n * Container ids never return, so no replay can commit the intent as\n * written; it leaves the replay set until the local container tombstone\n * cascade retargets it or the user re-enqueues the move.\n */\nexport type DocumentMoveIntentSyncStatus =\n | \"pending\"\n | \"blocked\"\n | \"denied\"\n | \"unavailable\";\n\nexport interface DocumentMoveIntentRecord {\n additionalLinkContainerIds?: readonly string[] | undefined;\n removedLinkContainerIds?: readonly string[] | undefined;\n id: string;\n documentId: string;\n intentType:\n | typeof DOCUMENT_MOVE_INTENT_TYPE\n | typeof DOCUMENT_LINK_INTENT_TYPE;\n lastAttemptedAt: string | null;\n lastError: string | null;\n localId: string;\n replaceLinkedContainers: boolean;\n sourceContainerId: string | null;\n syncStatus: DocumentMoveIntentSyncStatus;\n targetContainerId: string;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface SelectedDocumentMoveIntentRecord {\n id: string | null;\n documentId: string;\n intentType: string;\n lastAttemptedAt: string | null;\n lastError: string | null;\n localId: string | null;\n replaceLinkedContainers: boolean;\n sourceContainerId: string | null;\n syncStatus: string;\n targetContainerId: string;\n createdAt: string;\n updatedAt: string;\n}\n\nfunction parseDocumentMoveIntentSyncStatus(\n value: unknown,\n): DocumentMoveIntentSyncStatus {\n if (value === \"blocked\" || value === \"denied\" || value === \"unavailable\") {\n return value;\n }\n return \"pending\";\n}\n\nfunction resolveRecordedSyncStatus(input: {\n blocked?: boolean | undefined;\n denied?: boolean | undefined;\n unavailable?: boolean | undefined;\n}): DocumentMoveIntentSyncStatus {\n if (input.unavailable) return \"unavailable\";\n if (input.denied) return \"denied\";\n return input.blocked ? \"blocked\" : \"pending\";\n}\n\nfunction mapDocumentMoveIntentRecord(\n row: SelectedDocumentMoveIntentRecord,\n): DocumentMoveIntentRecord {\n return {\n id: String(row.id ?? \"\"),\n documentId: row.documentId,\n intentType:\n row.intentType === DOCUMENT_LINK_INTENT_TYPE\n ? DOCUMENT_LINK_INTENT_TYPE\n : DOCUMENT_MOVE_INTENT_TYPE,\n lastAttemptedAt: row.lastAttemptedAt,\n lastError: row.lastError,\n // Placement intents always carry their local document row.\n localId: row.localId ?? \"\",\n replaceLinkedContainers: Boolean(row.replaceLinkedContainers),\n sourceContainerId: row.sourceContainerId,\n syncStatus: parseDocumentMoveIntentSyncStatus(row.syncStatus),\n targetContainerId: row.targetContainerId,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport const sqlDocumentMoveIntentPersistence = {\n async ensureSchema(execSql: ExecSql): Promise<void> {\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n },\n\n enqueueMoveIntent: enqueueDocumentMoveIntent,\n enqueueLinkIntent: enqueueDocumentLinkIntent,\n enqueueUnlinkIntent: enqueueDocumentUnlinkIntent,\n\n async listPendingMoveIntents(\n execSql: ExecSql,\n ): Promise<DocumentMoveIntentRecord[]> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureSqlTables(lockedExecSql, documentPlacementIntentTables);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const rows = await tx\n .select({\n id: localIntents.id,\n documentId: localIntents.subjectId,\n intentType: localIntents.intentType,\n lastAttemptedAt: localIntents.lastAttemptedAt,\n lastError: localIntents.lastError,\n localId: localIntents.localId,\n replaceLinkedContainers: localIntents.replaceLinkedContainers,\n sourceContainerId: localIntents.sourceContainerId,\n syncStatus: localIntents.syncStatus,\n targetContainerId: localIntents.targetContainerId,\n createdAt: localIntents.createdAt,\n updatedAt: localIntents.updatedAt,\n })\n .from(localIntents)\n // Blocked intents replay too: \"blocked\" names the reason the last\n // attempt could not proceed (missing local doc / destination), not a\n // terminal verdict. The blocking condition can heal after hydration or\n // recovery, and re-checking is cheap — a still-blocked intent simply\n // re-records its reason without counting as lane progress.\n // Unavailable intents never replay: the server proved a cited container\n // is gone, so every replay would re-issue the same doomed requests\n // (#2278 #4). Only the tombstone cascade or a re-enqueue revives them.\n .where(\n and(\n placementLane,\n inArray(localIntents.syncStatus, [\"pending\", \"blocked\"]),\n inArray(\n localIntents.intentType,\n DOCUMENT_PLACEMENT_INTENT_TYPES,\n ),\n ),\n )\n .orderBy(asc(localIntents.createdAt));\n\n return Promise.all(\n rows.map(async (row) => {\n const targets = await loadDocumentIntentLinkTargets(\n lockedExecSql,\n row.id ?? \"\",\n );\n const added = targets\n .filter((target) => target.operation === \"link\")\n .map((target) => target.containerId)\n .sort();\n const removed = targets\n .filter((target) => target.operation === \"unlink\")\n .map((target) => target.containerId)\n .sort();\n return {\n ...mapDocumentMoveIntentRecord(row),\n ...(added.length ? { additionalLinkContainerIds: added } : {}),\n ...(removed.length ? { removedLinkContainerIds: removed } : {}),\n };\n }),\n );\n },\n );\n });\n },\n\n async markMoveIntentSynced(\n execSql: ExecSql,\n input: {\n documentId: string;\n expectedIntentId?: string | undefined;\n expectedUpdatedAt: string;\n },\n ): Promise<boolean> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const deleted = await lockedExecSql(\n `DELETE FROM local_intents\n WHERE lane = ?\n AND subject_id = ?\n AND intent_type IN (?, ?)\n AND updated_at = ?\n ${input.expectedIntentId ? \"AND id = ?\" : \"\"}\n RETURNING id`,\n [\n LOCAL_INTENT_LANES.documentPlacement,\n input.documentId,\n ...DOCUMENT_PLACEMENT_INTENT_TYPES,\n input.expectedUpdatedAt,\n ...(input.expectedIntentId ? [input.expectedIntentId] : []),\n ],\n );\n for (const { id } of deleted) {\n await getClientSQLitePersistenceRuntime(lockedExecSql)\n .db.delete(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, String(id)))\n .run();\n }\n return deleted.length > 0;\n });\n },\n\n async recordMoveIntentError(\n execSql: ExecSql,\n input: {\n blocked?: boolean | undefined;\n /**\n * A permission denial (403): the intent parks as `denied` — excluded\n * from routine structural replays — until the org-access-restored\n * signal or a manual retry flips it back to pending (edge-case row 7).\n */\n denied?: boolean | undefined;\n documentId: string;\n /** Exact intent revision read by the pass; preferred over wall-clock CAS. */\n expectedIntentId?: string | undefined;\n /**\n * Optimistic concurrency: when given, the error only records against\n * the intent revision the pass read — a re-enqueued move (new\n * updatedAt) is never parked by a stale in-flight failure.\n */\n expectedUpdatedAt?: string | undefined;\n message: string;\n stillCurrent?: (() => boolean) | undefined;\n /**\n * Server proof that a cited container was deleted (coded 404 or\n * `container_unavailable` 409): the intent parks terminally as\n * `unavailable`, outside every replay, until the tombstone cascade\n * retargets it or a re-enqueue replaces it. Outranks `denied`: a\n * restored permission cannot revive a deleted container.\n */\n unavailable?: boolean | undefined;\n },\n ): Promise<void> {\n await getClientSQLitePersistenceRuntime(execSql).guardedTransaction(\n async (db) => {\n const updatedAt = new Date().toISOString();\n await db\n .update(localIntents)\n .set({\n lastAttemptedAt: updatedAt,\n lastError: input.message,\n syncStatus: resolveRecordedSyncStatus(input),\n updatedAt,\n })\n .where(\n and(\n placementLane,\n eq(localIntents.subjectId, input.documentId),\n // Blocked/denied rows must stay updatable: a retried intent\n // records its fresh outcome, and a transient failure flips it\n // back to pending instead of freezing it forever.\n inArray(localIntents.syncStatus, [\n \"pending\",\n \"blocked\",\n \"denied\",\n ]),\n inArray(localIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES),\n ...(input.expectedIntentId\n ? [eq(localIntents.id, input.expectedIntentId)]\n : []),\n ...(input.expectedUpdatedAt\n ? [eq(localIntents.updatedAt, input.expectedUpdatedAt)]\n : []),\n ),\n )\n .run();\n },\n input.stillCurrent ?? (() => true),\n { behavior: \"immediate\" },\n );\n },\n /**\n * Evidence for the org-access-restored re-arm gate: a parked\n * permission-denied move proves a queued write was refused.\n */\n async hasDeniedMoveIntents(\n execSql: ExecSql,\n input?: { organizationId?: string },\n ): Promise<boolean> {\n await sqlDocumentMoveIntentPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, [\n ...documentProjectionTables,\n ...containerTables,\n ]);\n const rows = await execSql(\n `SELECT intent.subject_id AS document_id\n FROM local_intents intent\n ${input?.organizationId ? DENIED_INTENT_ORGANIZATION_JOINS_SQL : \"\"}\n WHERE intent.lane = ?\n AND intent.sync_status = 'denied'\n AND intent.intent_type IN (?, ?)\n ${input?.organizationId ? `AND (${DENIED_INTENT_ORGANIZATION_SQL} = ? OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL)` : \"\"}\n LIMIT 1`,\n [\n LOCAL_INTENT_LANES.documentPlacement,\n ...DOCUMENT_PLACEMENT_INTENT_TYPES,\n ...(input?.organizationId ? [input.organizationId] : []),\n ],\n );\n return rows.length > 0;\n },\n /**\n * Flip parked permission-denied moves back to pending so the re-armed\n * structural pass replays them (access restored, or a manual retry scoped\n * to one document).\n */\n async resetDeniedMoveIntents(\n execSql: ExecSql,\n input?: { localId?: string; organizationId?: string },\n ): Promise<void> {\n await sqlDocumentMoveIntentPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, [\n ...documentProjectionTables,\n ...containerTables,\n ]);\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n // Organization scoping resolves each intent's organization through the\n // container joins above: restoring one organization's access must not\n // un-park moves that another organization still denies. Rows with no\n // resolvable organization are included (device-first shapes).\n await lockedExecSql(\n `UPDATE local_intents\n SET sync_status = 'pending', updated_at = ?\n WHERE lane = ?\n AND sync_status = 'denied'\n AND intent_type IN (?, ?)\n ${input?.localId ? \"AND local_id = ?\" : \"\"}\n ${\n input?.organizationId\n ? `AND local_id IN (\n SELECT intent.local_id FROM local_intents intent\n ${DENIED_INTENT_ORGANIZATION_JOINS_SQL}\n WHERE intent.lane = ?\n AND (${DENIED_INTENT_ORGANIZATION_SQL} = ?\n OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL)\n )`\n : \"\"\n }`,\n [\n new Date().toISOString(),\n LOCAL_INTENT_LANES.documentPlacement,\n ...DOCUMENT_PLACEMENT_INTENT_TYPES,\n ...(input?.localId ? [input.localId] : []),\n ...(input?.organizationId\n ? [LOCAL_INTENT_LANES.documentPlacement, input.organizationId]\n : []),\n ],\n );\n });\n },\n};\n"]}
@@ -1,7 +1,7 @@
1
1
  import { and, eq } from "drizzle-orm";
2
2
  import { uniqueSortedStrings } from "../../documents/shared/readers.js";
3
3
  import { documentIntentLinkTargets } from "../../sqlite/documentPlacementIntentSchema.js";
4
- import { documentContainerProjection, documentContainerProjectionTables, documentMoveIntents, documentMoveIntentTables, } from "../../sqlite/schema.js";
4
+ import { documentContainerProjection, documentContainerProjectionTables, documentPlacementIntentTables, inLocalIntentLane, LOCAL_INTENT_LANES, LOCAL_INTENT_SUBJECT, localIntents, } from "../../sqlite/schema.js";
5
5
  import { getClientSQLitePersistenceRuntime } from "../../sqlite/sqlitePersistenceRuntime.js";
6
6
  import { ensureSqlTables, runSerializedSqlMutation, } from "../../sqlite/sqlSchema.js";
7
7
  export async function loadDocumentIntentLinkTargets(execSql, intentId) {
@@ -19,7 +19,7 @@ async function loadSurvivingMoveTarget(execSql, previous) {
19
19
  const [link] = await getClientSQLitePersistenceRuntime(execSql)
20
20
  .db.select({ containerId: documentContainerProjection.containerId })
21
21
  .from(documentContainerProjection)
22
- .where(and(eq(documentContainerProjection.documentId, previous.documentId), eq(documentContainerProjection.containerId, previous.targetContainerId)))
22
+ .where(and(eq(documentContainerProjection.documentId, previous.subjectId), eq(documentContainerProjection.containerId, previous.targetContainerId)))
23
23
  .limit(1);
24
24
  return link?.containerId ?? null;
25
25
  }
@@ -83,14 +83,14 @@ function resolvePlacement(input, previous, operation) {
83
83
  }
84
84
  async function enqueuePlacementIntent(execSql, input, operation) {
85
85
  await runSerializedSqlMutation(execSql, async (lockedExecSql) => {
86
- await ensureSqlTables(lockedExecSql, documentMoveIntentTables);
86
+ await ensureSqlTables(lockedExecSql, documentPlacementIntentTables);
87
87
  await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);
88
88
  const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);
89
89
  await runtime.transaction(async (tx) => {
90
90
  const [previous] = await tx
91
91
  .select()
92
- .from(documentMoveIntents)
93
- .where(eq(documentMoveIntents.documentId, input.documentId))
92
+ .from(localIntents)
93
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement), eq(localIntents.subjectId, input.documentId)))
94
94
  .limit(1);
95
95
  const previousTargets = previous?.id
96
96
  ? await loadDocumentIntentLinkTargets(lockedExecSql, previous.id)
@@ -105,7 +105,8 @@ async function enqueuePlacementIntent(execSql, input, operation) {
105
105
  // user additions may be replayed, never stale server-discovered links.
106
106
  const next = {
107
107
  id,
108
- documentId: input.documentId,
108
+ lane: LOCAL_INTENT_LANES.documentPlacement,
109
+ subjectId: input.documentId,
109
110
  localId: input.localId,
110
111
  intentType: operation === "move" || previous?.intentType === "document.move"
111
112
  ? "document.move"
@@ -117,10 +118,10 @@ async function enqueuePlacementIntent(execSql, input, operation) {
117
118
  ...resolvePlacement(input, previous, operation),
118
119
  };
119
120
  await tx
120
- .insert(documentMoveIntents)
121
+ .insert(localIntents)
121
122
  .values({ ...next, createdAt: updatedAt })
122
123
  .onConflictDoUpdate({
123
- target: documentMoveIntents.documentId,
124
+ target: LOCAL_INTENT_SUBJECT,
124
125
  set: next,
125
126
  })
126
127
  .run();
@@ -1 +1 @@
1
- {"version":3,"file":"documentPlacementIntentEnqueue.js","sourceRoot":"","sources":["documentPlacementIntentEnqueue.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { uniqueSortedStrings } from \"../../documents/shared/readers\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n documentContainerProjection,\n documentContainerProjectionTables,\n documentMoveIntents,\n documentMoveIntentTables,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\n\ninterface PlacementIntentInput {\n id?: string | undefined;\n documentId: string;\n localId: string;\n replaceLinkedContainers?: boolean | undefined;\n removedContainerId?: string | undefined;\n sourceContainerId?: string | null | undefined;\n targetContainerId: string;\n}\n\nexport async function loadDocumentIntentLinkTargets(\n execSql: ExecSql,\n intentId: string,\n) {\n return getClientSQLitePersistenceRuntime(execSql)\n .db.select({\n containerId: documentIntentLinkTargets.containerId,\n operation: documentIntentLinkTargets.operation,\n })\n .from(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intentId));\n}\n\ntype LinkTarget = { containerId: string; operation: \"link\" | \"unlink\" };\n\nasync function loadSurvivingMoveTarget(\n execSql: ExecSql,\n previous: typeof documentMoveIntents.$inferSelect | undefined,\n): Promise<string | null> {\n if (previous?.intentType !== \"document.move\") return null;\n const [link] = await getClientSQLitePersistenceRuntime(execSql)\n .db.select({ containerId: documentContainerProjection.containerId })\n .from(documentContainerProjection)\n .where(\n and(\n eq(documentContainerProjection.documentId, previous.documentId),\n eq(documentContainerProjection.containerId, previous.targetContainerId),\n ),\n )\n .limit(1);\n return link?.containerId ?? null;\n}\n\nfunction resolveTargets(\n input: PlacementIntentInput,\n previous: LinkTarget[],\n operation: \"move\" | \"link\" | \"unlink\",\n previousMoveTarget: string | null,\n): LinkTarget[] {\n const targets = new Map(\n previous.map((target) => [target.containerId, target.operation]),\n );\n if (operation === \"link\") targets.set(input.targetContainerId, \"link\");\n else if (operation === \"unlink\") {\n if (!input.removedContainerId)\n throw new Error(\"Unlink intent requires a removed container\");\n targets.set(input.removedContainerId, \"unlink\");\n } else if (input.replaceLinkedContainers) targets.clear();\n else {\n if (\n previousMoveTarget &&\n previousMoveTarget !== input.sourceContainerId &&\n previousMoveTarget !== input.targetContainerId &&\n targets.get(previousMoveTarget) !== \"unlink\"\n ) {\n targets.set(previousMoveTarget, \"link\");\n }\n targets.delete(input.targetContainerId);\n if (\n input.sourceContainerId &&\n input.sourceContainerId !== input.targetContainerId\n )\n targets.set(input.sourceContainerId, \"unlink\");\n }\n return uniqueSortedStrings([...targets.keys()]).map((containerId) => ({\n containerId,\n operation: targets.get(containerId) ?? \"link\",\n }));\n}\n\nfunction resolvePlacement(\n input: PlacementIntentInput,\n previous: typeof documentMoveIntents.$inferSelect | undefined,\n operation: \"move\" | \"link\" | \"unlink\",\n) {\n if (operation === \"unlink\") {\n return {\n sourceContainerId:\n previous?.intentType === \"document.move\"\n ? previous.sourceContainerId\n : input.targetContainerId,\n targetContainerId:\n previous?.intentType === \"document.move\" &&\n previous.targetContainerId !== input.removedContainerId\n ? previous.targetContainerId\n : input.targetContainerId,\n replaceLinkedContainers: previous?.replaceLinkedContainers ?? false,\n };\n }\n const linkOnly = operation === \"link\";\n const sourceContainerId =\n previous?.intentType === \"document.move\"\n ? (previous.sourceContainerId ?? input.sourceContainerId ?? null)\n : (input.sourceContainerId ?? null);\n return {\n sourceContainerId: linkOnly\n ? (previous?.sourceContainerId ?? input.sourceContainerId ?? null)\n : sourceContainerId,\n targetContainerId: linkOnly\n ? (previous?.targetContainerId ??\n input.sourceContainerId ??\n input.targetContainerId)\n : input.targetContainerId,\n replaceLinkedContainers:\n previous?.replaceLinkedContainers ||\n (input.replaceLinkedContainers ?? false),\n };\n}\n\nasync function enqueuePlacementIntent(\n execSql: ExecSql,\n input: PlacementIntentInput,\n operation: \"move\" | \"link\" | \"unlink\",\n): Promise<void> {\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureSqlTables(lockedExecSql, documentMoveIntentTables);\n await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n await runtime.transaction(async (tx) => {\n const [previous] = await tx\n .select()\n .from(documentMoveIntents)\n .where(eq(documentMoveIntents.documentId, input.documentId))\n .limit(1);\n const previousTargets = previous?.id\n ? await loadDocumentIntentLinkTargets(lockedExecSql, previous.id)\n : [];\n const targets = resolveTargets(\n input,\n previousTargets,\n operation,\n operation === \"move\"\n ? await loadSurvivingMoveTarget(lockedExecSql, previous)\n : null,\n );\n const id = input.id ?? crypto.randomUUID();\n const updatedAt = new Date().toISOString();\n // An additive intent retains the active placement and any pending move.\n // Its targets are separate from the discovery projection: only explicit\n // user additions may be replayed, never stale server-discovered links.\n const next = {\n id,\n documentId: input.documentId,\n localId: input.localId,\n intentType:\n operation === \"move\" || previous?.intentType === \"document.move\"\n ? \"document.move\"\n : \"document.link\",\n lastAttemptedAt: null,\n lastError: null,\n syncStatus: \"pending\",\n updatedAt,\n ...resolvePlacement(input, previous, operation),\n };\n await tx\n .insert(documentMoveIntents)\n .values({ ...next, createdAt: updatedAt })\n .onConflictDoUpdate({\n target: documentMoveIntents.documentId,\n set: next,\n })\n .run();\n if (previous?.id)\n await tx\n .delete(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, previous.id))\n .run();\n if (targets.length)\n await tx\n .insert(documentIntentLinkTargets)\n .values(targets.map((target) => ({ intentId: id, ...target })))\n .run();\n });\n });\n}\n\nexport function enqueueDocumentMoveIntent(\n execSql: ExecSql,\n input: PlacementIntentInput,\n): Promise<void> {\n return enqueuePlacementIntent(execSql, input, \"move\");\n}\n\nexport function enqueueDocumentLinkIntent(\n execSql: ExecSql,\n input: PlacementIntentInput & { sourceContainerId: string },\n): Promise<void> {\n return enqueuePlacementIntent(execSql, input, \"link\");\n}\n\nexport function enqueueDocumentUnlinkIntent(\n execSql: ExecSql,\n input: PlacementIntentInput & { removedContainerId: string },\n): Promise<void> {\n return enqueuePlacementIntent(execSql, input, \"unlink\");\n}\n"]}
1
+ {"version":3,"file":"documentPlacementIntentEnqueue.js","sourceRoot":"","sources":["documentPlacementIntentEnqueue.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { uniqueSortedStrings } from \"../../documents/shared/readers\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n documentContainerProjection,\n documentContainerProjectionTables,\n documentPlacementIntentTables,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n LOCAL_INTENT_SUBJECT,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\n\ninterface PlacementIntentInput {\n id?: string | undefined;\n documentId: string;\n localId: string;\n replaceLinkedContainers?: boolean | undefined;\n removedContainerId?: string | undefined;\n sourceContainerId?: string | null | undefined;\n targetContainerId: string;\n}\n\nexport async function loadDocumentIntentLinkTargets(\n execSql: ExecSql,\n intentId: string,\n) {\n return getClientSQLitePersistenceRuntime(execSql)\n .db.select({\n containerId: documentIntentLinkTargets.containerId,\n operation: documentIntentLinkTargets.operation,\n })\n .from(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intentId));\n}\n\ntype LinkTarget = { containerId: string; operation: \"link\" | \"unlink\" };\n\nasync function loadSurvivingMoveTarget(\n execSql: ExecSql,\n previous: typeof localIntents.$inferSelect | undefined,\n): Promise<string | null> {\n if (previous?.intentType !== \"document.move\") return null;\n const [link] = await getClientSQLitePersistenceRuntime(execSql)\n .db.select({ containerId: documentContainerProjection.containerId })\n .from(documentContainerProjection)\n .where(\n and(\n eq(documentContainerProjection.documentId, previous.subjectId),\n eq(documentContainerProjection.containerId, previous.targetContainerId),\n ),\n )\n .limit(1);\n return link?.containerId ?? null;\n}\n\nfunction resolveTargets(\n input: PlacementIntentInput,\n previous: LinkTarget[],\n operation: \"move\" | \"link\" | \"unlink\",\n previousMoveTarget: string | null,\n): LinkTarget[] {\n const targets = new Map(\n previous.map((target) => [target.containerId, target.operation]),\n );\n if (operation === \"link\") targets.set(input.targetContainerId, \"link\");\n else if (operation === \"unlink\") {\n if (!input.removedContainerId)\n throw new Error(\"Unlink intent requires a removed container\");\n targets.set(input.removedContainerId, \"unlink\");\n } else if (input.replaceLinkedContainers) targets.clear();\n else {\n if (\n previousMoveTarget &&\n previousMoveTarget !== input.sourceContainerId &&\n previousMoveTarget !== input.targetContainerId &&\n targets.get(previousMoveTarget) !== \"unlink\"\n ) {\n targets.set(previousMoveTarget, \"link\");\n }\n targets.delete(input.targetContainerId);\n if (\n input.sourceContainerId &&\n input.sourceContainerId !== input.targetContainerId\n )\n targets.set(input.sourceContainerId, \"unlink\");\n }\n return uniqueSortedStrings([...targets.keys()]).map((containerId) => ({\n containerId,\n operation: targets.get(containerId) ?? \"link\",\n }));\n}\n\nfunction resolvePlacement(\n input: PlacementIntentInput,\n previous: typeof localIntents.$inferSelect | undefined,\n operation: \"move\" | \"link\" | \"unlink\",\n) {\n if (operation === \"unlink\") {\n return {\n sourceContainerId:\n previous?.intentType === \"document.move\"\n ? previous.sourceContainerId\n : input.targetContainerId,\n targetContainerId:\n previous?.intentType === \"document.move\" &&\n previous.targetContainerId !== input.removedContainerId\n ? previous.targetContainerId\n : input.targetContainerId,\n replaceLinkedContainers: previous?.replaceLinkedContainers ?? false,\n };\n }\n const linkOnly = operation === \"link\";\n const sourceContainerId =\n previous?.intentType === \"document.move\"\n ? (previous.sourceContainerId ?? input.sourceContainerId ?? null)\n : (input.sourceContainerId ?? null);\n return {\n sourceContainerId: linkOnly\n ? (previous?.sourceContainerId ?? input.sourceContainerId ?? null)\n : sourceContainerId,\n targetContainerId: linkOnly\n ? (previous?.targetContainerId ??\n input.sourceContainerId ??\n input.targetContainerId)\n : input.targetContainerId,\n replaceLinkedContainers:\n previous?.replaceLinkedContainers ||\n (input.replaceLinkedContainers ?? false),\n };\n}\n\nasync function enqueuePlacementIntent(\n execSql: ExecSql,\n input: PlacementIntentInput,\n operation: \"move\" | \"link\" | \"unlink\",\n): Promise<void> {\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureSqlTables(lockedExecSql, documentPlacementIntentTables);\n await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n await runtime.transaction(async (tx) => {\n const [previous] = await tx\n .select()\n .from(localIntents)\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n eq(localIntents.subjectId, input.documentId),\n ),\n )\n .limit(1);\n const previousTargets = previous?.id\n ? await loadDocumentIntentLinkTargets(lockedExecSql, previous.id)\n : [];\n const targets = resolveTargets(\n input,\n previousTargets,\n operation,\n operation === \"move\"\n ? await loadSurvivingMoveTarget(lockedExecSql, previous)\n : null,\n );\n const id = input.id ?? crypto.randomUUID();\n const updatedAt = new Date().toISOString();\n // An additive intent retains the active placement and any pending move.\n // Its targets are separate from the discovery projection: only explicit\n // user additions may be replayed, never stale server-discovered links.\n const next = {\n id,\n lane: LOCAL_INTENT_LANES.documentPlacement,\n subjectId: input.documentId,\n localId: input.localId,\n intentType:\n operation === \"move\" || previous?.intentType === \"document.move\"\n ? \"document.move\"\n : \"document.link\",\n lastAttemptedAt: null,\n lastError: null,\n syncStatus: \"pending\",\n updatedAt,\n ...resolvePlacement(input, previous, operation),\n };\n await tx\n .insert(localIntents)\n .values({ ...next, createdAt: updatedAt })\n .onConflictDoUpdate({\n target: LOCAL_INTENT_SUBJECT,\n set: next,\n })\n .run();\n if (previous?.id)\n await tx\n .delete(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, previous.id))\n .run();\n if (targets.length)\n await tx\n .insert(documentIntentLinkTargets)\n .values(targets.map((target) => ({ intentId: id, ...target })))\n .run();\n });\n });\n}\n\nexport function enqueueDocumentMoveIntent(\n execSql: ExecSql,\n input: PlacementIntentInput,\n): Promise<void> {\n return enqueuePlacementIntent(execSql, input, \"move\");\n}\n\nexport function enqueueDocumentLinkIntent(\n execSql: ExecSql,\n input: PlacementIntentInput & { sourceContainerId: string },\n): Promise<void> {\n return enqueuePlacementIntent(execSql, input, \"link\");\n}\n\nexport function enqueueDocumentUnlinkIntent(\n execSql: ExecSql,\n input: PlacementIntentInput & { removedContainerId: string },\n): Promise<void> {\n return enqueuePlacementIntent(execSql, input, \"unlink\");\n}\n"]}
@@ -1,14 +1,14 @@
1
- import { eq } from "drizzle-orm";
2
- import { documentMoveIntents, documentMoveIntentTables, } from "../../sqlite/schema.js";
1
+ import { and, eq } from "drizzle-orm";
2
+ import { documentPlacementIntentTables, inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../sqlite/schema.js";
3
3
  import { getClientSQLitePersistenceRuntime } from "../../sqlite/sqlitePersistenceRuntime.js";
4
4
  import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
5
5
  /** Even denied or unavailable placement intent belongs to the local user. */
6
6
  export async function hasUnsettledDocumentPlacement(execSql, localId) {
7
- await ensureSqlTables(execSql, documentMoveIntentTables);
7
+ await ensureSqlTables(execSql, documentPlacementIntentTables);
8
8
  const [intent] = await getClientSQLitePersistenceRuntime(execSql)
9
- .db.select({ id: documentMoveIntents.id })
10
- .from(documentMoveIntents)
11
- .where(eq(documentMoveIntents.localId, localId))
9
+ .db.select({ id: localIntents.id })
10
+ .from(localIntents)
11
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement), eq(localIntents.localId, localId)))
12
12
  .limit(1);
13
13
  return intent !== undefined;
14
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"documentPurgePlacement.js","sourceRoot":"","sources":["documentPurgePlacement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EACL,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAgB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEvE,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,OAAgB,EAChB,OAAe;IAEf,MAAM,eAAe,CAAC,OAAO,EAAE,wBAAwB,CAAC,CAAC;IACzD,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,iCAAiC,CAAC,OAAO,CAAC;SAC9D,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,mBAAmB,CAAC,EAAE,EAAE,CAAC;SACzC,IAAI,CAAC,mBAAmB,CAAC;SACzB,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;SAC/C,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,MAAM,KAAK,SAAS,CAAC;AAC9B,CAAC","sourcesContent":["import { eq } from \"drizzle-orm\";\nimport {\n documentMoveIntents,\n documentMoveIntentTables,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\n\n/** Even denied or unavailable placement intent belongs to the local user. */\nexport async function hasUnsettledDocumentPlacement(\n execSql: ExecSql,\n localId: string,\n): Promise<boolean> {\n await ensureSqlTables(execSql, documentMoveIntentTables);\n const [intent] = await getClientSQLitePersistenceRuntime(execSql)\n .db.select({ id: documentMoveIntents.id })\n .from(documentMoveIntents)\n .where(eq(documentMoveIntents.localId, localId))\n .limit(1);\n return intent !== undefined;\n}\n"]}
1
+ {"version":3,"file":"documentPurgePlacement.js","sourceRoot":"","sources":["documentPurgePlacement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAgB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEvE,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,OAAgB,EAChB,OAAe;IAEf,MAAM,eAAe,CAAC,OAAO,EAAE,6BAA6B,CAAC,CAAC;IAC9D,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,iCAAiC,CAAC,OAAO,CAAC;SAC9D,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,EAAE,EAAE,CAAC;SAClC,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CACJ,GAAG,CACD,iBAAiB,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,EACvD,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAClC,CACF;SACA,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,MAAM,KAAK,SAAS,CAAC;AAC9B,CAAC","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport {\n documentPlacementIntentTables,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\n\n/** Even denied or unavailable placement intent belongs to the local user. */\nexport async function hasUnsettledDocumentPlacement(\n execSql: ExecSql,\n localId: string,\n): Promise<boolean> {\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n const [intent] = await getClientSQLitePersistenceRuntime(execSql)\n .db.select({ id: localIntents.id })\n .from(localIntents)\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n eq(localIntents.localId, localId),\n ),\n )\n .limit(1);\n return intent !== undefined;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { and, eq, inArray, sql } from "drizzle-orm";
2
- import { documentHistoryCheckpoints, documentHistoryUpdates, documentSyncFailures, documents, dormantContainerMetadata, } from "../../sqlite/schema.js";
2
+ import { documentHistoryCheckpoints, documentHistoryUpdates, documents, dormantContainerMetadata, } from "../../sqlite/schema.js";
3
3
  export const CONTAINER_METADATA_APP_KIND = "container-metadata";
4
4
  export async function retainDormantContainerMetadataInTransaction(tx, retained) {
5
5
  if (retained.length === 0) {
@@ -44,10 +44,6 @@ export async function deleteContainerMetadataDocumentRowsInTransaction(tx, conta
44
44
  .delete(documentHistoryUpdates)
45
45
  .where(and(eq(documentHistoryUpdates.appKind, CONTAINER_METADATA_APP_KIND), inArray(documentHistoryUpdates.localId, ids)))
46
46
  .run();
47
- await tx
48
- .delete(documentSyncFailures)
49
- .where(and(eq(documentSyncFailures.appKind, CONTAINER_METADATA_APP_KIND), inArray(documentSyncFailures.localId, ids)))
50
- .run();
51
47
  await clearDormantContainerMetadataInTransaction(tx, ids);
52
48
  }
53
49
  //# sourceMappingURL=dormantContainerMetadata.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dormantContainerMetadata.js","sourceRoot":"","sources":["dormantContainerMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,EACpB,SAAS,EACT,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,CAAC,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAQhE,MAAM,CAAC,KAAK,UAAU,2CAA2C,CAC/D,EAAgC,EAChC,QAA0D;IAE1D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO;IACT,CAAC;IAED,MAAM,EAAE;SACL,MAAM,CAAC,wBAAwB,CAAC;SAChC,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;SACrB,kBAAkB,CAAC;QAClB,MAAM,EAAE,wBAAwB,CAAC,WAAW;QAC5C,GAAG,EAAE;YACH,cAAc,EAAE,GAAG,CAAA,0BAA0B;YAC7C,UAAU,EAAE,GAAG,CAAA,sBAAsB;SACtC;KACF,CAAC;SACD,GAAG,EAAE,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0CAA0C,CAC9D,EAAgC,EAChC,YAAmC;IAEnC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IACD,MAAM,EAAE;SACL,MAAM,CAAC,wBAAwB,CAAC;SAChC,KAAK,CAAC,OAAO,CAAC,wBAAwB,CAAC,WAAW,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;SACvE,GAAG,EAAE,CAAC;AACX,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,gDAAgD,CACpE,EAAgC,EAChC,YAAmC;IAEnC,MAAM,GAAG,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC;IAC9B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IACD,MAAM,EAAE;SACL,MAAM,CAAC,SAAS,CAAC;SACjB,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,2BAA2B,CAAC,EAClD,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAChC,CACF;SACA,GAAG,EAAE,CAAC;IACT,MAAM,EAAE;SACL,MAAM,CAAC,0BAA0B,CAAC;SAClC,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,CAAC,EACnE,OAAO,CAAC,0BAA0B,CAAC,OAAO,EAAE,GAAG,CAAC,CACjD,CACF;SACA,GAAG,EAAE,CAAC;IACT,MAAM,EAAE;SACL,MAAM,CAAC,sBAAsB,CAAC;SAC9B,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,sBAAsB,CAAC,OAAO,EAAE,2BAA2B,CAAC,EAC/D,OAAO,CAAC,sBAAsB,CAAC,OAAO,EAAE,GAAG,CAAC,CAC7C,CACF;SACA,GAAG,EAAE,CAAC;IACT,MAAM,EAAE;SACL,MAAM,CAAC,oBAAoB,CAAC;SAC5B,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,CAAC,EAC7D,OAAO,CAAC,oBAAoB,CAAC,OAAO,EAAE,GAAG,CAAC,CAC3C,CACF;SACA,GAAG,EAAE,CAAC;IACT,MAAM,0CAA0C,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC","sourcesContent":["import { and, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n documentHistoryCheckpoints,\n documentHistoryUpdates,\n documentSyncFailures,\n documents,\n dormantContainerMetadata,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\nexport const CONTAINER_METADATA_APP_KIND = \"container-metadata\";\n\ninterface DormantContainerMetadataRetention {\n readonly containerId: string;\n readonly organizationId: string;\n readonly retainedAt: string;\n}\n\nexport async function retainDormantContainerMetadataInTransaction(\n tx: ClientSQLiteTransactionScope,\n retained: ReadonlyArray<DormantContainerMetadataRetention>,\n): Promise<void> {\n if (retained.length === 0) {\n return;\n }\n\n await tx\n .insert(dormantContainerMetadata)\n .values([...retained])\n .onConflictDoUpdate({\n target: dormantContainerMetadata.containerId,\n set: {\n organizationId: sql`excluded.organization_id`,\n retainedAt: sql`excluded.retained_at`,\n },\n })\n .run();\n}\n\nexport async function clearDormantContainerMetadataInTransaction(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n if (containerIds.length === 0) {\n return;\n }\n await tx\n .delete(dormantContainerMetadata)\n .where(inArray(dormantContainerMetadata.containerId, [...containerIds]))\n .run();\n}\n\n/** Delete every durable row owned by a container-metadata document scope. */\nexport async function deleteContainerMetadataDocumentRowsInTransaction(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n const ids = [...containerIds];\n if (ids.length === 0) {\n return;\n }\n await tx\n .delete(documents)\n .where(\n and(\n eq(documents.appKind, CONTAINER_METADATA_APP_KIND),\n inArray(documents.localId, ids),\n ),\n )\n .run();\n await tx\n .delete(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, CONTAINER_METADATA_APP_KIND),\n inArray(documentHistoryCheckpoints.localId, ids),\n ),\n )\n .run();\n await tx\n .delete(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, CONTAINER_METADATA_APP_KIND),\n inArray(documentHistoryUpdates.localId, ids),\n ),\n )\n .run();\n await tx\n .delete(documentSyncFailures)\n .where(\n and(\n eq(documentSyncFailures.appKind, CONTAINER_METADATA_APP_KIND),\n inArray(documentSyncFailures.localId, ids),\n ),\n )\n .run();\n await clearDormantContainerMetadataInTransaction(tx, ids);\n}\n"]}
1
+ {"version":3,"file":"dormantContainerMetadata.js","sourceRoot":"","sources":["dormantContainerMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,sBAAsB,EACtB,SAAS,EACT,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,CAAC,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAQhE,MAAM,CAAC,KAAK,UAAU,2CAA2C,CAC/D,EAAgC,EAChC,QAA0D;IAE1D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO;IACT,CAAC;IAED,MAAM,EAAE;SACL,MAAM,CAAC,wBAAwB,CAAC;SAChC,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;SACrB,kBAAkB,CAAC;QAClB,MAAM,EAAE,wBAAwB,CAAC,WAAW;QAC5C,GAAG,EAAE;YACH,cAAc,EAAE,GAAG,CAAA,0BAA0B;YAC7C,UAAU,EAAE,GAAG,CAAA,sBAAsB;SACtC;KACF,CAAC;SACD,GAAG,EAAE,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0CAA0C,CAC9D,EAAgC,EAChC,YAAmC;IAEnC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IACD,MAAM,EAAE;SACL,MAAM,CAAC,wBAAwB,CAAC;SAChC,KAAK,CAAC,OAAO,CAAC,wBAAwB,CAAC,WAAW,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;SACvE,GAAG,EAAE,CAAC;AACX,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,gDAAgD,CACpE,EAAgC,EAChC,YAAmC;IAEnC,MAAM,GAAG,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC;IAC9B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IACD,MAAM,EAAE;SACL,MAAM,CAAC,SAAS,CAAC;SACjB,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,2BAA2B,CAAC,EAClD,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAChC,CACF;SACA,GAAG,EAAE,CAAC;IACT,MAAM,EAAE;SACL,MAAM,CAAC,0BAA0B,CAAC;SAClC,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,CAAC,EACnE,OAAO,CAAC,0BAA0B,CAAC,OAAO,EAAE,GAAG,CAAC,CACjD,CACF;SACA,GAAG,EAAE,CAAC;IACT,MAAM,EAAE;SACL,MAAM,CAAC,sBAAsB,CAAC;SAC9B,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,sBAAsB,CAAC,OAAO,EAAE,2BAA2B,CAAC,EAC/D,OAAO,CAAC,sBAAsB,CAAC,OAAO,EAAE,GAAG,CAAC,CAC7C,CACF;SACA,GAAG,EAAE,CAAC;IACT,MAAM,0CAA0C,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC","sourcesContent":["import { and, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n documentHistoryCheckpoints,\n documentHistoryUpdates,\n documents,\n dormantContainerMetadata,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\nexport const CONTAINER_METADATA_APP_KIND = \"container-metadata\";\n\ninterface DormantContainerMetadataRetention {\n readonly containerId: string;\n readonly organizationId: string;\n readonly retainedAt: string;\n}\n\nexport async function retainDormantContainerMetadataInTransaction(\n tx: ClientSQLiteTransactionScope,\n retained: ReadonlyArray<DormantContainerMetadataRetention>,\n): Promise<void> {\n if (retained.length === 0) {\n return;\n }\n\n await tx\n .insert(dormantContainerMetadata)\n .values([...retained])\n .onConflictDoUpdate({\n target: dormantContainerMetadata.containerId,\n set: {\n organizationId: sql`excluded.organization_id`,\n retainedAt: sql`excluded.retained_at`,\n },\n })\n .run();\n}\n\nexport async function clearDormantContainerMetadataInTransaction(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n if (containerIds.length === 0) {\n return;\n }\n await tx\n .delete(dormantContainerMetadata)\n .where(inArray(dormantContainerMetadata.containerId, [...containerIds]))\n .run();\n}\n\n/** Delete every durable row owned by a container-metadata document scope. */\nexport async function deleteContainerMetadataDocumentRowsInTransaction(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n const ids = [...containerIds];\n if (ids.length === 0) {\n return;\n }\n await tx\n .delete(documents)\n .where(\n and(\n eq(documents.appKind, CONTAINER_METADATA_APP_KIND),\n inArray(documents.localId, ids),\n ),\n )\n .run();\n await tx\n .delete(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, CONTAINER_METADATA_APP_KIND),\n inArray(documentHistoryCheckpoints.localId, ids),\n ),\n )\n .run();\n await tx\n .delete(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, CONTAINER_METADATA_APP_KIND),\n inArray(documentHistoryUpdates.localId, ids),\n ),\n )\n .run();\n await clearDormantContainerMetadataInTransaction(tx, ids);\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { and, eq } from "drizzle-orm";
2
- import { containerCreateIntents, containerCreateIntentTables, } from "../../sqlite/schema.js";
2
+ import { inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, localIntentTables, } from "../../sqlite/schema.js";
3
3
  import { getClientSQLitePersistenceRuntime } from "../../sqlite/sqlitePersistenceRuntime.js";
4
4
  import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
5
5
  import { CONTAINER_CREATE_INTENT_TYPE } from "./containerContentsPersistenceTypes.js";
@@ -9,12 +9,12 @@ import { CONTAINER_CREATE_INTENT_TYPE } from "./containerContentsPersistenceType
9
9
  * until the create settles.
10
10
  */
11
11
  export async function isContainerCreatePending(execSql, containerId) {
12
- await ensureSqlTables(execSql, containerCreateIntentTables);
12
+ await ensureSqlTables(execSql, localIntentTables);
13
13
  const { db } = getClientSQLitePersistenceRuntime(execSql);
14
14
  const [pending] = await db
15
- .select({ containerId: containerCreateIntents.containerId })
16
- .from(containerCreateIntents)
17
- .where(and(eq(containerCreateIntents.containerId, containerId), eq(containerCreateIntents.intentType, CONTAINER_CREATE_INTENT_TYPE), eq(containerCreateIntents.syncStatus, "pending")))
15
+ .select({ containerId: localIntents.subjectId })
16
+ .from(localIntents)
17
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate), eq(localIntents.subjectId, containerId), eq(localIntents.intentType, CONTAINER_CREATE_INTENT_TYPE), eq(localIntents.syncStatus, "pending")))
18
18
  .limit(1);
19
19
  return pending !== undefined;
20
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pendingContainerCreate.js","sourceRoot":"","sources":["pendingContainerCreate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACL,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAgB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAEnF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAgB,EAChB,WAAmB;IAEnB,MAAM,eAAe,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC;IAC5D,MAAM,EAAE,EAAE,EAAE,GAAG,iCAAiC,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE;SACvB,MAAM,CAAC,EAAE,WAAW,EAAE,sBAAsB,CAAC,WAAW,EAAE,CAAC;SAC3D,IAAI,CAAC,sBAAsB,CAAC;SAC5B,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,sBAAsB,CAAC,WAAW,EAAE,WAAW,CAAC,EACnD,EAAE,CAAC,sBAAsB,CAAC,UAAU,EAAE,4BAA4B,CAAC,EACnE,EAAE,CAAC,sBAAsB,CAAC,UAAU,EAAE,SAAS,CAAC,CACjD,CACF;SACA,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,OAAO,KAAK,SAAS,CAAC;AAC/B,CAAC","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport {\n containerCreateIntents,\n containerCreateIntentTables,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { CONTAINER_CREATE_INTENT_TYPE } from \"./containerContentsPersistenceTypes\";\n\n/**\n * A folder whose create has not settled can carry a listed identity that\n * adoption has not verified, or has refused; nothing is created inside it\n * until the create settles.\n */\nexport async function isContainerCreatePending(\n execSql: ExecSql,\n containerId: string,\n): Promise<boolean> {\n await ensureSqlTables(execSql, containerCreateIntentTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [pending] = await db\n .select({ containerId: containerCreateIntents.containerId })\n .from(containerCreateIntents)\n .where(\n and(\n eq(containerCreateIntents.containerId, containerId),\n eq(containerCreateIntents.intentType, CONTAINER_CREATE_INTENT_TYPE),\n eq(containerCreateIntents.syncStatus, \"pending\"),\n ),\n )\n .limit(1);\n return pending !== undefined;\n}\n"]}
1
+ {"version":3,"file":"pendingContainerCreate.js","sourceRoot":"","sources":["pendingContainerCreate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAgB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAEnF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAgB,EAChB,WAAmB;IAEnB,MAAM,eAAe,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IAClD,MAAM,EAAE,EAAE,EAAE,GAAG,iCAAiC,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE;SACvB,MAAM,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC;SAC/C,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CACJ,GAAG,CACD,iBAAiB,CAAC,kBAAkB,CAAC,eAAe,CAAC,EACrD,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,WAAW,CAAC,EACvC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,4BAA4B,CAAC,EACzD,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,SAAS,CAAC,CACvC,CACF;SACA,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,OAAO,KAAK,SAAS,CAAC;AAC/B,CAAC","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport {\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n localIntentTables,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { CONTAINER_CREATE_INTENT_TYPE } from \"./containerContentsPersistenceTypes\";\n\n/**\n * A folder whose create has not settled can carry a listed identity that\n * adoption has not verified, or has refused; nothing is created inside it\n * until the create settles.\n */\nexport async function isContainerCreatePending(\n execSql: ExecSql,\n containerId: string,\n): Promise<boolean> {\n await ensureSqlTables(execSql, localIntentTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [pending] = await db\n .select({ containerId: localIntents.subjectId })\n .from(localIntents)\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate),\n eq(localIntents.subjectId, containerId),\n eq(localIntents.intentType, CONTAINER_CREATE_INTENT_TYPE),\n eq(localIntents.syncStatus, \"pending\"),\n ),\n )\n .limit(1);\n return pending !== undefined;\n}\n"]}