@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,79 +1,68 @@
1
1
  import { and, eq, isNotNull } from "drizzle-orm";
2
- import { containerCreateIntents, containerCreateIntentTables, containerMoveIntents, containerMoveIntentTables, documentMoveIntents, documentMoveIntentTables, documentSyncFailures, documentTables, } from "./schema.js";
3
- import { getClientSQLitePersistenceRuntime } from "./sqlitePersistenceRuntime.js";
2
+ import { documents, documentTables, localIntents, localIntentTables, } from "./schema.js";
3
+ import { getClientSQLitePersistenceRuntime, } from "./sqlitePersistenceRuntime.js";
4
4
  import { ensureSqlTables } from "./sqlSchema.js";
5
+ /** The `documents` column values of a scope with no recorded failure. */
6
+ export const CLEARED_DOCUMENT_SYNC_FAILURE = {
7
+ syncFailureAttemptedAt: null,
8
+ syncFailureMessage: null,
9
+ syncFailureStatus: null,
10
+ };
11
+ function scopeWhere(scope) {
12
+ return and(eq(documents.appKind, scope.appKind), eq(documents.localId, scope.localId));
13
+ }
5
14
  /**
6
- * Upsert the last terminal failure or incoming-update quarantine for a document
7
- * scope. The durable pending-update queue has no status columns, so this row is
8
- * what lets the write-queue view explain a blocked document. One row per scope:
9
- * a newer failure replaces the old.
15
+ * Record the last terminal failure or incoming-update quarantine on a document
16
+ * scope's row. The durable pending-update queue has no status columns, so
17
+ * these columns let the write-queue view explain a blocked document. A newer
18
+ * failure replaces the old. A scope with no `documents` row has nothing left
19
+ * to explain, so the update is a no-op there.
10
20
  */
11
21
  export async function recordDocumentSyncFailure(execSql, scope, failure) {
12
22
  await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {
13
23
  await db
14
- .insert(documentSyncFailures)
15
- .values({
16
- appKind: scope.appKind,
17
- localId: scope.localId,
18
- status: failure.status,
19
- message: failure.message,
20
- attemptedAt: failure.attemptedAt,
21
- })
22
- .onConflictDoUpdate({
23
- target: [documentSyncFailures.appKind, documentSyncFailures.localId],
24
- set: {
25
- status: failure.status,
26
- message: failure.message,
27
- attemptedAt: failure.attemptedAt,
28
- },
24
+ .update(documents)
25
+ .set({
26
+ syncFailureAttemptedAt: failure.attemptedAt,
27
+ syncFailureMessage: failure.message,
28
+ syncFailureStatus: failure.status,
29
29
  })
30
+ .where(scopeWhere(scope))
30
31
  .run();
31
32
  });
32
33
  }
33
34
  /**
34
- * Whether any queued local write has recorded a terminal failure — a
35
- * `document_sync_failures` row or an intent row carrying `last_error`. This is
35
+ * Whether any queued local write has recorded a terminal failure — a document
36
+ * row carrying a sync failure or an intent row carrying `last_error`. This is
36
37
  * the evidence gate for recovery triggers (e.g. re-arming write lanes when org
37
38
  * access is restored): without it a transient denial during bootstrap would
38
39
  * fire recovery work with nothing to recover, racing the startup sync passes.
39
40
  */
40
41
  export async function hasRecordedTerminalSyncFailures(execSql) {
41
- await ensureSqlTables(execSql, [
42
- ...documentTables,
43
- ...containerCreateIntentTables,
44
- ...containerMoveIntentTables,
45
- ...documentMoveIntentTables,
46
- ]);
42
+ await ensureSqlTables(execSql, [...documentTables, ...localIntentTables]);
47
43
  const { db } = getClientSQLitePersistenceRuntime(execSql);
48
44
  const [failure] = await db
49
- .select({ localId: documentSyncFailures.localId })
50
- .from(documentSyncFailures)
45
+ .select({ localId: documents.localId })
46
+ .from(documents)
47
+ .where(isNotNull(documents.syncFailureMessage))
51
48
  .limit(1);
52
49
  if (failure) {
53
50
  return true;
54
51
  }
55
- const [createIntent] = await db
56
- .select({ id: containerCreateIntents.id })
57
- .from(containerCreateIntents)
58
- .where(isNotNull(containerCreateIntents.lastError))
59
- .limit(1);
60
- if (createIntent) {
61
- return true;
62
- }
63
- const [moveIntent] = await db
64
- .select({ id: containerMoveIntents.id })
65
- .from(containerMoveIntents)
66
- .where(isNotNull(containerMoveIntents.lastError))
52
+ const [intent] = await db
53
+ .select({ id: localIntents.id })
54
+ .from(localIntents)
55
+ .where(isNotNull(localIntents.lastError))
67
56
  .limit(1);
68
- if (moveIntent) {
69
- return true;
70
- }
71
- const [documentMoveIntent] = await db
72
- .select({ id: documentMoveIntents.id })
73
- .from(documentMoveIntents)
74
- .where(isNotNull(documentMoveIntents.lastError))
75
- .limit(1);
76
- return documentMoveIntent !== undefined;
57
+ return intent !== undefined;
58
+ }
59
+ /** Clear a scope's recorded failure inside a caller's transaction. */
60
+ export async function clearDocumentSyncFailureInTransaction(tx, scope) {
61
+ await tx
62
+ .update(documents)
63
+ .set(CLEARED_DOCUMENT_SYNC_FAILURE)
64
+ .where(scopeWhere(scope))
65
+ .run();
77
66
  }
78
67
  /**
79
68
  * Drop the recorded failure for a scope. Called when a later sync pass for the
@@ -83,8 +72,9 @@ export async function hasRecordedTerminalSyncFailures(execSql) {
83
72
  export async function clearDocumentSyncFailure(execSql, scope) {
84
73
  await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {
85
74
  await db
86
- .delete(documentSyncFailures)
87
- .where(and(eq(documentSyncFailures.appKind, scope.appKind), eq(documentSyncFailures.localId, scope.localId)))
75
+ .update(documents)
76
+ .set(CLEARED_DOCUMENT_SYNC_FAILURE)
77
+ .where(scopeWhere(scope))
88
78
  .run();
89
79
  });
90
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"documentSyncFailurePersistence.js","sourceRoot":"","sources":["documentSyncFailurePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNotNull } from \"drizzle-orm\";\nimport type { DocumentScope } from \"./documentPersistenceTypes\";\nimport {\n containerCreateIntents,\n containerCreateIntentTables,\n containerMoveIntents,\n containerMoveIntentTables,\n documentMoveIntents,\n documentMoveIntentTables,\n documentSyncFailures,\n documentTables,\n} from \"./schema\";\nimport { getClientSQLitePersistenceRuntime } from \"./sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"./sqlSchema\";\n\ninterface DocumentSyncFailureFields {\n readonly attemptedAt: string;\n readonly message: string;\n readonly status: number | null;\n}\n\n/**\n * Upsert the last terminal failure or incoming-update quarantine for a document\n * scope. The durable pending-update queue has no status columns, so this row is\n * what lets the write-queue view explain a blocked document. One row per scope:\n * a newer failure replaces the old.\n */\nexport async function recordDocumentSyncFailure(\n execSql: ExecSql,\n scope: DocumentScope,\n failure: DocumentSyncFailureFields,\n): Promise<void> {\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n await db\n .insert(documentSyncFailures)\n .values({\n appKind: scope.appKind,\n localId: scope.localId,\n status: failure.status,\n message: failure.message,\n attemptedAt: failure.attemptedAt,\n })\n .onConflictDoUpdate({\n target: [documentSyncFailures.appKind, documentSyncFailures.localId],\n set: {\n status: failure.status,\n message: failure.message,\n attemptedAt: failure.attemptedAt,\n },\n })\n .run();\n });\n}\n\n/**\n * Whether any queued local write has recorded a terminal failure — a\n * `document_sync_failures` row or an intent row carrying `last_error`. This is\n * the evidence gate for recovery triggers (e.g. re-arming write lanes when org\n * access is restored): without it a transient denial during bootstrap would\n * fire recovery work with nothing to recover, racing the startup sync passes.\n */\nexport async function hasRecordedTerminalSyncFailures(\n execSql: ExecSql,\n): Promise<boolean> {\n await ensureSqlTables(execSql, [\n ...documentTables,\n ...containerCreateIntentTables,\n ...containerMoveIntentTables,\n ...documentMoveIntentTables,\n ]);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [failure] = await db\n .select({ localId: documentSyncFailures.localId })\n .from(documentSyncFailures)\n .limit(1);\n if (failure) {\n return true;\n }\n const [createIntent] = await db\n .select({ id: containerCreateIntents.id })\n .from(containerCreateIntents)\n .where(isNotNull(containerCreateIntents.lastError))\n .limit(1);\n if (createIntent) {\n return true;\n }\n const [moveIntent] = await db\n .select({ id: containerMoveIntents.id })\n .from(containerMoveIntents)\n .where(isNotNull(containerMoveIntents.lastError))\n .limit(1);\n if (moveIntent) {\n return true;\n }\n const [documentMoveIntent] = await db\n .select({ id: documentMoveIntents.id })\n .from(documentMoveIntents)\n .where(isNotNull(documentMoveIntents.lastError))\n .limit(1);\n return documentMoveIntent !== undefined;\n}\n\n/**\n * Drop the recorded failure for a scope. Called when a later sync pass for the\n * scope succeeds and whenever the scope's local sync state is torn down, so a\n * stale failure never outlives the condition it describes.\n */\nexport async function clearDocumentSyncFailure(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<void> {\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n await db\n .delete(documentSyncFailures)\n .where(\n and(\n eq(documentSyncFailures.appKind, scope.appKind),\n eq(documentSyncFailures.localId, scope.localId),\n ),\n )\n .run();\n });\n}\n"]}
1
+ {"version":3,"file":"documentSyncFailurePersistence.js","sourceRoot":"","sources":["documentSyncFailurePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNotNull } from \"drizzle-orm\";\nimport type { DocumentScope } from \"./documentPersistenceTypes\";\nimport {\n documents,\n documentTables,\n localIntents,\n localIntentTables,\n} from \"./schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"./sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"./sqlSchema\";\n\ninterface DocumentSyncFailureFields {\n readonly attemptedAt: string;\n readonly message: string;\n readonly status: number | null;\n}\n\n/** The `documents` column values of a scope with no recorded failure. */\nexport const CLEARED_DOCUMENT_SYNC_FAILURE = {\n syncFailureAttemptedAt: null,\n syncFailureMessage: null,\n syncFailureStatus: null,\n} as const;\n\nfunction scopeWhere(scope: DocumentScope) {\n return and(\n eq(documents.appKind, scope.appKind),\n eq(documents.localId, scope.localId),\n );\n}\n\n/**\n * Record the last terminal failure or incoming-update quarantine on a document\n * scope's row. The durable pending-update queue has no status columns, so\n * these columns let the write-queue view explain a blocked document. A newer\n * failure replaces the old. A scope with no `documents` row has nothing left\n * to explain, so the update is a no-op there.\n */\nexport async function recordDocumentSyncFailure(\n execSql: ExecSql,\n scope: DocumentScope,\n failure: DocumentSyncFailureFields,\n): Promise<void> {\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n await db\n .update(documents)\n .set({\n syncFailureAttemptedAt: failure.attemptedAt,\n syncFailureMessage: failure.message,\n syncFailureStatus: failure.status,\n })\n .where(scopeWhere(scope))\n .run();\n });\n}\n\n/**\n * Whether any queued local write has recorded a terminal failure — a document\n * row carrying a sync failure or an intent row carrying `last_error`. This is\n * the evidence gate for recovery triggers (e.g. re-arming write lanes when org\n * access is restored): without it a transient denial during bootstrap would\n * fire recovery work with nothing to recover, racing the startup sync passes.\n */\nexport async function hasRecordedTerminalSyncFailures(\n execSql: ExecSql,\n): Promise<boolean> {\n await ensureSqlTables(execSql, [...documentTables, ...localIntentTables]);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [failure] = await db\n .select({ localId: documents.localId })\n .from(documents)\n .where(isNotNull(documents.syncFailureMessage))\n .limit(1);\n if (failure) {\n return true;\n }\n const [intent] = await db\n .select({ id: localIntents.id })\n .from(localIntents)\n .where(isNotNull(localIntents.lastError))\n .limit(1);\n return intent !== undefined;\n}\n\n/** Clear a scope's recorded failure inside a caller's transaction. */\nexport async function clearDocumentSyncFailureInTransaction(\n tx: ClientSQLiteTransactionScope,\n scope: DocumentScope,\n): Promise<void> {\n await tx\n .update(documents)\n .set(CLEARED_DOCUMENT_SYNC_FAILURE)\n .where(scopeWhere(scope))\n .run();\n}\n\n/**\n * Drop the recorded failure for a scope. Called when a later sync pass for the\n * scope succeeds and whenever the scope's local sync state is torn down, so a\n * stale failure never outlives the condition it describes.\n */\nexport async function clearDocumentSyncFailure(\n execSql: ExecSql,\n scope: DocumentScope,\n): Promise<void> {\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n await db\n .update(documents)\n .set(CLEARED_DOCUMENT_SYNC_FAILURE)\n .where(scopeWhere(scope))\n .run();\n });\n}\n"]}
@@ -1,39 +1,61 @@
1
+ import { type SQL } from "drizzle-orm";
1
2
  /**
2
- * Device-first document moves waiting for remote link-set replay.
3
+ * Lanes of the durable local outbox. Each lane keeps at most one intent per
4
+ * subject: a container may hold a create and a move at once, while a
5
+ * document's move and link coalesce into one placement intent.
6
+ */
7
+ export declare const LOCAL_INTENT_LANES: {
8
+ readonly containerCreate: "container.create";
9
+ readonly containerMove: "container.move";
10
+ readonly documentPlacement: "document.placement";
11
+ };
12
+ export type LocalIntentLane = (typeof LOCAL_INTENT_LANES)[keyof typeof LOCAL_INTENT_LANES];
13
+ /**
14
+ * Device-first structural writes waiting for remote replay: container creates,
15
+ * container moves, and document placement (move and link) intents.
3
16
  *
4
- * A document move is applied to local projections immediately, then replayed as
5
- * remote link/unlink mutations when writer context is available. Replays are
6
- * rebuilt from current writer projections so the queue stores only intent.
17
+ * Every lane shares one revision protocol: `id` is rewritten on each enqueue or
18
+ * repair, settlement compares `(id, updatedAt)` before acting, and replay runs
19
+ * in `createdAt` order. Remote reset deletes the scoped intents of every lane
20
+ * (and re-queues creates for surviving containers); nothing here survives a
21
+ * reset. Durable requests that must survive reset (the principal mutation
22
+ * journal and organization provisioning attempts) live in their own tables.
7
23
  *
8
24
  * Columns:
9
- * - `id`: Client-generated intent id.
10
- * - `localId`: Local document row to update after replay.
11
- * - `documentId`: Server document id whose link set should move.
12
- * - `targetContainerId`: Desired active/final container id.
13
- * - `sourceContainerId`: Original active container to unlink for move-style
14
- * intents. Replace-style intents unlink every remote container except target.
15
- * - `replaceLinkedContainers`: Whether the final remote link set should be
25
+ * - `id`: Revision token and primary key.
26
+ * - `lane`: One of `LOCAL_INTENT_LANES`.
27
+ * - `intentType`: `container.create`, `container.move`, `document.move`, or
28
+ * `document.link`. A placement intent switches between the last two in place.
29
+ * - `subjectId`: Container id for container lanes, server document id for the
30
+ * placement lane.
31
+ * - `localId`: Local document row the placement intent updates; null for
32
+ * container lanes.
33
+ * - `targetContainerId`: Desired parent (container lanes) or target container
34
+ * (placement lane).
35
+ * - `sourceContainerId`: Parent observed when a container move was first
36
+ * queued, or the source container a document move unlinks.
37
+ * - `replaceLinkedContainers`: Placement only — the final remote link set is
16
38
  * only the target container.
17
- * - `intentType`: `document.move` requests placement; `document.link` requests
18
- * only explicit additions/removals, even if preferred containers match.
19
- * - `syncStatus`: Current sync state, currently `pending` or `blocked`.
20
- * - `lastError`: Last sync error message for retry/debug display.
21
- * - `lastAttemptedAt`: Timestamp of the last replay attempt.
22
- * - `createdAt`: Intent creation timestamp used for FIFO sync.
23
- * - `updatedAt`: Local timestamp for the latest target/status/error change.
39
+ * - `remoteContainerId`, `remoteMetadataDocumentId`,
40
+ * `remoteMetadataAccessStateHash`: Remote identities a container create
41
+ * records once accepted.
42
+ * - `syncStatus`: Lane-specific state (`pending`/`synced` for creates,
43
+ * `pending`/`blocked` for container moves, `pending`/`blocked`/`denied`/
44
+ * `unavailable` for placements).
45
+ * - `lastError`, `lastAttemptedAt`: Replay diagnostics.
46
+ * - `createdAt`: FIFO replay order; `updatedAt`: last target/status change.
24
47
  *
25
48
  * Indexes:
26
- * - `id` is the primary key.
27
- * - `documentId` is unique so same-document moves coalesce to one intent.
28
- * - `(syncStatus, createdAt)` supports listing pending intents in queue order.
49
+ * - `(lane, subjectId)` is unique so repeated enqueues coalesce per lane.
50
+ * - `(lane, syncStatus, createdAt)` lists a lane's queue in order.
29
51
  */
30
- export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
31
- name: "document_move_intents";
52
+ export declare const localIntents: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
53
+ name: "local_intents";
32
54
  schema: undefined;
33
55
  columns: {
34
56
  id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
35
57
  name: "id";
36
- tableName: "document_move_intents";
58
+ tableName: "local_intents";
37
59
  dataType: "string";
38
60
  columnType: "SQLiteText";
39
61
  data: string;
@@ -50,9 +72,9 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
50
72
  }, {}, {
51
73
  length: number | undefined;
52
74
  }>;
53
- localId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
54
- name: "local_id";
55
- tableName: "document_move_intents";
75
+ lane: import("drizzle-orm/sqlite-core").SQLiteColumn<{
76
+ name: "lane";
77
+ tableName: "local_intents";
56
78
  dataType: "string";
57
79
  columnType: "SQLiteText";
58
80
  data: string;
@@ -69,9 +91,9 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
69
91
  }, {}, {
70
92
  length: number | undefined;
71
93
  }>;
72
- documentId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
73
- name: "document_id";
74
- tableName: "document_move_intents";
94
+ intentType: import("drizzle-orm/sqlite-core").SQLiteColumn<{
95
+ name: "intent_type";
96
+ tableName: "local_intents";
75
97
  dataType: "string";
76
98
  columnType: "SQLiteText";
77
99
  data: string;
@@ -88,9 +110,9 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
88
110
  }, {}, {
89
111
  length: number | undefined;
90
112
  }>;
91
- targetContainerId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
92
- name: "target_container_id";
93
- tableName: "document_move_intents";
113
+ subjectId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
114
+ name: "subject_id";
115
+ tableName: "local_intents";
94
116
  dataType: "string";
95
117
  columnType: "SQLiteText";
96
118
  data: string;
@@ -107,9 +129,9 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
107
129
  }, {}, {
108
130
  length: number | undefined;
109
131
  }>;
110
- sourceContainerId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
111
- name: "source_container_id";
112
- tableName: "document_move_intents";
132
+ localId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
133
+ name: "local_id";
134
+ tableName: "local_intents";
113
135
  dataType: "string";
114
136
  columnType: "SQLiteText";
115
137
  data: string;
@@ -126,31 +148,33 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
126
148
  }, {}, {
127
149
  length: number | undefined;
128
150
  }>;
129
- replaceLinkedContainers: import("drizzle-orm/sqlite-core").SQLiteColumn<{
130
- name: "replace_linked_containers";
131
- tableName: "document_move_intents";
132
- dataType: "boolean";
133
- columnType: "SQLiteBoolean";
134
- data: boolean;
135
- driverParam: number;
151
+ targetContainerId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
152
+ name: "target_container_id";
153
+ tableName: "local_intents";
154
+ dataType: "string";
155
+ columnType: "SQLiteText";
156
+ data: string;
157
+ driverParam: string;
136
158
  notNull: true;
137
- hasDefault: true;
159
+ hasDefault: false;
138
160
  isPrimaryKey: false;
139
161
  isAutoincrement: false;
140
162
  hasRuntimeDefault: false;
141
- enumValues: undefined;
163
+ enumValues: [string, ...string[]];
142
164
  baseColumn: never;
143
165
  identity: undefined;
144
166
  generated: undefined;
145
- }, {}, {}>;
146
- intentType: import("drizzle-orm/sqlite-core").SQLiteColumn<{
147
- name: "intent_type";
148
- tableName: "document_move_intents";
167
+ }, {}, {
168
+ length: number | undefined;
169
+ }>;
170
+ sourceContainerId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
171
+ name: "source_container_id";
172
+ tableName: "local_intents";
149
173
  dataType: "string";
150
174
  columnType: "SQLiteText";
151
175
  data: string;
152
176
  driverParam: string;
153
- notNull: true;
177
+ notNull: false;
154
178
  hasDefault: false;
155
179
  isPrimaryKey: false;
156
180
  isAutoincrement: false;
@@ -162,14 +186,31 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
162
186
  }, {}, {
163
187
  length: number | undefined;
164
188
  }>;
165
- syncStatus: import("drizzle-orm/sqlite-core").SQLiteColumn<{
166
- name: "sync_status";
167
- tableName: "document_move_intents";
189
+ replaceLinkedContainers: import("drizzle-orm/sqlite-core").SQLiteColumn<{
190
+ name: "replace_linked_containers";
191
+ tableName: "local_intents";
192
+ dataType: "boolean";
193
+ columnType: "SQLiteBoolean";
194
+ data: boolean;
195
+ driverParam: number;
196
+ notNull: true;
197
+ hasDefault: true;
198
+ isPrimaryKey: false;
199
+ isAutoincrement: false;
200
+ hasRuntimeDefault: false;
201
+ enumValues: undefined;
202
+ baseColumn: never;
203
+ identity: undefined;
204
+ generated: undefined;
205
+ }, {}, {}>;
206
+ remoteContainerId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
207
+ name: "remote_container_id";
208
+ tableName: "local_intents";
168
209
  dataType: "string";
169
210
  columnType: "SQLiteText";
170
211
  data: string;
171
212
  driverParam: string;
172
- notNull: true;
213
+ notNull: false;
173
214
  hasDefault: false;
174
215
  isPrimaryKey: false;
175
216
  isAutoincrement: false;
@@ -181,9 +222,9 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
181
222
  }, {}, {
182
223
  length: number | undefined;
183
224
  }>;
184
- lastError: import("drizzle-orm/sqlite-core").SQLiteColumn<{
185
- name: "last_error";
186
- tableName: "document_move_intents";
225
+ remoteMetadataDocumentId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
226
+ name: "remote_metadata_document_id";
227
+ tableName: "local_intents";
187
228
  dataType: "string";
188
229
  columnType: "SQLiteText";
189
230
  data: string;
@@ -200,9 +241,9 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
200
241
  }, {}, {
201
242
  length: number | undefined;
202
243
  }>;
203
- lastAttemptedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
204
- name: "last_attempted_at";
205
- tableName: "document_move_intents";
244
+ remoteMetadataAccessStateHash: import("drizzle-orm/sqlite-core").SQLiteColumn<{
245
+ name: "remote_metadata_access_state_hash";
246
+ tableName: "local_intents";
206
247
  dataType: "string";
207
248
  columnType: "SQLiteText";
208
249
  data: string;
@@ -219,9 +260,9 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
219
260
  }, {}, {
220
261
  length: number | undefined;
221
262
  }>;
222
- createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
223
- name: "created_at";
224
- tableName: "document_move_intents";
263
+ syncStatus: import("drizzle-orm/sqlite-core").SQLiteColumn<{
264
+ name: "sync_status";
265
+ tableName: "local_intents";
225
266
  dataType: "string";
226
267
  columnType: "SQLiteText";
227
268
  data: string;
@@ -238,14 +279,14 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
238
279
  }, {}, {
239
280
  length: number | undefined;
240
281
  }>;
241
- updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
242
- name: "updated_at";
243
- tableName: "document_move_intents";
282
+ lastError: import("drizzle-orm/sqlite-core").SQLiteColumn<{
283
+ name: "last_error";
284
+ tableName: "local_intents";
244
285
  dataType: "string";
245
286
  columnType: "SQLiteText";
246
287
  data: string;
247
288
  driverParam: string;
248
- notNull: true;
289
+ notNull: false;
249
290
  hasDefault: false;
250
291
  isPrimaryKey: false;
251
292
  isAutoincrement: false;
@@ -257,22 +298,14 @@ export declare const documentMoveIntents: import("drizzle-orm/sqlite-core").SQLi
257
298
  }, {}, {
258
299
  length: number | undefined;
259
300
  }>;
260
- };
261
- dialect: "sqlite";
262
- }>;
263
- /** Additional links share the parent placement intent revision and settlement. */
264
- export declare const documentIntentLinkTargets: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
265
- name: "document_intent_link_targets";
266
- schema: undefined;
267
- columns: {
268
- intentId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
269
- name: "intent_id";
270
- tableName: "document_intent_link_targets";
301
+ lastAttemptedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
302
+ name: "last_attempted_at";
303
+ tableName: "local_intents";
271
304
  dataType: "string";
272
305
  columnType: "SQLiteText";
273
306
  data: string;
274
307
  driverParam: string;
275
- notNull: true;
308
+ notNull: false;
276
309
  hasDefault: false;
277
310
  isPrimaryKey: false;
278
311
  isAutoincrement: false;
@@ -284,28 +317,28 @@ export declare const documentIntentLinkTargets: import("drizzle-orm/sqlite-core"
284
317
  }, {}, {
285
318
  length: number | undefined;
286
319
  }>;
287
- operation: import("drizzle-orm/sqlite-core").SQLiteColumn<{
288
- name: "operation";
289
- tableName: "document_intent_link_targets";
320
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
321
+ name: "created_at";
322
+ tableName: "local_intents";
290
323
  dataType: "string";
291
324
  columnType: "SQLiteText";
292
- data: "link" | "unlink";
325
+ data: string;
293
326
  driverParam: string;
294
327
  notNull: true;
295
- hasDefault: true;
328
+ hasDefault: false;
296
329
  isPrimaryKey: false;
297
330
  isAutoincrement: false;
298
331
  hasRuntimeDefault: false;
299
- enumValues: ["link", "unlink"];
332
+ enumValues: [string, ...string[]];
300
333
  baseColumn: never;
301
334
  identity: undefined;
302
335
  generated: undefined;
303
336
  }, {}, {
304
337
  length: number | undefined;
305
338
  }>;
306
- containerId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
307
- name: "container_id";
308
- tableName: "document_intent_link_targets";
339
+ updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
340
+ name: "updated_at";
341
+ tableName: "local_intents";
309
342
  dataType: "string";
310
343
  columnType: "SQLiteText";
311
344
  data: string;
@@ -325,3 +358,44 @@ export declare const documentIntentLinkTargets: import("drizzle-orm/sqlite-core"
325
358
  };
326
359
  dialect: "sqlite";
327
360
  }>;
361
+ /** The conflict target that coalesces repeated enqueues within a lane. */
362
+ export declare const LOCAL_INTENT_SUBJECT: (import("drizzle-orm/sqlite-core").SQLiteColumn<{
363
+ name: "lane";
364
+ tableName: "local_intents";
365
+ dataType: "string";
366
+ columnType: "SQLiteText";
367
+ data: string;
368
+ driverParam: string;
369
+ notNull: true;
370
+ hasDefault: false;
371
+ isPrimaryKey: false;
372
+ isAutoincrement: false;
373
+ hasRuntimeDefault: false;
374
+ enumValues: [string, ...string[]];
375
+ baseColumn: never;
376
+ identity: undefined;
377
+ generated: undefined;
378
+ }, {}, {
379
+ length: number | undefined;
380
+ }> | import("drizzle-orm/sqlite-core").SQLiteColumn<{
381
+ name: "subject_id";
382
+ tableName: "local_intents";
383
+ dataType: "string";
384
+ columnType: "SQLiteText";
385
+ data: string;
386
+ driverParam: string;
387
+ notNull: true;
388
+ hasDefault: false;
389
+ isPrimaryKey: false;
390
+ isAutoincrement: false;
391
+ hasRuntimeDefault: false;
392
+ enumValues: [string, ...string[]];
393
+ baseColumn: never;
394
+ identity: undefined;
395
+ generated: undefined;
396
+ }, {}, {
397
+ length: number | undefined;
398
+ }>)[];
399
+ export declare function inLocalIntentLane(lane: LocalIntentLane): SQL;
400
+ /** Both container lanes: what a container's own removal or reset discards. */
401
+ export declare function inContainerIntentLanes(): SQL;
@@ -0,0 +1,90 @@
1
+ import { eq, inArray } from "drizzle-orm";
2
+ import { index, integer, primaryKey, sqliteTable, text, unique, } from "drizzle-orm/sqlite-core";
3
+ /**
4
+ * Lanes of the durable local outbox. Each lane keeps at most one intent per
5
+ * subject: a container may hold a create and a move at once, while a
6
+ * document's move and link coalesce into one placement intent.
7
+ */
8
+ export const LOCAL_INTENT_LANES = {
9
+ containerCreate: "container.create",
10
+ containerMove: "container.move",
11
+ documentPlacement: "document.placement",
12
+ };
13
+ /**
14
+ * Device-first structural writes waiting for remote replay: container creates,
15
+ * container moves, and document placement (move and link) intents.
16
+ *
17
+ * Every lane shares one revision protocol: `id` is rewritten on each enqueue or
18
+ * repair, settlement compares `(id, updatedAt)` before acting, and replay runs
19
+ * in `createdAt` order. Remote reset deletes the scoped intents of every lane
20
+ * (and re-queues creates for surviving containers); nothing here survives a
21
+ * reset. Durable requests that must survive reset (the principal mutation
22
+ * journal and organization provisioning attempts) live in their own tables.
23
+ *
24
+ * Columns:
25
+ * - `id`: Revision token and primary key.
26
+ * - `lane`: One of `LOCAL_INTENT_LANES`.
27
+ * - `intentType`: `container.create`, `container.move`, `document.move`, or
28
+ * `document.link`. A placement intent switches between the last two in place.
29
+ * - `subjectId`: Container id for container lanes, server document id for the
30
+ * placement lane.
31
+ * - `localId`: Local document row the placement intent updates; null for
32
+ * container lanes.
33
+ * - `targetContainerId`: Desired parent (container lanes) or target container
34
+ * (placement lane).
35
+ * - `sourceContainerId`: Parent observed when a container move was first
36
+ * queued, or the source container a document move unlinks.
37
+ * - `replaceLinkedContainers`: Placement only — the final remote link set is
38
+ * only the target container.
39
+ * - `remoteContainerId`, `remoteMetadataDocumentId`,
40
+ * `remoteMetadataAccessStateHash`: Remote identities a container create
41
+ * records once accepted.
42
+ * - `syncStatus`: Lane-specific state (`pending`/`synced` for creates,
43
+ * `pending`/`blocked` for container moves, `pending`/`blocked`/`denied`/
44
+ * `unavailable` for placements).
45
+ * - `lastError`, `lastAttemptedAt`: Replay diagnostics.
46
+ * - `createdAt`: FIFO replay order; `updatedAt`: last target/status change.
47
+ *
48
+ * Indexes:
49
+ * - `(lane, subjectId)` is unique so repeated enqueues coalesce per lane.
50
+ * - `(lane, syncStatus, createdAt)` lists a lane's queue in order.
51
+ */
52
+ export const localIntents = sqliteTable("local_intents", {
53
+ id: text("id"),
54
+ lane: text("lane").notNull(),
55
+ intentType: text("intent_type").notNull(),
56
+ subjectId: text("subject_id").notNull(),
57
+ localId: text("local_id"),
58
+ targetContainerId: text("target_container_id").notNull(),
59
+ sourceContainerId: text("source_container_id"),
60
+ replaceLinkedContainers: integer("replace_linked_containers", {
61
+ mode: "boolean",
62
+ })
63
+ .notNull()
64
+ .default(false),
65
+ remoteContainerId: text("remote_container_id"),
66
+ remoteMetadataDocumentId: text("remote_metadata_document_id"),
67
+ remoteMetadataAccessStateHash: text("remote_metadata_access_state_hash"),
68
+ syncStatus: text("sync_status").notNull(),
69
+ lastError: text("last_error"),
70
+ lastAttemptedAt: text("last_attempted_at"),
71
+ createdAt: text("created_at").notNull(),
72
+ updatedAt: text("updated_at").notNull(),
73
+ }, (table) => [
74
+ primaryKey({ columns: [table.id] }),
75
+ unique().on(table.lane, table.subjectId),
76
+ index("local_intents_lane_status_created_idx").on(table.lane, table.syncStatus, table.createdAt),
77
+ ]);
78
+ /** The conflict target that coalesces repeated enqueues within a lane. */
79
+ export const LOCAL_INTENT_SUBJECT = [localIntents.lane, localIntents.subjectId];
80
+ export function inLocalIntentLane(lane) {
81
+ return eq(localIntents.lane, lane);
82
+ }
83
+ /** Both container lanes: what a container's own removal or reset discards. */
84
+ export function inContainerIntentLanes() {
85
+ return inArray(localIntents.lane, [
86
+ LOCAL_INTENT_LANES.containerCreate,
87
+ LOCAL_INTENT_LANES.containerMove,
88
+ ]);
89
+ }
90
+ //# sourceMappingURL=localIntentSchema.js.map