@seedprotocol/sdk 0.7.4-next.784.g879c6a3 → 0.7.4

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 (120) hide show
  1. package/dist/{BasePathResolver-DxdS0wJ0.js → BasePathResolver-DrzGVH9L.js} +232 -40
  2. package/dist/BasePathResolver-DrzGVH9L.js.map +1 -0
  3. package/dist/{ClientManager-De-vKHGe.js → ClientManager-Dfu4jcgm.js} +583 -219
  4. package/dist/ClientManager-Dfu4jcgm.js.map +1 -0
  5. package/dist/{Item-cq9VcsAY.js → Item-BUUFuywT.js} +175 -306
  6. package/dist/Item-BUUFuywT.js.map +1 -0
  7. package/dist/{ModelProperty-DuuoFXaC.js → ModelProperty-B6cnRvIz.js} +38 -39
  8. package/dist/ModelProperty-B6cnRvIz.js.map +1 -0
  9. package/dist/{Schema-Dc5EA8Yb.js → Schema-Bf-jOLq0.js} +37 -36
  10. package/dist/{Schema-Dc5EA8Yb.js.map → Schema-Bf-jOLq0.js.map} +1 -1
  11. package/dist/{SchemaValidationService-DViJ056F.js → SchemaValidationService-BNYMacsx.js} +7 -8
  12. package/dist/{SchemaValidationService-DViJ056F.js.map → SchemaValidationService-BNYMacsx.js.map} +1 -1
  13. package/dist/{backfillMetadataPropertyIds-DacVthS5.js → backfillMetadataPropertyIds-C2-s_jUa.js} +4 -5
  14. package/dist/{backfillMetadataPropertyIds-DacVthS5.js.map → backfillMetadataPropertyIds-C2-s_jUa.js.map} +1 -1
  15. package/dist/{configurePlatform-DlZ23w7e.js → configurePlatform-BzfirKuM.js} +3 -4
  16. package/dist/{configurePlatform-DlZ23w7e.js.map → configurePlatform-BzfirKuM.js.map} +1 -1
  17. package/dist/{detachRowsBeforeDelete-YRSZY6dO.js → detachRowsBeforeDelete-B0Daoz9Z.js} +3 -4
  18. package/dist/{detachRowsBeforeDelete-YRSZY6dO.js.map → detachRowsBeforeDelete-B0Daoz9Z.js.map} +1 -1
  19. package/dist/{eas-D_6ptL_F.js → eas-Dq5Kx_Nd.js} +14 -32
  20. package/dist/eas-Dq5Kx_Nd.js.map +1 -0
  21. package/dist/{errors-Cl8rbtKR.js → errors-DdWbxb_n.js} +42 -39
  22. package/dist/errors-DdWbxb_n.js.map +1 -0
  23. package/dist/{getItem-BcmJcUZB.js → getItem-9bYSErSj.js} +4 -5
  24. package/dist/{getItem-BcmJcUZB.js.map → getItem-9bYSErSj.js.map} +1 -1
  25. package/dist/{getPublishPayload-C278kK9P.js → getPublishPayload-DPy-uMrR.js} +28 -29
  26. package/dist/getPublishPayload-DPy-uMrR.js.map +1 -0
  27. package/dist/{getPublishUploads-DJ1UPh7i.js → getPublishUploads-J-143iSV.js} +10 -11
  28. package/dist/{getPublishUploads-DJ1UPh7i.js.map → getPublishUploads-J-143iSV.js.map} +1 -1
  29. package/dist/{index-DAl9U4rn.js → index-7y2f_iQz.js} +2 -2
  30. package/dist/index-7y2f_iQz.js.map +1 -0
  31. package/dist/{index-CqnU-5i0.js → index-C61Rxy0F.js} +9 -10
  32. package/dist/index-C61Rxy0F.js.map +1 -0
  33. package/dist/{index.browser-J6QZ9smP.js → index.browser-C1pisOEa.js} +10 -10
  34. package/dist/{index.browser-J6QZ9smP.js.map → index.browser-C1pisOEa.js.map} +1 -1
  35. package/dist/{index.node-C9VB9NlX.js → index.node-BAlST8pD.js} +6 -6
  36. package/dist/{index.node-C9VB9NlX.js.map → index.node-BAlST8pD.js.map} +1 -1
  37. package/dist/main.js +27 -28
  38. package/dist/main.js.map +1 -1
  39. package/dist/node.js +12 -13
  40. package/dist/node.js.map +1 -1
  41. package/dist/platform.browser.js +6 -7
  42. package/dist/platform.browser.js.map +1 -1
  43. package/dist/platform.node.js +5 -6
  44. package/dist/platform.node.js.map +1 -1
  45. package/dist/{property-Brgr5RdT.js → property-zFPMtR3T.js} +10 -14
  46. package/dist/property-zFPMtR3T.js.map +1 -0
  47. package/dist/{propertyValidationRules-CDRitiiK.js → propertyValidationRules-CeFVhdQg.js} +10 -10
  48. package/dist/{propertyValidationRules-CDRitiiK.js.map → propertyValidationRules-CeFVhdQg.js.map} +1 -1
  49. package/dist/{publishDraftGraph-C37ibxb5.js → publishDraftGraph-CuVq6iAR.js} +7 -6
  50. package/dist/{publishDraftGraph-C37ibxb5.js.map → publishDraftGraph-CuVq6iAR.js.map} +1 -1
  51. package/dist/{resetAmbiguousLegacyItemData-CcLFRREE.js → resetAmbiguousLegacyItemData-DsFTnMBz.js} +5 -6
  52. package/dist/resetAmbiguousLegacyItemData-DsFTnMBz.js.map +1 -0
  53. package/dist/{resolveModelForSyncedSeed-BTAeCOJE.js → resolveModelForSyncedSeed-RUsgAZXG.js} +2 -2
  54. package/dist/{resolveModelForSyncedSeed-BTAeCOJE.js.map → resolveModelForSyncedSeed-RUsgAZXG.js.map} +1 -1
  55. package/dist/src/Item/Item.d.ts.map +1 -1
  56. package/dist/src/ItemProperty/service/propertyMachine.d.ts +4 -4
  57. package/dist/src/Model/Model.d.ts +3 -2
  58. package/dist/src/Model/Model.d.ts.map +1 -1
  59. package/dist/src/ModelProperty/ModelProperty.d.ts +1 -1
  60. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts +1 -1
  61. package/dist/src/Schema/service/schemaMachine.d.ts +2 -2
  62. package/dist/src/client/ClientManager.d.ts +6 -6
  63. package/dist/src/client/ClientManager.d.ts.map +1 -1
  64. package/dist/src/client/actors/addModelsToDb.d.ts.map +1 -1
  65. package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
  66. package/dist/src/client/clientManagerMachine.d.ts +2 -2
  67. package/dist/src/client/clientManagerMachine.d.ts.map +1 -1
  68. package/dist/src/db/canonicalizeModelTypes.d.ts +18 -0
  69. package/dist/src/db/canonicalizeModelTypes.d.ts.map +1 -0
  70. package/dist/src/db/read/getModelSchemas.d.ts +6 -0
  71. package/dist/src/db/read/getModelSchemas.d.ts.map +1 -1
  72. package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
  73. package/dist/src/db/read/getSchemaUidForModel.d.ts +6 -0
  74. package/dist/src/db/read/getSchemaUidForModel.d.ts.map +1 -1
  75. package/dist/src/db/read/resolveModelRecord.d.ts.map +1 -1
  76. package/dist/src/db/removeForeignAttestationRows.d.ts +27 -0
  77. package/dist/src/db/removeForeignAttestationRows.d.ts.map +1 -0
  78. package/dist/src/db/write/createMetadata.d.ts.map +1 -1
  79. package/dist/src/db/write/easSyncProcess.d.ts +9 -1
  80. package/dist/src/db/write/easSyncProcess.d.ts.map +1 -1
  81. package/dist/src/db/write/updateMetadata.d.ts.map +1 -1
  82. package/dist/src/eas.d.ts.map +1 -1
  83. package/dist/src/events/item/easSyncManager.d.ts.map +1 -1
  84. package/dist/src/events/item/syncDbWithEas.d.ts.map +1 -1
  85. package/dist/src/helpers/db.d.ts +20 -0
  86. package/dist/src/helpers/db.d.ts.map +1 -1
  87. package/dist/src/helpers/index.d.ts +3 -1
  88. package/dist/src/helpers/index.d.ts.map +1 -1
  89. package/dist/src/helpers/metadataPropertyNames.d.ts +12 -0
  90. package/dist/src/helpers/metadataPropertyNames.d.ts.map +1 -1
  91. package/dist/src/helpers/retryWhenOnline.d.ts +20 -0
  92. package/dist/src/helpers/retryWhenOnline.d.ts.map +1 -0
  93. package/dist/src/imports/json.d.ts +10 -0
  94. package/dist/src/imports/json.d.ts.map +1 -1
  95. package/dist/src/index.d.ts +4 -2
  96. package/dist/src/index.d.ts.map +1 -1
  97. package/dist/src/query/createLocalQueryDataSource.d.ts.map +1 -1
  98. package/dist/src/types/import.d.ts +4 -0
  99. package/dist/src/types/import.d.ts.map +1 -1
  100. package/dist/{syncDbWithEas-B-Ho0IOB.js → syncDbWithEas-CRpnkq00.js} +157 -61
  101. package/dist/syncDbWithEas-CRpnkq00.js.map +1 -0
  102. package/dist/{tabEvents-BTzckWrN.js → tabEvents-nBUq_1u1.js} +6 -6
  103. package/dist/{tabEvents-BTzckWrN.js.map → tabEvents-nBUq_1u1.js.map} +1 -1
  104. package/dist/{tolerantWebAccess-DOm2Xv-f.js → tolerantWebAccess-DIt3R8_u.js} +7 -8
  105. package/dist/{tolerantWebAccess-DOm2Xv-f.js.map → tolerantWebAccess-DIt3R8_u.js.map} +1 -1
  106. package/dist/{updateSeedRevokedAt-IHXnJI-i.js → updateSeedRevokedAt-DtTPSzpM.js} +3 -3
  107. package/dist/{updateSeedRevokedAt-IHXnJI-i.js.map → updateSeedRevokedAt-DtTPSzpM.js.map} +1 -1
  108. package/package.json +5 -5
  109. package/dist/BasePathResolver-DxdS0wJ0.js.map +0 -1
  110. package/dist/ClientManager-De-vKHGe.js.map +0 -1
  111. package/dist/Item-cq9VcsAY.js.map +0 -1
  112. package/dist/ModelProperty-DuuoFXaC.js.map +0 -1
  113. package/dist/eas-D_6ptL_F.js.map +0 -1
  114. package/dist/errors-Cl8rbtKR.js.map +0 -1
  115. package/dist/getPublishPayload-C278kK9P.js.map +0 -1
  116. package/dist/index-CqnU-5i0.js.map +0 -1
  117. package/dist/index-DAl9U4rn.js.map +0 -1
  118. package/dist/property-Brgr5RdT.js.map +0 -1
  119. package/dist/resetAmbiguousLegacyItemData-CcLFRREE.js.map +0 -1
  120. package/dist/syncDbWithEas-B-Ho0IOB.js.map +0 -1
@@ -1,12 +1,12 @@
1
1
  import { startCase, isEqual } from 'lodash-es';
2
- import { E as BaseDb, l as easSyncProcesses, G as eventEmitter, a8 as isNode, ah as isBrowser, I as BasePathResolver, aA as normalizeAddressConfig, h as appState, aL as downloadAllFilesRequestHandler, aM as downloadAllFilesBinaryRequestHandler, J as BaseFileManager, H as toSnakeCase, s as models$1, r as modelUids, A as seeds, aB as normalizeAddressList, z as schemas, aK as loadModelsFromDbForSchema, aI as addSchemaToDb, aF as isInternalSchema, aN as INTERNAL_SCHEMA_IDS, $ as generateId, aO as linkModelToSchema, n as normalizeDataType, aq as SEED_PROTOCOL_SCHEMA_NAME, q as modelSchemas, m as metadata$1, u as properties, L as resolveItemModelFileId, C as versions, o as htmlEmbeddedImageCoPublish, j as arweaveL1FinalizeJobs, y as publishProcesses, X as normalizePublisher, aP as getWatchedAddressesFromDb, aQ as getOwnedAddressesFromDb, aR as CLIENT_NOT_INITIALIZED } from './BasePathResolver-DxdS0wJ0.js';
2
+ import { E as BaseDb, l as easSyncProcesses, H as eventEmitter, ab as isNode, ak as isBrowser, I as BasePathResolver, aD as normalizeAddressConfig, h as appState, aO as downloadAllFilesRequestHandler, aP as downloadAllFilesBinaryRequestHandler, J as BaseFileManager, s as models$1, r as modelUids, A as seeds, aE as normalizeAddressList, z as schemas, aN as loadModelsFromDbForSchema, aL as addSchemaToDb, aI as isInternalSchema, aQ as INTERNAL_SCHEMA_IDS, $ as generateId, aR as linkModelToSchema, n as normalizeDataType, a2 as easPropertyNameForPublish, at as SEED_PROTOCOL_SCHEMA_NAME, q as modelSchemas, m as metadata$1, u as properties, L as resolveItemModelFileId, C as versions, o as htmlEmbeddedImageCoPublish, j as arweaveL1FinalizeJobs, y as publishProcesses, X as normalizePublisher, aS as getWatchedAddressesFromDb, aT as getOwnedAddressesFromDb, aU as CLIENT_NOT_INITIALIZED } from './BasePathResolver-DrzGVH9L.js';
3
3
  import debug from 'debug';
4
4
  import { createActor, setup, assign, fromPromise, fromCallback, waitFor } from 'xstate';
5
- import { x as MachineIds, a2 as ClientManagerEvents, a3 as withLockedItemStorageRow, a4 as getItemStorageFilePath, a5 as writeItemStorageFile, c as compareMetadataRowsLatestFirst, C as ClientManagerState, a6 as resolvePublisherFromSeedRow, J as isLocalUnsealedDraft, I as Item } from './Item-cq9VcsAY.js';
6
- import { eq, inArray, and, isNotNull, ne, desc, countDistinct, isNull, or, like } from 'drizzle-orm';
7
- import { a as withDbTabLock, i as isLeaderTab, c as postTabMessage, o as onTabMessage, w as whenLeaderTab, l as listenForOtherTabsEvents, s as startTabCoordination, b as withSeedDbLock, p as parseAddressesPersistedPayload, e as emitAcrossTabs, A as ADDRESSES_PERSISTED_EVENT, L as LOCAL_COPIES_REMOVED_EVENT } from './tabEvents-BTzckWrN.js';
5
+ import { u as MachineIds, a0 as ClientManagerEvents, a1 as withLockedItemStorageRow, a2 as getItemStorageFilePath, a3 as writeItemStorageFile, c as compareMetadataRowsLatestFirst, w as writeInBatches, C as ClientManagerState, a4 as resolvePublisherFromSeedRow, G as isLocalUnsealedDraft, I as Item } from './Item-BUUFuywT.js';
6
+ import { eq, inArray, and, isNotNull, ne, desc, countDistinct, isNull, or, sql, like } from 'drizzle-orm';
7
+ import { a as withDbTabLock, i as isLeaderTab, c as postTabMessage, o as onTabMessage, w as whenLeaderTab, l as listenForOtherTabsEvents, s as startTabCoordination, b as withSeedDbLock, e as emitAcrossTabs, A as ADDRESSES_PERSISTED_EVENT, p as parseAddressesPersistedPayload, L as LOCAL_COPIES_REMOVED_EVENT } from './tabEvents-nBUq_1u1.js';
8
8
  import { seedGatewayConfigFromSeedConfig, setPreferredArweaveReadBaseUrls, resolveSeedGatewayEndpoints, setResolvedSeedGatewayEndpoints, BaseArweaveClient, ensureReadGatewaySelected, DEFAULT_ARWEAVE_HOST } from '@seedprotocol/arweave';
9
- import { configureEasReadChain, BaseQueryClient, BaseEasClient, GET_SCHEMAS, whenEasReadChainSettled, getEasReadChainId, DEFAULT_EAS_CHAIN_ID, isEasReadChainConfigured, isValidEasAttestationUid } from '@seedprotocol/eas';
9
+ import { configureEasReadChain, easName, BaseQueryClient, BaseEasClient, GET_SCHEMAS, whenEasReadChainSettled, getEasReadChainId, DEFAULT_EAS_CHAIN_ID, isEasReadChainConfigured, localNamesInBestMatchTier, isValidEasAttestationUid } from '@seedprotocol/eas';
10
10
  import 'pluralize';
11
11
  import { sha3_256 } from '@noble/hashes/sha3';
12
12
  import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils';
@@ -18,6 +18,7 @@ function enqueueEasSyncDbWrite(fn) {
18
18
  writeTail = next.catch(() => { });
19
19
  return next;
20
20
  }
21
+ const INTERRUPTED_SYNC_MESSAGE = 'Interrupted: the sync stopped before it finished (tab closed?)';
21
22
  const MAX_ERROR_MESSAGE_LENGTH = 500;
22
23
  const MAX_ERROR_DETAILS_LENGTH = 2000;
23
24
  async function insertEasSyncProcessRow(params) {
@@ -61,6 +62,30 @@ async function finalizeEasSyncProcessRow(id, params) {
61
62
  .where(eq(easSyncProcesses.id, id));
62
63
  });
63
64
  }
65
+ /**
66
+ * Marks every `in_progress` row failed ("interrupted"). Called by a sync holding the `eas-sync` tab
67
+ * lock, before it inserts its own row: syncs take turns under that lock, so no other sync is
68
+ * running and such a row belongs to one whose tab closed or crashed. Returns how many it marked.
69
+ */
70
+ async function failInterruptedEasSyncProcessRows() {
71
+ return enqueueEasSyncDbWrite(async () => {
72
+ const db = BaseDb.getAppDb();
73
+ if (!db)
74
+ return 0;
75
+ const now = Date.now();
76
+ const rows = await db
77
+ .update(easSyncProcesses)
78
+ .set({
79
+ status: 'failed',
80
+ completedAt: now,
81
+ updatedAt: now,
82
+ errorMessage: INTERRUPTED_SYNC_MESSAGE,
83
+ })
84
+ .where(eq(easSyncProcesses.status, 'in_progress'))
85
+ .returning({ id: easSyncProcesses.id });
86
+ return rows.length;
87
+ });
88
+ }
64
89
 
65
90
  /**
66
91
  * Merge multiple EAS sync intents: full sync (no concrete address list) wins;
@@ -94,6 +119,89 @@ function mergeEasSyncRequestIntents(parts) {
94
119
  return { addresses: [...addressMap.values()] };
95
120
  }
96
121
 
122
+ /**
123
+ * Retries background work that failed because the device is (probably) offline: the startup EAS
124
+ * schema lookup and automatic EAS syncs. Without it, a session that starts offline never syncs.
125
+ *
126
+ * A retry waits for the browser's `online` event while `navigator.onLine` is false, and otherwise
127
+ * backs off on a timer (15 s doubling to 5 min, which also covers Node and a reachable network
128
+ * with an unreachable indexer). Only one retry per task is pending at a time. Timers don't keep a
129
+ * Node process alive. `client.stop()` cancels every pending retry.
130
+ *
131
+ * Off in test runs (NODE_ENV or Vite's mode is `test`), where a late retry would sync in the middle
132
+ * of another test. Tests that exercise retries pass `enabled` or call
133
+ * setRetriesWhenOnlineEnabled(true).
134
+ */
135
+ const DEFAULT_DELAYS_MS = [15000, 30000, 60000, 120000, 300000];
136
+ const all = new Set();
137
+ /** NODE_ENV in Node; in the browser, Vite (and so Vitest) sets import.meta.env.MODE. */
138
+ const isTestEnv = () => (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test') ||
139
+ import.meta.env?.MODE === 'test';
140
+ let enabledOverride;
141
+ /**
142
+ * Turns retries without an explicit `enabled` on or off; `undefined` restores the default.
143
+ * Returns the previous setting, so a test can put it back.
144
+ */
145
+ function setRetriesWhenOnlineEnabled(enabled) {
146
+ const previous = enabledOverride;
147
+ enabledOverride = enabled;
148
+ return previous;
149
+ }
150
+ const isOffline = () => typeof navigator !== 'undefined' && navigator.onLine === false;
151
+ const onlineTarget = () => typeof globalThis.addEventListener === 'function' ? globalThis : undefined;
152
+ function createRetryWhenOnline(run, options = {}) {
153
+ const delaysMs = options.delaysMs ?? DEFAULT_DELAYS_MS;
154
+ const isEnabled = () => options.enabled ?? enabledOverride ?? !isTestEnv();
155
+ let attempt = 0;
156
+ let timer;
157
+ let onOnline;
158
+ const clear = () => {
159
+ if (timer !== undefined)
160
+ clearTimeout(timer);
161
+ timer = undefined;
162
+ if (onOnline)
163
+ onlineTarget()?.removeEventListener('online', onOnline);
164
+ onOnline = undefined;
165
+ };
166
+ const retry = {
167
+ schedule() {
168
+ if (!isEnabled() || timer !== undefined || onOnline)
169
+ return;
170
+ const fire = () => {
171
+ clear();
172
+ run();
173
+ };
174
+ const target = onlineTarget();
175
+ if (isOffline() && target) {
176
+ onOnline = fire;
177
+ target.addEventListener('online', onOnline);
178
+ return;
179
+ }
180
+ const delay = delaysMs[Math.min(attempt, delaysMs.length - 1)];
181
+ attempt += 1;
182
+ timer = setTimeout(fire, delay);
183
+ timer.unref?.();
184
+ },
185
+ succeeded() {
186
+ attempt = 0;
187
+ },
188
+ cancel() {
189
+ clear();
190
+ attempt = 0;
191
+ },
192
+ get pending() {
193
+ return timer !== undefined || onOnline !== undefined;
194
+ },
195
+ };
196
+ all.add(retry);
197
+ return retry;
198
+ }
199
+ /** Cancels every pending retry (client.stop()). */
200
+ function cancelAllRetriesWhenOnline() {
201
+ for (const retry of all)
202
+ retry.cancel();
203
+ }
204
+
97
205
  const log = debug('seedSdk:events:easSyncManager');
98
206
  function toRunSyncArg(options) {
99
207
  if (options &&
@@ -104,41 +212,61 @@ function toRunSyncArg(options) {
104
212
  return undefined;
105
213
  }
106
214
  const runEasSync = fromPromise(async ({ input, }) => {
107
- const rowId = await insertEasSyncProcessRow({
108
- requestPayload: input.requestPayload,
109
- persistedSnapshot: input.runningSnapshot,
110
- });
111
- const { runSyncFromEas } = await import('./syncDbWithEas-B-Ho0IOB.js');
112
- try {
113
- // Sync checks for rows and then inserts the rest (no unique index on uids), so syncs in
114
- // different tabs take turns. A sync can run for minutes, so wait without a timeout.
115
- await withDbTabLock('eas-sync', () => runSyncFromEas(toRunSyncArg(input.options)), {
116
- timeoutMs: Infinity,
117
- });
118
- await finalizeEasSyncProcessRow(rowId, {
119
- status: 'completed',
120
- persistedSnapshot: {
121
- phase: 'completed',
122
- correlationIds: input.requestPayload.correlationIds,
123
- },
215
+ const { runSyncFromEas } = await import('./syncDbWithEas-CRpnkq00.js');
216
+ // Sync checks for rows and then inserts the rest (no unique index on uids), so syncs in
217
+ // different tabs take turns. A sync can run for minutes, so wait without a timeout.
218
+ await withDbTabLock('eas-sync', async () => {
219
+ // Under the lock no other sync runs, so rows still in progress were interrupted.
220
+ await failInterruptedEasSyncProcessRows();
221
+ const rowId = await insertEasSyncProcessRow({
222
+ requestPayload: input.requestPayload,
223
+ persistedSnapshot: input.runningSnapshot,
124
224
  });
125
- }
126
- catch (err) {
127
- const message = err instanceof Error ? err.message : String(err);
128
- const details = err instanceof Error && err.stack
129
- ? err.stack.slice(0, 2000)
130
- : undefined;
131
- await finalizeEasSyncProcessRow(rowId, {
132
- status: 'failed',
133
- errorMessage: message,
134
- errorDetails: details,
135
- persistedSnapshot: {
136
- phase: 'failed',
137
- correlationIds: input.requestPayload.correlationIds,
138
- },
225
+ try {
226
+ await runSyncFromEas(toRunSyncArg(input.options));
227
+ await finalizeEasSyncProcessRow(rowId, {
228
+ status: 'completed',
229
+ persistedSnapshot: {
230
+ phase: 'completed',
231
+ correlationIds: input.requestPayload.correlationIds,
232
+ },
233
+ });
234
+ }
235
+ catch (err) {
236
+ const message = err instanceof Error ? err.message : String(err);
237
+ const details = err instanceof Error && err.stack
238
+ ? err.stack.slice(0, 2000)
239
+ : undefined;
240
+ await finalizeEasSyncProcessRow(rowId, {
241
+ status: 'failed',
242
+ errorMessage: message,
243
+ errorDetails: details,
244
+ persistedSnapshot: {
245
+ phase: 'failed',
246
+ correlationIds: input.requestPayload.correlationIds,
247
+ },
248
+ });
249
+ throw err;
250
+ }
251
+ }, { timeoutMs: Infinity });
252
+ });
253
+ /**
254
+ * Automatic syncs that failed (probably offline), merged and run again once the device is back
255
+ * online. A failed `client_api` sync isn't retried: its caller gets the error.
256
+ */
257
+ let failedSyncOptions = [];
258
+ const syncRetry = createRetryWhenOnline(() => {
259
+ const options = mergeEasSyncRequestIntents(failedSyncOptions);
260
+ failedSyncOptions = [];
261
+ runInLeaderTab('retry', () => {
262
+ startEasSyncActor();
263
+ easSyncActor.send({
264
+ type: 'REQUEST',
265
+ correlationId: newCorrelationId(),
266
+ options,
267
+ source: 'retry',
139
268
  });
140
- throw err;
141
- }
269
+ });
142
270
  });
143
271
  const initialContext = {
144
272
  runOptions: {},
@@ -211,19 +339,29 @@ const easSyncMachine = setup({
211
339
  }),
212
340
  onDone: {
213
341
  target: 'afterRun',
214
- actions: assign({
215
- lastFinishedCorrelationIds: ({ context }) => [
216
- ...context.activeCorrelationIds,
217
- ],
218
- lastFinishedStatus: () => 'success',
219
- lastFinishedError: () => null,
220
- activeCorrelationIds: () => [],
221
- runOptions: () => ({}),
222
- }),
342
+ actions: [
343
+ () => syncRetry.succeeded(),
344
+ assign({
345
+ lastFinishedCorrelationIds: ({ context }) => [
346
+ ...context.activeCorrelationIds,
347
+ ],
348
+ lastFinishedStatus: () => 'success',
349
+ lastFinishedError: () => null,
350
+ activeCorrelationIds: () => [],
351
+ runOptions: () => ({}),
352
+ }),
353
+ ],
223
354
  },
224
355
  onError: {
225
356
  target: 'afterRun',
226
357
  actions: [
358
+ // Before the assign below clears runOptions.
359
+ ({ context }) => {
360
+ if (context.activePrimarySource === 'client_api')
361
+ return;
362
+ failedSyncOptions.push(context.runOptions);
363
+ syncRetry.schedule();
364
+ },
227
365
  assign({
228
366
  lastFinishedCorrelationIds: ({ context }) => [
229
367
  ...context.activeCorrelationIds,
@@ -284,7 +422,7 @@ function newCorrelationId() {
284
422
  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
285
423
  }
286
424
  /** Requests deferred in this tab until it leads, by source; each runs once on takeover. */
287
- const deferredUntilLeader$1 = new Set();
425
+ const deferredUntilLeader$3 = new Set();
288
426
  /**
289
427
  * Automatic syncs run in the leader tab only (docs/MULTI_TAB.md). A tab that skips one runs it if
290
428
  * it later becomes leader, so closing the leader doesn't lose the catch-up sync.
@@ -295,11 +433,11 @@ function runInLeaderTab(source, request) {
295
433
  return;
296
434
  }
297
435
  log(`${source} sync skipped: another tab leads`);
298
- if (deferredUntilLeader$1.has(source))
436
+ if (deferredUntilLeader$3.has(source))
299
437
  return;
300
- deferredUntilLeader$1.add(source);
438
+ deferredUntilLeader$3.add(source);
301
439
  void whenLeaderTab().then(() => {
302
- deferredUntilLeader$1.delete(source);
440
+ deferredUntilLeader$3.delete(source);
303
441
  request();
304
442
  });
305
443
  }
@@ -373,10 +511,10 @@ var easSyncManager = /*#__PURE__*/Object.freeze({
373
511
  startEasSyncActor: startEasSyncActor
374
512
  });
375
513
 
376
- const logger$c = debug('seedSdk:services:events');
514
+ const logger$e = debug('seedSdk:services:events');
377
515
  const handleServiceSaveState = (event) => {
378
516
  const { state, serviceId } = event;
379
- logger$c(`[browser] [service.saveState.request] serviceId: ${serviceId}`);
517
+ logger$e(`[browser] [service.saveState.request] serviceId: ${serviceId}`);
380
518
  // TODO: This is called from Node as well. Do we need it? If so, need to generalize this to all platforms.
381
519
  // localStorage.setItem(`seed_sdk_service_${serviceId}`, JSON.stringify(state))
382
520
  };
@@ -427,7 +565,7 @@ function normalizeSeedConfigEndpoints(config) {
427
565
  return { ...config, endpoints: next };
428
566
  }
429
567
 
430
- const logger$b = debug('seedSdk:ClientManager:initialize');
568
+ const logger$d = debug('seedSdk:ClientManager:initialize');
431
569
  /**
432
570
  * Whether a relative filesDir already names the .seed directory or a path inside
433
571
  * it ('.seed', '.seed/files', './.seed/files'). '.seed-alt/x' is not: it is
@@ -438,7 +576,7 @@ function isUnderDotSeed(relativePath) {
438
576
  return firstSegment === '.seed';
439
577
  }
440
578
  const platformClassesInit = fromCallback(({ sendBack, input: { context, event } }) => {
441
- logger$b('initialize from ClientManager');
579
+ logger$d('initialize from ClientManager');
442
580
  const { isInitialized } = context;
443
581
  if (!event || !('options' in event)) {
444
582
  throw new Error('Initialize event must include options');
@@ -471,7 +609,7 @@ const platformClassesInit = fromCallback(({ sendBack, input: { context, event }
471
609
  }
472
610
  // Prefer the condition-resolved factory when available; fall back to explicit
473
611
  // platform imports so source/vitest resolution stays reliable.
474
- const { configurePlatform } = await import('./configurePlatform-DlZ23w7e.js');
612
+ const { configurePlatform } = await import('./configurePlatform-BzfirKuM.js');
475
613
  if (isBrowser()) {
476
614
  const { createPlatformServices } = await import('./platform.browser.js');
477
615
  configurePlatform(createPlatformServices());
@@ -611,7 +749,7 @@ const platformClassesInit = fromCallback(({ sendBack, input: { context, event }
611
749
  };
612
750
  });
613
751
 
614
- const logger$a = debug('seedSdk:client:writeToDb');
752
+ const logger$c = debug('seedSdk:client:writeToDb');
615
753
  const saveAppState = fromCallback(({ sendBack, input: { key, value } }) => {
616
754
  const _saveAppState = async () => {
617
755
  try {
@@ -619,7 +757,7 @@ const saveAppState = fromCallback(({ sendBack, input: { key, value } }) => {
619
757
  if (!appDb) {
620
758
  // In test environments, continue anyway
621
759
  if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
622
- logger$a('[client/actors] [saveAppState] App DB not found, but continuing in test environment');
760
+ logger$c('[client/actors] [saveAppState] App DB not found, but continuing in test environment');
623
761
  return;
624
762
  }
625
763
  throw new Error('App DB not found');
@@ -635,13 +773,13 @@ const saveAppState = fromCallback(({ sendBack, input: { key, value } }) => {
635
773
  value: JSON.stringify(value),
636
774
  },
637
775
  });
638
- logger$a('result', result);
776
+ logger$c('result', result);
639
777
  }
640
778
  catch (error) {
641
- logger$a('[client/actors] [saveAppState] Error saving app state:', error);
779
+ logger$c('[client/actors] [saveAppState] Error saving app state:', error);
642
780
  // In test environments, continue anyway
643
781
  if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
644
- logger$a('[client/actors] [saveAppState] Continuing despite error in test environment');
782
+ logger$c('[client/actors] [saveAppState] Continuing despite error in test environment');
645
783
  return;
646
784
  }
647
785
  throw error;
@@ -656,10 +794,10 @@ const saveAppState = fromCallback(({ sendBack, input: { key, value } }) => {
656
794
  });
657
795
  })
658
796
  .catch((error) => {
659
- logger$a('[client/actors] [saveAppState] Error in promise chain:', error);
797
+ logger$c('[client/actors] [saveAppState] Error in promise chain:', error);
660
798
  // In test environments, still send success to allow state machine to progress
661
799
  if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
662
- logger$a('[client/actors] [saveAppState] Sending success despite error in test environment');
800
+ logger$c('[client/actors] [saveAppState] Sending success despite error in test environment');
663
801
  sendBack({
664
802
  type: 'saveAppStateSuccess',
665
803
  key,
@@ -729,7 +867,7 @@ const fileSystemInit = fromCallback(({ sendBack, input: { context }, }) => {
729
867
  };
730
868
  });
731
869
 
732
- const logger$9 = debug('seedSdk:client:actors:dbInit');
870
+ const logger$b = debug('seedSdk:client:actors:dbInit');
733
871
  const dbInit = fromCallback(({ sendBack, input: { context } }) => {
734
872
  const _dbInit = async () => {
735
873
  const { filesDir, dbConfig } = context;
@@ -743,14 +881,14 @@ const dbInit = fromCallback(({ sendBack, input: { context } }) => {
743
881
  if (!appDb) {
744
882
  throw new Error('Database not available after preparation');
745
883
  }
746
- logger$9('[client/actors] [dbInit] Database prepared and ready');
884
+ logger$b('[client/actors] [dbInit] Database prepared and ready');
747
885
  };
748
886
  _dbInit()
749
887
  .then(() => {
750
888
  sendBack({ type: ClientManagerEvents.DB_READY });
751
889
  })
752
890
  .catch((error) => {
753
- logger$9('Error in dbInit:', error);
891
+ logger$b('Error in dbInit:', error);
754
892
  sendBack({
755
893
  type: 'error',
756
894
  error: error instanceof Error ? error : new Error(String(error))
@@ -758,7 +896,7 @@ const dbInit = fromCallback(({ sendBack, input: { context } }) => {
758
896
  });
759
897
  });
760
898
 
761
- const logger$8 = debug('seedSdk:client:actors:addModelsToStore');
899
+ const logger$a = debug('seedSdk:client:actors:addModelsToStore');
762
900
  const addModelsToStore = fromCallback(({ sendBack, input: { context } }) => {
763
901
  const _addModelsToStore = async () => {
764
902
  let { models } = context;
@@ -772,7 +910,7 @@ const addModelsToStore = fromCallback(({ sendBack, input: { context } }) => {
772
910
  // Models are now Model instances, no registration needed
773
911
  // They should be created via Model.create() and are accessible via Model static methods
774
912
  for (const [key] of Object.entries(allModels)) {
775
- logger$8('model available:', key);
913
+ logger$a('model available:', key);
776
914
  }
777
915
  };
778
916
  _addModelsToStore()
@@ -780,7 +918,7 @@ const addModelsToStore = fromCallback(({ sendBack, input: { context } }) => {
780
918
  sendBack({ type: ClientManagerEvents.ADD_MODELS_TO_STORE_SUCCESS });
781
919
  })
782
920
  .catch((error) => {
783
- logger$8('[client/actors] [addModelsToStore] Error:', error);
921
+ logger$a('[client/actors] [addModelsToStore] Error:', error);
784
922
  sendBack({
785
923
  type: 'error',
786
924
  error: error instanceof Error ? error : new Error(String(error)),
@@ -788,7 +926,7 @@ const addModelsToStore = fromCallback(({ sendBack, input: { context } }) => {
788
926
  });
789
927
  });
790
928
 
791
- const logger$7 = debug('seedSdk:client:actors:addModelsToDb');
929
+ const logger$9 = debug('seedSdk:client:actors:addModelsToDb');
792
930
  /**
793
931
  * Returns every `models` row named in `modelNames`, inserting a null-schemaFileId stub for each name
794
932
  * with no row yet (the schema import adopts stubs via findOrCreateModelRecord). Check-then-insert:
@@ -811,12 +949,15 @@ async function ensureModelStubs(appDb, modelNames, filesDir) {
811
949
  .returning({ id: models$1.id, name: models$1.name, schemaFileId: models$1.schemaFileId }));
812
950
  rows.push(...newlyInserted);
813
951
  for (const name of modelsToInsert) {
814
- logger$7('[client/actors] [addModelsToDb] inserted model:', name);
952
+ logger$9('[client/actors] [addModelsToDb] inserted model:', name);
815
953
  }
816
954
  }
817
955
  return rows;
818
956
  });
819
957
  }
958
+ /** Retries the latest init's EAS schema lookup (see retryWhenOnline). */
959
+ let retryModelUidsLookup;
960
+ const modelUidsRetry = createRetryWhenOnline(() => void retryModelUidsLookup?.());
820
961
  const addModelsToDb = fromCallback(({ sendBack, input: { context } }) => {
821
962
  const { models } = context;
822
963
  const _addModelsToDb = async () => {
@@ -848,19 +989,19 @@ const addModelsToDb = fromCallback(({ sendBack, input: { context } }) => {
848
989
  for (const modelName of modelNames) {
849
990
  const foundModel = rowFor(modelName);
850
991
  if (!foundModel) {
851
- logger$7('[client/actors] [addModelsToDb] Warning: Could not find or create model:', modelName);
992
+ logger$9('[client/actors] [addModelsToDb] Warning: Could not find or create model:', modelName);
852
993
  hasModelsInDb = false;
853
994
  continue;
854
995
  }
855
996
  // The EAS schema string depends only on the model name, so every same-name row shares its uid.
856
997
  schemaDefsByModelName.set(modelName, {
857
998
  dbIds: existingModels.filter((m) => m.name === modelName).map((m) => m.id),
858
- schemaDef: `bytes32 ${toSnakeCase(modelName)}`,
999
+ schemaDef: `bytes32 ${easName(modelName)}`,
859
1000
  });
860
1001
  }
861
1002
  // If we have no models to process, still send success (empty models is valid)
862
1003
  if (schemaDefsByModelName.size === 0) {
863
- logger$7('[client/actors] [addModelsToDb] No models to process, but continuing');
1004
+ logger$9('[client/actors] [addModelsToDb] No models to process, but continuing');
864
1005
  sendBack({ type: ClientManagerEvents.ADD_MODELS_TO_DB_SUCCESS });
865
1006
  return;
866
1007
  }
@@ -881,8 +1022,13 @@ const addModelsToDb = fromCallback(({ sendBack, input: { context } }) => {
881
1022
  },
882
1023
  }),
883
1024
  });
884
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('EAS query timeout after 10 seconds')), 10000));
885
- const { schemas } = await Promise.race([queryPromise, timeoutPromise]);
1025
+ let timeout;
1026
+ const timeoutPromise = new Promise((_, reject) => {
1027
+ timeout = setTimeout(() => reject(new Error('EAS query timeout after 10 seconds')), 10000);
1028
+ });
1029
+ // Cleared, so a finished lookup doesn't keep a Node process alive for 10 s.
1030
+ const { schemas } = await Promise.race([queryPromise, timeoutPromise]).finally(() => clearTimeout(timeout));
1031
+ modelUidsRetry.succeeded();
886
1032
  if (schemas && schemas.length > 0) {
887
1033
  const db = BaseDb.getAppDb();
888
1034
  if (db) {
@@ -903,18 +1049,22 @@ const addModelsToDb = fromCallback(({ sendBack, input: { context } }) => {
903
1049
  requestEasSyncFromModelsInit();
904
1050
  }
905
1051
  else {
906
- logger$7('[client/actors] [addModelsToDb] No schemas found from EAS, but continuing');
1052
+ logger$9('[client/actors] [addModelsToDb] No schemas found from EAS, but continuing');
907
1053
  }
908
1054
  }
909
1055
  catch (error) {
910
1056
  if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
911
- logger$7('[client/actors] [addModelsToDb] Warning: Could not fetch schemas from EAS (background):', error.message);
1057
+ logger$9('[client/actors] [addModelsToDb] Warning: Could not fetch schemas from EAS (background):', error.message);
912
1058
  }
913
1059
  else {
914
- logger$7('[client/actors] [addModelsToDb] Error fetching schemas (background):', error.message);
1060
+ logger$9('[client/actors] [addModelsToDb] Error fetching schemas (background):', error.message);
915
1061
  }
1062
+ // Probably offline: the startup sync waits for this lookup, so try again.
1063
+ modelUidsRetry.schedule();
916
1064
  }
917
1065
  };
1066
+ retryModelUidsLookup = fetchEasAndPopulateModelUids;
1067
+ modelUidsRetry.cancel();
918
1068
  fetchEasAndPopulateModelUids();
919
1069
  return hasModelsInDb;
920
1070
  };
@@ -923,10 +1073,10 @@ const addModelsToDb = fromCallback(({ sendBack, input: { context } }) => {
923
1073
  sendBack({ type: ClientManagerEvents.ADD_MODELS_TO_DB_SUCCESS });
924
1074
  })
925
1075
  .catch((error) => {
926
- logger$7('[client/actors] [addModelsToDb] Error:', error);
1076
+ logger$9('[client/actors] [addModelsToDb] Error:', error);
927
1077
  // In test environments, still send success to allow initialization to complete
928
1078
  if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
929
- logger$7('[client/actors] [addModelsToDb] Continuing despite error in test environment');
1079
+ logger$9('[client/actors] [addModelsToDb] Continuing despite error in test environment');
930
1080
  sendBack({ type: ClientManagerEvents.ADD_MODELS_TO_DB_SUCCESS });
931
1081
  }
932
1082
  else {
@@ -939,7 +1089,7 @@ const addModelsToDb = fromCallback(({ sendBack, input: { context } }) => {
939
1089
  return () => { };
940
1090
  });
941
1091
 
942
- const logger$6 = debug('seedSdk:helpers:localDbChain');
1092
+ const logger$8 = debug('seedSdk:helpers:localDbChain');
943
1093
  /** appState key recording which EAS chain the local database holds attestations from. */
944
1094
  const EAS_CHAIN_ID_APP_STATE_KEY = 'easChainId';
945
1095
  /**
@@ -1009,7 +1159,7 @@ async function loadLocalDbChain() {
1009
1159
  const localChainId = await resolveLocalDbChainId(appDb);
1010
1160
  if (localChainId !== undefined) {
1011
1161
  configureEasReadChain('localDb', { chainId: localChainId });
1012
- logger$6('local DB holds chain %d', localChainId);
1162
+ logger$8('local DB holds chain %d', localChainId);
1013
1163
  return;
1014
1164
  }
1015
1165
  if (isEasReadChainConfigured()) {
@@ -1035,7 +1185,7 @@ async function assertLocalDbChain() {
1035
1185
  configureEasReadChain('localDb', { chainId: localChainId ?? chainId });
1036
1186
  }
1037
1187
 
1038
- const logger$5 = debug('seedSdk:client:actors:saveConfig');
1188
+ const logger$7 = debug('seedSdk:client:actors:saveConfig');
1039
1189
  let warnedKeptStoredAddresses = false;
1040
1190
  /**
1041
1191
  * Saves the addresses passed to init.
@@ -1071,15 +1221,18 @@ async function persistInitAddresses(appDb, { addresses, addressesProvided, owned
1071
1221
  .insert(appState)
1072
1222
  .values({ key: 'addresses', value })
1073
1223
  .onConflictDoUpdate({ target: appState.key, set: { value } });
1224
+ // Same event setAddresses emits, so listeners (e.g. React's session context) also see the
1225
+ // addresses init saved. Their sync is init's startup sync, which reads them from the DB.
1226
+ emitAcrossTabs(ADDRESSES_PERSISTED_EVENT, { owned, watched });
1074
1227
  }
1075
1228
  const saveConfig = fromCallback(({ sendBack, input: { context } }) => {
1076
- logger$5('saveConfig starting');
1229
+ logger$7('saveConfig starting');
1077
1230
  const { endpoints, addresses, addressesProvided, ownedAddresses, watchedAddresses, arweaveDomain, uploadApiBaseUrl, gatewayTransport, gatewayHyperKey, gatewaySidecarHost, gatewaySidecarPort, gatewayProxyBaseUrl, } = context;
1078
1231
  // Validate endpoints - required for proper initialization
1079
1232
  // If endpoints are missing or invalid, initialization should fail
1080
1233
  if (!endpoints || !endpoints.filePaths || !endpoints.files) {
1081
1234
  const error = new Error('saveConfig called with invalid endpoints: endpoints must include both filePaths and files');
1082
- logger$5('[internal/actors] [saveConfig] Invalid endpoints:', { endpoints });
1235
+ logger$7('[internal/actors] [saveConfig] Invalid endpoints:', { endpoints });
1083
1236
  throw error;
1084
1237
  }
1085
1238
  const _saveConfig = async () => {
@@ -1088,7 +1241,7 @@ const saveConfig = fromCallback(({ sendBack, input: { context } }) => {
1088
1241
  if (!appDb) {
1089
1242
  // In test environments, continue anyway
1090
1243
  if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
1091
- logger$5('[internal/actors] [saveConfig] App DB not found, but continuing in test environment');
1244
+ logger$7('[internal/actors] [saveConfig] App DB not found, but continuing in test environment');
1092
1245
  return;
1093
1246
  }
1094
1247
  throw new Error('App DB not found');
@@ -1143,10 +1296,10 @@ const saveConfig = fromCallback(({ sendBack, input: { context } }) => {
1143
1296
  await loadLocalDbChain();
1144
1297
  }
1145
1298
  catch (error) {
1146
- logger$5('[internal/actors] [saveConfig] Error saving config:', error);
1299
+ logger$7('[internal/actors] [saveConfig] Error saving config:', error);
1147
1300
  // In test environments, continue anyway
1148
1301
  if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
1149
- logger$5('[internal/actors] [saveConfig] Continuing despite error in test environment');
1302
+ logger$7('[internal/actors] [saveConfig] Continuing despite error in test environment');
1150
1303
  return;
1151
1304
  }
1152
1305
  throw error;
@@ -1155,14 +1308,14 @@ const saveConfig = fromCallback(({ sendBack, input: { context } }) => {
1155
1308
  // Init writes run one tab at a time (docs/MULTI_TAB.md).
1156
1309
  withSeedDbLock('init', context.filesDir, _saveConfig)
1157
1310
  .then(() => {
1158
- logger$5('[internal/actors] [saveConfig] saveConfig success');
1311
+ logger$7('[internal/actors] [saveConfig] saveConfig success');
1159
1312
  return sendBack({ type: ClientManagerEvents.SAVE_CONFIG_SUCCESS });
1160
1313
  })
1161
1314
  .catch((error) => {
1162
- logger$5('[internal/actors] [saveConfig] Error in saveConfig promise chain:', error);
1315
+ logger$7('[internal/actors] [saveConfig] Error in saveConfig promise chain:', error);
1163
1316
  // In test environments, still send success to allow state machine to progress
1164
1317
  if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
1165
- logger$5('[internal/actors] [saveConfig] Sending success despite error in test environment');
1318
+ logger$7('[internal/actors] [saveConfig] Sending success despite error in test environment');
1166
1319
  sendBack({ type: ClientManagerEvents.SAVE_CONFIG_SUCCESS });
1167
1320
  }
1168
1321
  else {
@@ -1175,7 +1328,7 @@ const saveConfig = fromCallback(({ sendBack, input: { context } }) => {
1175
1328
  return () => { };
1176
1329
  });
1177
1330
 
1178
- const logger$4 = debug('seedSdk:helpers:schema');
1331
+ const logger$6 = debug('seedSdk:helpers:schema');
1179
1332
  /**
1180
1333
  * Sanitize a schema name to be filesystem-safe
1181
1334
  * Replaces all special characters (except alphanumeric, hyphens, underscores) with underscores
@@ -1371,7 +1524,7 @@ async function listCompleteSchemaFiles() {
1371
1524
  const dirExists = await BaseFileManager.pathExists(workingDir);
1372
1525
  if (!dirExists) {
1373
1526
  // Directory doesn't exist yet - return empty array (not an error condition)
1374
- logger$4(`Working directory does not exist yet: ${workingDir}, returning empty schema list`);
1527
+ logger$6(`Working directory does not exist yet: ${workingDir}, returning empty schema list`);
1375
1528
  return [];
1376
1529
  }
1377
1530
  const files = await fs.promises.readdir(workingDir);
@@ -1508,7 +1661,7 @@ async function loadAllSchemasFromDb() {
1508
1661
  }
1509
1662
  catch (error) {
1510
1663
  // If we can't list schema files (e.g., directory doesn't exist yet), continue without file-based loading
1511
- logger$4(`Error listing complete schema files (continuing with DB data): ${error instanceof Error ? error.message : String(error)}`);
1664
+ logger$6(`Error listing complete schema files (continuing with DB data): ${error instanceof Error ? error.message : String(error)}`);
1512
1665
  completeSchemaFiles = [];
1513
1666
  }
1514
1667
  }
@@ -1561,7 +1714,7 @@ async function loadAllSchemasFromDb() {
1561
1714
  continue;
1562
1715
  }
1563
1716
  catch (error) {
1564
- logger$4(`Error parsing schemaData for ${schemaName}:`, error);
1717
+ logger$6(`Error parsing schemaData for ${schemaName}:`, error);
1565
1718
  // Fall through to file-based loading
1566
1719
  }
1567
1720
  }
@@ -1603,7 +1756,7 @@ async function loadAllSchemasFromDb() {
1603
1756
  continue;
1604
1757
  }
1605
1758
  catch (error) {
1606
- logger$4(`Error loading schema file for ${schemaName}:`, error);
1759
+ logger$6(`Error loading schema file for ${schemaName}:`, error);
1607
1760
  // Fall through to try loading from schemaData if available
1608
1761
  }
1609
1762
  }
@@ -1647,7 +1800,7 @@ async function loadAllSchemasFromDb() {
1647
1800
  continue;
1648
1801
  }
1649
1802
  catch (error) {
1650
- logger$4(`Error parsing schemaData for ${schemaName}:`, error);
1803
+ logger$6(`Error parsing schemaData for ${schemaName}:`, error);
1651
1804
  }
1652
1805
  }
1653
1806
  }
@@ -1658,11 +1811,11 @@ async function loadAllSchemasFromDb() {
1658
1811
  // Only process if not already in result
1659
1812
  if (!processedSchemaNames.has(schemaFileInfo.name)) {
1660
1813
  if (isInternalSchema(schemaFileInfo.name, schemaFileInfo.schemaFileId)) {
1661
- logger$4(`STEP 3: skipping internal schema file: ${schemaFileInfo.filePath}`);
1814
+ logger$6(`STEP 3: skipping internal schema file: ${schemaFileInfo.filePath}`);
1662
1815
  continue;
1663
1816
  }
1664
1817
  try {
1665
- logger$4(`STEP 3: processing schema file: ${schemaFileInfo.name} from ${schemaFileInfo.filePath}`);
1818
+ logger$6(`STEP 3: processing schema file: ${schemaFileInfo.name} from ${schemaFileInfo.filePath}`);
1666
1819
  const content = await BaseFileManager.readFileAsString(schemaFileInfo.filePath);
1667
1820
  const schemaFile = JSON.parse(content);
1668
1821
  // Import this schema to database (mark as non-draft since it's in a file)
@@ -1682,7 +1835,7 @@ async function loadAllSchemasFromDb() {
1682
1835
  processedSchemaNames.add(schemaFileInfo.name);
1683
1836
  }
1684
1837
  catch (error) {
1685
- logger$4(`Error loading schema file ${schemaFileInfo.filePath}:`, error);
1838
+ logger$6(`Error loading schema file ${schemaFileInfo.filePath}:`, error);
1686
1839
  }
1687
1840
  }
1688
1841
  }
@@ -1699,7 +1852,7 @@ async function migrateFileSchemasToDb() {
1699
1852
  if (!db) {
1700
1853
  throw new Error('Database not found');
1701
1854
  }
1702
- logger$4('Starting migration of file-based schemas to database');
1855
+ logger$6('Starting migration of file-based schemas to database');
1703
1856
  // Get all complete schema files
1704
1857
  const completeSchemas = await listCompleteSchemaFiles();
1705
1858
  let migratedCount = 0;
@@ -1709,7 +1862,7 @@ async function migrateFileSchemasToDb() {
1709
1862
  const content = await BaseFileManager.readFileAsString(schemaFileInfo.filePath);
1710
1863
  const schemaFile = JSON.parse(content);
1711
1864
  if (!schemaFile.$schema || !schemaFile.metadata?.name) {
1712
- logger$4(`Skipping invalid schema file: ${schemaFileInfo.filePath}`);
1865
+ logger$6(`Skipping invalid schema file: ${schemaFileInfo.filePath}`);
1713
1866
  continue;
1714
1867
  }
1715
1868
  const schemaName = schemaFile.metadata.name;
@@ -1731,17 +1884,17 @@ async function migrateFileSchemasToDb() {
1731
1884
  JSON.stringify(schemaFile, null, 2), // schemaData
1732
1885
  false);
1733
1886
  migratedCount++;
1734
- logger$4(`Migrated schema: ${schemaName} v${schemaFile.version}`);
1887
+ logger$6(`Migrated schema: ${schemaName} v${schemaFile.version}`);
1735
1888
  }
1736
1889
  else {
1737
- logger$4(`Schema ${schemaName} already exists in database, skipping`);
1890
+ logger$6(`Schema ${schemaName} already exists in database, skipping`);
1738
1891
  }
1739
1892
  }
1740
1893
  catch (error) {
1741
- logger$4(`Error migrating schema file ${schemaFileInfo.filePath}:`, error);
1894
+ logger$6(`Error migrating schema file ${schemaFileInfo.filePath}:`, error);
1742
1895
  }
1743
1896
  }
1744
- logger$4(`Migration complete: ${migratedCount} schemas migrated`);
1897
+ logger$6(`Migration complete: ${migratedCount} schemas migrated`);
1745
1898
  return migratedCount;
1746
1899
  }
1747
1900
  /**
@@ -1794,7 +1947,7 @@ const getDeterministicIdsWithPrefix = (prefix) => {
1794
1947
  return (suffix) => bytesToHex(prefixHash.clone().update(utf8ToBytes(suffix)).digest()).slice(0, 10);
1795
1948
  };
1796
1949
 
1797
- const logger$3 = debug('seedSdk:imports:json');
1950
+ const logger$5 = debug('seedSdk:imports:json');
1798
1951
  /**
1799
1952
  * Get refValueType from a property object with case-insensitive key lookup.
1800
1953
  * Handles refValueType, refvaluetype, ref_value_type, elementType, and legacy items.type / items.Type.
@@ -1872,8 +2025,8 @@ function getRefValueType(prop) {
1872
2025
  * @returns Promise that resolves when properties are found, or rejects if not found after max retries
1873
2026
  */
1874
2027
  const verifyPropertiesPersisted = async (db, modelId, modelName, maxRetries = 10, retryDelay = 100) => {
1875
- const _mod_0 = await import('./BasePathResolver-DxdS0wJ0.js');
1876
- const _ns_0 = _mod_0.a_;
2028
+ const _mod_0 = await import('./BasePathResolver-DrzGVH9L.js');
2029
+ const _ns_0 = _mod_0.b6;
1877
2030
  const modelSchemaMod = _ns_0;
1878
2031
  const { properties: propertiesTable } = modelSchemaMod;
1879
2032
  const drizzleMod = await import('drizzle-orm');
@@ -1885,11 +2038,11 @@ const verifyPropertiesPersisted = async (db, modelId, modelName, maxRetries = 10
1885
2038
  .where(eq(propertiesTable.modelId, modelId))
1886
2039
  .limit(1);
1887
2040
  if (props.length > 0) {
1888
- logger$3(`Verified properties exist for model "${modelName}" (modelId: ${modelId}) after ${attempt + 1} attempt(s)`);
2041
+ logger$5(`Verified properties exist for model "${modelName}" (modelId: ${modelId}) after ${attempt + 1} attempt(s)`);
1889
2042
  return;
1890
2043
  }
1891
2044
  if (attempt < maxRetries - 1) {
1892
- logger$3(`Properties not yet visible for model "${modelName}" (modelId: ${modelId}), retrying in ${retryDelay}ms (attempt ${attempt + 1}/${maxRetries})`);
2045
+ logger$5(`Properties not yet visible for model "${modelName}" (modelId: ${modelId}), retrying in ${retryDelay}ms (attempt ${attempt + 1}/${maxRetries})`);
1893
2046
  await new Promise(resolve => setTimeout(resolve, retryDelay));
1894
2047
  }
1895
2048
  }
@@ -2178,6 +2331,46 @@ const getSchemaFilePath = (workingDir, name, version, schemaFileId) => {
2178
2331
  const filename = `${schemaFileId}_${sanitizedName}_v${version}.json`;
2179
2332
  return path.join(workingDir, filename);
2180
2333
  };
2334
+ /**
2335
+ * Import a JSON schema file and save it in the full schema file format
2336
+ * Also converts the JSON models to Model classes and adds them to the database
2337
+ * @param importFilePath - Path to the JSON import file
2338
+ * @param version - The version number for the schema (defaults to 1)
2339
+ * @returns The file path where the schema was saved
2340
+ * @throws Error if import file cannot be read, parsed, or if schema already exists
2341
+ */
2342
+ /**
2343
+ * Warns when two models, or two properties of one model, have the same EAS name (ADR 0008): they
2344
+ * would publish to the same EAS schema (`HTMLPage` and `HtmlPage` are both `html_page`). A warning,
2345
+ * not an error, so schema files that already load keep loading.
2346
+ */
2347
+ function warnOnEasNameCollisions(schemaFile) {
2348
+ const warnings = [];
2349
+ const collisions = (names) => {
2350
+ const byEasName = new Map();
2351
+ for (const [name, eas] of names)
2352
+ byEasName.set(eas, [...(byEasName.get(eas) ?? []), name]);
2353
+ return [...byEasName.entries()].filter(([, group]) => group.length > 1);
2354
+ };
2355
+ const models = Object.entries(schemaFile.models ?? {});
2356
+ for (const [eas, group] of collisions(models.map(([name]) => [name, easName(name)]))) {
2357
+ warnings.push(`models ${group.join(', ')} all publish as "bytes32 ${eas}"`);
2358
+ }
2359
+ for (const [modelName, model] of models) {
2360
+ const properties = Object.entries(model?.properties ?? {});
2361
+ const named = properties.map(([key, def]) => [
2362
+ key,
2363
+ easPropertyNameForPublish(key, { ...def, dataType: def?.dataType ?? def?.type, name: key }),
2364
+ ]);
2365
+ for (const [eas, group] of collisions(named)) {
2366
+ warnings.push(`${modelName} properties ${group.join(', ')} all publish as "${eas}"`);
2367
+ }
2368
+ }
2369
+ for (const warning of warnings) {
2370
+ console.warn(`[seed] schema ${schemaFile.metadata?.name ?? ''}: ${warning}; EAS can't tell them apart.`);
2371
+ }
2372
+ return warnings;
2373
+ }
2181
2374
  async function importJsonSchema(importFilePathOrContents, version = 1) {
2182
2375
  // Determine if we have a file path or file contents
2183
2376
  let importData;
@@ -2195,7 +2388,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2195
2388
  if (parsed.$schema && parsed.metadata) {
2196
2389
  // It's already a complete schema format - preserve it (id may be missing, will be generated)
2197
2390
  originalSchemaFile = parsed;
2198
- logger$3(`Input is complete schema format (${originalSchemaFile.id ? `preserving ID: ${originalSchemaFile.id}` : 'id will be generated'})`);
2391
+ logger$5(`Input is complete schema format (${originalSchemaFile.id ? `preserving ID: ${originalSchemaFile.id}` : 'id will be generated'})`);
2199
2392
  }
2200
2393
  }
2201
2394
  catch {
@@ -2220,8 +2413,8 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2220
2413
  // Check DB for existing schema by name+version (same as minimal path)
2221
2414
  const db = BaseDb.getAppDb();
2222
2415
  if (db) {
2223
- const _mod_1 = await import('./BasePathResolver-DxdS0wJ0.js');
2224
- const _ns_1 = _mod_1.b0;
2416
+ const _mod_1 = await import('./BasePathResolver-DrzGVH9L.js');
2417
+ const _ns_1 = _mod_1.b8;
2225
2418
  const schemaSchemaMod = _ns_1;
2226
2419
  const { schemas: schemasTable } = schemaSchemaMod;
2227
2420
  const drizzleMod = await import('drizzle-orm');
@@ -2234,16 +2427,16 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2234
2427
  .limit(1);
2235
2428
  if (existingByName.length > 0 && existingByName[0].schemaData) {
2236
2429
  const existing = existingByName[0];
2237
- logger$3(`Idempotency: found existing schema by name+version: ${schemaFile.metadata.name} v${schemaFile.version ?? version}`);
2430
+ logger$5(`Idempotency: found existing schema by name+version: ${schemaFile.metadata.name} v${schemaFile.version ?? version}`);
2238
2431
  const existingSchema = JSON.parse(existing.schemaData);
2239
2432
  const contentMatches = compareSchemaContent(schemaFile, existingSchema);
2240
- logger$3(`Idempotency: compareSchemaContent=${contentMatches} for ${schemaFile.metadata.name}`);
2433
+ logger$5(`Idempotency: compareSchemaContent=${contentMatches} for ${schemaFile.metadata.name}`);
2241
2434
  if (contentMatches) {
2242
2435
  if (existing.schemaFileId) {
2243
2436
  const workingDir = BaseFileManager.getWorkingDir();
2244
2437
  const existingFilePath = getSchemaFilePath(workingDir, schemaFile.metadata.name, schemaFile.version ?? version, existing.schemaFileId);
2245
2438
  if (await BaseFileManager.pathExists(existingFilePath)) {
2246
- logger$3(`Schema ${schemaFile.metadata.name} v${schemaFile.version} already exists with same content, loading existing file`);
2439
+ logger$5(`Schema ${schemaFile.metadata.name} v${schemaFile.version} already exists with same content, loading existing file`);
2247
2440
  return await loadSchemaFromFile(existingFilePath);
2248
2441
  }
2249
2442
  await BaseFileManager.createDirIfNotExists(workingDir);
@@ -2251,7 +2444,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2251
2444
  await BaseFileManager.waitForFileWithContent(existingFilePath, 100, 5000);
2252
2445
  return await loadSchemaFromFile(existingFilePath);
2253
2446
  }
2254
- logger$3(`Schema ${schemaFile.metadata.name} v${schemaFile.version} already exists with same content (no schemaFileId), using DB`);
2447
+ logger$5(`Schema ${schemaFile.metadata.name} v${schemaFile.version} already exists with same content (no schemaFileId), using DB`);
2255
2448
  return '';
2256
2449
  }
2257
2450
  }
@@ -2267,7 +2460,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2267
2460
  // processSchemaFiles runs multiple times (React StrictMode, app restarts).
2268
2461
  if (!schemaFile.id) {
2269
2462
  schemaFile.id = await getDeterministicSchemaFileId(schemaFile);
2270
- logger$3(`Generated deterministic schema ID for ${schemaFile.metadata.name}:`, schemaFile.id);
2463
+ logger$5(`Generated deterministic schema ID for ${schemaFile.metadata.name}:`, schemaFile.id);
2271
2464
  }
2272
2465
  // Before generating IDs, check if a file already exists with this schemaFileId
2273
2466
  // This handles the case where the schema was already loaded via loadSchemaFromFile
@@ -2277,7 +2470,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2277
2470
  const existingFileExists = await BaseFileManager.pathExists(existingFilePath);
2278
2471
  if (existingFileExists) {
2279
2472
  // File already exists with this schemaFileId - just load it instead of creating a new one
2280
- logger$3(`Schema file already exists with schemaFileId ${schemaFile.id}, loading it instead of creating new file`);
2473
+ logger$5(`Schema file already exists with schemaFileId ${schemaFile.id}, loading it instead of creating new file`);
2281
2474
  return await loadSchemaFromFile(existingFilePath);
2282
2475
  }
2283
2476
  // Generate missing model and property IDs.
@@ -2288,12 +2481,12 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2288
2481
  for (const [modelName, model] of Object.entries(schemaFile.models || {})) {
2289
2482
  if (!model.id) {
2290
2483
  model.id = getDeterministicId(modelName);
2291
- logger$3(`Generated deterministic model ID for ${modelName}:`, model.id);
2484
+ logger$5(`Generated deterministic model ID for ${modelName}:`, model.id);
2292
2485
  }
2293
2486
  for (const [propName, prop] of Object.entries(model.properties || {})) {
2294
2487
  if (!prop.id) {
2295
2488
  prop.id = getDeterministicId(modelName + propName);
2296
- logger$3(`Generated deterministic property ID for ${modelName}.${propName}:`, prop.id);
2489
+ logger$5(`Generated deterministic property ID for ${modelName}.${propName}:`, prop.id);
2297
2490
  }
2298
2491
  }
2299
2492
  }
@@ -2305,8 +2498,8 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2305
2498
  // For minimal imports: check DB for existing schema before creating new file
2306
2499
  const db = BaseDb.getAppDb();
2307
2500
  if (db) {
2308
- const _mod_2 = await import('./BasePathResolver-DxdS0wJ0.js');
2309
- const _ns_2 = _mod_2.b0;
2501
+ const _mod_2 = await import('./BasePathResolver-DrzGVH9L.js');
2502
+ const _ns_2 = _mod_2.b8;
2310
2503
  const schemaSchemaMod = _ns_2;
2311
2504
  const { schemas: schemasTable } = schemaSchemaMod;
2312
2505
  const drizzleMod = await import('drizzle-orm');
@@ -2329,14 +2522,14 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2329
2522
  const workingDir = BaseFileManager.getWorkingDir();
2330
2523
  const existingFilePath = getSchemaFilePath(workingDir, importData.name, version, existing.schemaFileId);
2331
2524
  if (await BaseFileManager.pathExists(existingFilePath)) {
2332
- logger$3(`Schema ${importData.name} v${version} already exists with same content, loading existing file`);
2525
+ logger$5(`Schema ${importData.name} v${version} already exists with same content, loading existing file`);
2333
2526
  return await loadSchemaFromFile(existingFilePath);
2334
2527
  }
2335
2528
  // File missing but schemaData exists - restore file from schemaData
2336
2529
  await BaseFileManager.createDirIfNotExists(workingDir);
2337
2530
  await BaseFileManager.saveFile(existingFilePath, existing.schemaData);
2338
2531
  await BaseFileManager.waitForFileWithContent(existingFilePath, 100, 5000);
2339
- logger$3(`Restored schema file from schemaData for ${importData.name} v${version}`);
2532
+ logger$5(`Restored schema file from schemaData for ${importData.name} v${version}`);
2340
2533
  return await loadSchemaFromFile(existingFilePath);
2341
2534
  }
2342
2535
  // No schemaFileId - restore file from schemaData using id from parsed schema
@@ -2347,10 +2540,10 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2347
2540
  await BaseFileManager.createDirIfNotExists(workingDir);
2348
2541
  await BaseFileManager.saveFile(existingFilePath, existing.schemaData);
2349
2542
  await BaseFileManager.waitForFileWithContent(existingFilePath, 100, 5000);
2350
- logger$3(`Restored schema file from schemaData for ${importData.name} v${version}`);
2543
+ logger$5(`Restored schema file from schemaData for ${importData.name} v${version}`);
2351
2544
  return await loadSchemaFromFile(existingFilePath);
2352
2545
  }
2353
- logger$3(`Schema ${importData.name} v${version} already exists with same content (no id), using DB`);
2546
+ logger$5(`Schema ${importData.name} v${version} already exists with same content (no id), using DB`);
2354
2547
  return '';
2355
2548
  }
2356
2549
  // Content differs - create new version
@@ -2361,7 +2554,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2361
2554
  .orderBy(desc(schemasTable.version))
2362
2555
  .limit(1);
2363
2556
  const newVersion = latestVersionRows.length > 0 ? latestVersionRows[0].version + 1 : version;
2364
- logger$3(`Schema ${importData.name} content changed, creating new version v${newVersion}`);
2557
+ logger$5(`Schema ${importData.name} content changed, creating new version v${newVersion}`);
2365
2558
  schemaFile = transformImportToSchemaFile(importData, newVersion);
2366
2559
  version = newVersion;
2367
2560
  }
@@ -2382,10 +2575,11 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2382
2575
  schemaFile = transformImportToSchemaFile(importData, version);
2383
2576
  }
2384
2577
  }
2578
+ warnOnEasNameCollisions(schemaFile);
2385
2579
  // Check if this is an internal SDK schema (should not create files in app directory)
2386
2580
  const isInternal = isInternalSchema(schemaFile.metadata.name, schemaFile.id);
2387
2581
  if (isInternal) {
2388
- logger$3(`Skipping file creation for internal schema: ${schemaFile.metadata.name}`);
2582
+ logger$5(`Skipping file creation for internal schema: ${schemaFile.metadata.name}`);
2389
2583
  // For internal schemas, just load to database and store, don't create file
2390
2584
  // Extract schema name and version
2391
2585
  const schemaName = schemaFile.metadata?.name;
@@ -2442,7 +2636,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2442
2636
  // Use fixed internal schema ID if missing (never generate for internal)
2443
2637
  if (!schemaFile.id) {
2444
2638
  schemaFile.id = INTERNAL_SCHEMA_IDS[0];
2445
- logger$3('Using fixed internal schema ID:', schemaFile.id);
2639
+ logger$5('Using fixed internal schema ID:', schemaFile.id);
2446
2640
  }
2447
2641
  // Extract schemaFileIds from JSON file and generate missing ones BEFORE creating models
2448
2642
  // This ensures Model instances are created with correct IDs
@@ -2452,7 +2646,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2452
2646
  // Generate model ID if missing
2453
2647
  if (!model.id) {
2454
2648
  model.id = generateId();
2455
- logger$3(`Generated model ID for ${modelName}:`, model.id);
2649
+ logger$5(`Generated model ID for ${modelName}:`, model.id);
2456
2650
  // Update the schemaFile object so it's included in schemaData
2457
2651
  schemaFile.models[modelName].id = model.id;
2458
2652
  }
@@ -2462,7 +2656,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2462
2656
  // Generate property ID if missing
2463
2657
  if (!prop.id) {
2464
2658
  prop.id = generateId();
2465
- logger$3(`Generated property ID for ${modelName}.${propName}:`, prop.id);
2659
+ logger$5(`Generated property ID for ${modelName}.${propName}:`, prop.id);
2466
2660
  // Update the schemaFile object so it's included in schemaData
2467
2661
  schemaFile.models[modelName].properties[propName].id = prop.id;
2468
2662
  }
@@ -2474,7 +2668,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2474
2668
  }
2475
2669
  // Convert JSON models to Model classes, passing modelFileIds and propertyFileIds so Model instances use correct IDs
2476
2670
  const modelDefinitions = await createModelsFromJson(importDataForInternal, modelFileIds, propertyFileIds);
2477
- logger$3('loadSchemaFromFile (internal) - modelDefinitions length:', Object.keys(modelDefinitions).length);
2671
+ logger$5('loadSchemaFromFile (internal) - modelDefinitions length:', Object.keys(modelDefinitions).length);
2478
2672
  // Convert schema file metadata to schema input for database
2479
2673
  const schemaInput = {
2480
2674
  name: schemaName,
@@ -2484,8 +2678,8 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2484
2678
  updatedAt: new Date(schemaFile.metadata.updatedAt).getTime(),
2485
2679
  };
2486
2680
  // Use dynamic import to break circular dependency
2487
- const _mod_3 = await import('./BasePathResolver-DxdS0wJ0.js');
2488
- const _ns_3 = _mod_3.b3;
2681
+ const _mod_3 = await import('./BasePathResolver-DrzGVH9L.js');
2682
+ const _ns_3 = _mod_3.bb;
2489
2683
  const dbMod = _ns_3;
2490
2684
  const { addSchemaToDb, addModelsToDb } = dbMod;
2491
2685
  // Try to add schema and models to database if database is available
@@ -2506,12 +2700,12 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2506
2700
  });
2507
2701
  // Verify expected models are linked (single attempt - addModelsToDb already confirmed)
2508
2702
  const expectedModelNames = Object.keys(schemaFile.models || {});
2509
- const _mod_4 = await import('./BasePathResolver-DxdS0wJ0.js');
2510
- const _ns_4 = _mod_4.b1;
2703
+ const _mod_4 = await import('./BasePathResolver-DrzGVH9L.js');
2704
+ const _ns_4 = _mod_4.b9;
2511
2705
  const modelSchemaSchemaMod = _ns_4;
2512
2706
  const { modelSchemas } = modelSchemaSchemaMod;
2513
- const _mod_5 = await import('./BasePathResolver-DxdS0wJ0.js');
2514
- const _ns_5 = _mod_5.a_;
2707
+ const _mod_5 = await import('./BasePathResolver-DrzGVH9L.js');
2708
+ const _ns_5 = _mod_5.b6;
2515
2709
  const modelSchemaMod = _ns_5;
2516
2710
  const { models: modelsTable } = modelSchemaMod;
2517
2711
  const drizzleMod = await import('drizzle-orm');
@@ -2600,12 +2794,12 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2600
2794
  }
2601
2795
  catch (dbError) {
2602
2796
  // Database not available - log warning but continue
2603
- logger$3('Database not available, skipping database operations:', dbError instanceof Error ? dbError.message : String(dbError));
2797
+ logger$5('Database not available, skipping database operations:', dbError instanceof Error ? dbError.message : String(dbError));
2604
2798
  }
2605
2799
  // Models are now Model instances, no registration needed
2606
2800
  // They should be created via Model.create() and are accessible via Model static methods
2607
2801
  for (const [modelName] of Object.entries(modelDefinitions)) {
2608
- logger$3('loadSchemaFromFile (internal) - model available:', modelName);
2802
+ logger$5('loadSchemaFromFile (internal) - model available:', modelName);
2609
2803
  }
2610
2804
  // Schema is now saved to database
2611
2805
  // useSchemas hook will pick it up via database queries
@@ -2616,7 +2810,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2616
2810
  // Ensure schema ID is defined (should always be set by this point, but check for safety)
2617
2811
  if (!schemaFile.id) {
2618
2812
  schemaFile.id = generateId();
2619
- logger$3('Generated schema ID before file path creation:', schemaFile.id);
2813
+ logger$5('Generated schema ID before file path creation:', schemaFile.id);
2620
2814
  }
2621
2815
  // Get the target file path using ID-based naming (preferred)
2622
2816
  const filePath = getSchemaFilePath(workingDir, schemaFile.metadata.name, version, schemaFile.id);
@@ -2635,7 +2829,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2635
2829
  const data = JSON.parse(content);
2636
2830
  if (data.$schema) {
2637
2831
  // File is already a complete schema, just load it
2638
- logger$3(`Schema file ${filePath} already exists and is complete, loading it`);
2832
+ logger$5(`Schema file ${filePath} already exists and is complete, loading it`);
2639
2833
  return await loadSchemaFromFile(filePath);
2640
2834
  }
2641
2835
  }
@@ -2653,7 +2847,7 @@ async function importJsonSchema(importFilePathOrContents, version = 1) {
2653
2847
  // Compare schemas structurally
2654
2848
  if (compareSchemasStructurally(schemaFile, existingSchema)) {
2655
2849
  // Schemas match - just load the existing file
2656
- logger$3(`Schema ${schemaFile.metadata.name} v${version} already exists with matching content, loading it`);
2850
+ logger$5(`Schema ${schemaFile.metadata.name} v${version} already exists with matching content, loading it`);
2657
2851
  return await loadSchemaFromFile(filePath);
2658
2852
  }
2659
2853
  else {
@@ -2709,8 +2903,8 @@ async function applySchemaToDb(schemaFile, modelDefinitions, modelFileIds, prope
2709
2903
  createdAt: new Date(schemaFile.metadata.createdAt).getTime(),
2710
2904
  updatedAt: new Date(schemaFile.metadata.updatedAt).getTime(),
2711
2905
  };
2712
- const _mod_6 = await import('./BasePathResolver-DxdS0wJ0.js');
2713
- const _ns_6 = _mod_6.b3;
2906
+ const _mod_6 = await import('./BasePathResolver-DrzGVH9L.js');
2907
+ const _ns_6 = _mod_6.bb;
2714
2908
  const dbMod = _ns_6;
2715
2909
  const { addSchemaToDb, addModelsToDb } = dbMod;
2716
2910
  const db = BaseDb.getAppDb();
@@ -2831,7 +3025,7 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2831
3025
  // Generate schema ID if missing (before creating schemaInput)
2832
3026
  if (!schemaFile.id) {
2833
3027
  schemaFile.id = generateId();
2834
- logger$3('Generated schema ID for schema file:', schemaFile.id);
3028
+ logger$5('Generated schema ID for schema file:', schemaFile.id);
2835
3029
  }
2836
3030
  // Extract schemaFileIds from JSON file and generate missing ones BEFORE creating models
2837
3031
  // This ensures Model instances are created with correct IDs
@@ -2841,7 +3035,7 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2841
3035
  // Generate model ID if missing
2842
3036
  if (!model.id) {
2843
3037
  model.id = generateId();
2844
- logger$3(`Generated model ID for ${modelName}:`, model.id);
3038
+ logger$5(`Generated model ID for ${modelName}:`, model.id);
2845
3039
  // Update the schemaFile object so it's included in schemaData
2846
3040
  schemaFile.models[modelName].id = model.id;
2847
3041
  }
@@ -2851,7 +3045,7 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2851
3045
  // Generate property ID if missing
2852
3046
  if (!prop.id) {
2853
3047
  prop.id = generateId();
2854
- logger$3(`Generated property ID for ${modelName}.${propName}:`, prop.id);
3048
+ logger$5(`Generated property ID for ${modelName}.${propName}:`, prop.id);
2855
3049
  // Update the schemaFile object so it's included in schemaData
2856
3050
  schemaFile.models[modelName].properties[propName].id = prop.id;
2857
3051
  }
@@ -2863,7 +3057,7 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2863
3057
  }
2864
3058
  // Convert JSON models to Model classes, passing modelFileIds and propertyFileIds so Model instances use correct IDs
2865
3059
  const modelDefinitions = await createModelsFromJson(importData, modelFileIds, propertyFileIds);
2866
- logger$3('loadSchemaFromFile - modelDefinitions length:', Object.keys(modelDefinitions).length);
3060
+ logger$5('loadSchemaFromFile - modelDefinitions length:', Object.keys(modelDefinitions).length);
2867
3061
  // Try to add schema and models to database if database is available
2868
3062
  try {
2869
3063
  const db = BaseDb.getAppDb();
@@ -2877,12 +3071,12 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2877
3071
  ]));
2878
3072
  const { schemaRecord } = await applySchemaToDb(schemaFile, modelDefinitions, modelFileIds, propertyFileIds, propertyDefinitions);
2879
3073
  // Query the database to get model IDs that were just created
2880
- const _mod_7 = await import('./BasePathResolver-DxdS0wJ0.js');
2881
- const _ns_7 = _mod_7.b1;
3074
+ const _mod_7 = await import('./BasePathResolver-DrzGVH9L.js');
3075
+ const _ns_7 = _mod_7.b9;
2882
3076
  const modelSchemaSchemaMod = _ns_7;
2883
3077
  const { modelSchemas } = modelSchemaSchemaMod;
2884
- const _mod_8 = await import('./BasePathResolver-DxdS0wJ0.js');
2885
- const _ns_8 = _mod_8.a_;
3078
+ const _mod_8 = await import('./BasePathResolver-DrzGVH9L.js');
3079
+ const _ns_8 = _mod_8.b6;
2886
3080
  const modelSchemaMod = _ns_8;
2887
3081
  const { models: modelsTable } = modelSchemaMod;
2888
3082
  const drizzleMod = await import('drizzle-orm');
@@ -2929,17 +3123,17 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2929
3123
  await new Promise(resolve => setTimeout(resolve, 100));
2930
3124
  }
2931
3125
  }
2932
- logger$3(`Verifying properties: found ${modelLinks.length} model links for schema ${schemaName} (id: ${schemaRecord.id})`);
3126
+ logger$5(`Verifying properties: found ${modelLinks.length} model links for schema ${schemaName} (id: ${schemaRecord.id})`);
2933
3127
  // If we didn't verify via direct lookup, verify via join table
2934
3128
  if (!seedModelId || (seedModel.length === 0 || !seedModel[0].id)) {
2935
3129
  const seedModelLink = modelLinks.find((link) => link.modelName === 'Seed');
2936
3130
  if (seedModelLink && seedModelLink.modelId) {
2937
- logger$3(`Verifying properties for Seed model via join table (modelId: ${seedModelLink.modelId})`);
3131
+ logger$5(`Verifying properties for Seed model via join table (modelId: ${seedModelLink.modelId})`);
2938
3132
  await verifyPropertiesPersisted(db, seedModelLink.modelId, 'Seed', 10, 100);
2939
3133
  }
2940
3134
  else if (modelLinks.length > 0 && modelLinks[0].modelId) {
2941
3135
  // Fallback to first model if Seed not found
2942
- logger$3(`Verifying properties for ${modelLinks[0].modelName} model via join table (modelId: ${modelLinks[0].modelId})`);
3136
+ logger$5(`Verifying properties for ${modelLinks[0].modelName} model via join table (modelId: ${modelLinks[0].modelId})`);
2943
3137
  await verifyPropertiesPersisted(db, modelLinks[0].modelId, modelLinks[0].modelName || 'unknown', 10, 100);
2944
3138
  }
2945
3139
  }
@@ -2947,8 +3141,8 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2947
3141
  // After properties are created (file path), ensure schemaFile has the correct IDs from database
2948
3142
  // Query the database to get the actual schemaFileId values that were used
2949
3143
  // This ensures schemaData matches what's actually in the database
2950
- const _mod_9 = await import('./BasePathResolver-DxdS0wJ0.js');
2951
- const _ns_9 = _mod_9.a_;
3144
+ const _mod_9 = await import('./BasePathResolver-DrzGVH9L.js');
3145
+ const _ns_9 = _mod_9.b6;
2952
3146
  const modelSchemaMod2 = _ns_9;
2953
3147
  const { properties: propertiesTable } = modelSchemaMod2;
2954
3148
  let schemaFileUpdated = false;
@@ -2972,7 +3166,7 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2972
3166
  const currentId = schemaFile.models[modelName].properties[propRecord.name].id;
2973
3167
  if (currentId !== propRecord.schemaFileId) {
2974
3168
  schemaFile.models[modelName].properties[propRecord.name].id = propRecord.schemaFileId;
2975
- logger$3(`Updated schemaFile property ID for ${modelName}.${propRecord.name} from ${currentId} to ${propRecord.schemaFileId}`);
3169
+ logger$5(`Updated schemaFile property ID for ${modelName}.${propRecord.name} from ${currentId} to ${propRecord.schemaFileId}`);
2976
3170
  schemaFileUpdated = true;
2977
3171
  }
2978
3172
  }
@@ -2983,8 +3177,8 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2983
3177
  // This is important even if nothing changed, to ensure consistency
2984
3178
  const updatedSchemaData = JSON.stringify(schemaFile, null, 2);
2985
3179
  // Update the schema record in the database with current schemaData
2986
- const _mod_10 = await import('./BasePathResolver-DxdS0wJ0.js');
2987
- const _ns_10 = _mod_10.b0;
3180
+ const _mod_10 = await import('./BasePathResolver-DrzGVH9L.js');
3181
+ const _ns_10 = _mod_10.b8;
2988
3182
  const schemaSchemaMod = _ns_10;
2989
3183
  const { schemas: schemasTable } = schemaSchemaMod;
2990
3184
  await db
@@ -2992,21 +3186,21 @@ const loadSchemaFromFile = async (schemaFilePath) => {
2992
3186
  .set({ schemaData: updatedSchemaData })
2993
3187
  .where(eq(schemasTable.id, schemaRecord.id));
2994
3188
  if (schemaFileUpdated) {
2995
- logger$3(`Updated schemaData in database with actual property IDs for schema "${schemaName}"`);
3189
+ logger$5(`Updated schemaData in database with actual property IDs for schema "${schemaName}"`);
2996
3190
  }
2997
3191
  else {
2998
- logger$3(`Verified schemaData in database matches schemaFile for schema "${schemaName}"`);
3192
+ logger$5(`Verified schemaData in database matches schemaFile for schema "${schemaName}"`);
2999
3193
  }
3000
3194
  }
3001
3195
  }
3002
3196
  catch (dbError) {
3003
3197
  // Database not available - log warning but continue
3004
- logger$3('Database not available, skipping database operations:', dbError instanceof Error ? dbError.message : String(dbError));
3198
+ logger$5('Database not available, skipping database operations:', dbError instanceof Error ? dbError.message : String(dbError));
3005
3199
  }
3006
3200
  // Models are now Model instances, no registration needed
3007
3201
  // They should be created via Model.create() and are accessible via Model static methods
3008
3202
  for (const [modelName] of Object.entries(modelDefinitions)) {
3009
- logger$3('loadSchemaFromFile - model available:', modelName);
3203
+ logger$5('loadSchemaFromFile - model available:', modelName);
3010
3204
  }
3011
3205
  return schemaFilePath;
3012
3206
  }
@@ -3093,7 +3287,7 @@ const jsonPropertiesToSchemaProperties = (modelName, modelDef, propertyFileIds)
3093
3287
  */
3094
3288
  const createModelFromJson = async (modelName, modelDef, schemaName, modelFileId, // Optional modelFileId from JSON file
3095
3289
  propertyFileIds) => {
3096
- const _mod_11 = await import('./errors-Cl8rbtKR.js');
3290
+ const _mod_11 = await import('./errors-DdWbxb_n.js');
3097
3291
  const _ns_11 = _mod_11.h;
3098
3292
  const modelMod = _ns_11;
3099
3293
  const { Model } = modelMod;
@@ -3102,10 +3296,10 @@ propertyFileIds) => {
3102
3296
  // Pass id (schemaFileId) if provided to ensure Model instance uses correct ID
3103
3297
  // Note: indexes and description are ignored - they remain in JSON for schema file format compliance but are not used by Model instances
3104
3298
  if (modelFileId) {
3105
- logger$3(`Creating schema model "${modelName}" with modelFileId: ${modelFileId}`);
3299
+ logger$5(`Creating schema model "${modelName}" with modelFileId: ${modelFileId}`);
3106
3300
  }
3107
3301
  else {
3108
- logger$3(`Warning: Creating schema model "${modelName}" without modelFileId - it will be renamed if duplicates exist`);
3302
+ logger$5(`Warning: Creating schema model "${modelName}" without modelFileId - it will be renamed if duplicates exist`);
3109
3303
  }
3110
3304
  const modelInstance = Model.create(modelName, schemaName, {
3111
3305
  modelFileId: modelFileId, // Pass modelFileId (preferred) as id (schemaFileId) from JSON file
@@ -3131,7 +3325,7 @@ const createModelsFromJson = async (importData, modelFileIds, propertyFileIds) =
3131
3325
  // Use modelFileId from map (no fallback to modelDef.id as JsonImportSchema doesn't have id property)
3132
3326
  const finalModelFileId = modelFileId;
3133
3327
  if (!finalModelFileId) {
3134
- logger$3(`Warning: No modelFileId found for model "${modelName}" - model will be created without ID`);
3328
+ logger$5(`Warning: No modelFileId found for model "${modelName}" - model will be created without ID`);
3135
3329
  }
3136
3330
  // Get propertyFileIds for this model if available
3137
3331
  const modelPropertyFileIds = propertyFileIds?.get(modelName);
@@ -3321,7 +3515,7 @@ var SEEDPROTOCOL_Seed_Protocol_v1 = /*#__PURE__*/Object.freeze({
3321
3515
  version: version
3322
3516
  });
3323
3517
 
3324
- const logger$2 = debug('seedSdk:client:actors:processSchemaFiles');
3518
+ const logger$4 = debug('seedSdk:client:actors:processSchemaFiles');
3325
3519
  let processSchemaFilesChain = Promise.resolve();
3326
3520
  function resolveSchemaFilePath(schemaFile) {
3327
3521
  const path = BaseFileManager.getPathModule();
@@ -3349,7 +3543,7 @@ async function resolveSchemaNameFromSource(source) {
3349
3543
  }
3350
3544
  }
3351
3545
  catch (error) {
3352
- logger$2('Could not resolve schema name from path:', source, error);
3546
+ logger$4('Could not resolve schema name from path:', source, error);
3353
3547
  }
3354
3548
  }
3355
3549
  return null;
@@ -3364,7 +3558,7 @@ function userSchemaLoadError(option, source, error) {
3364
3558
  // Schema processing can be slow with syncSchemaFromSource, loadAllSchemasFromDb, and OPFS file I/O
3365
3559
  const PROCESS_SCHEMA_FILES_TIMEOUT_MS = 120000;
3366
3560
  const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3367
- logger$2('processSchemaFiles started');
3561
+ logger$4('processSchemaFiles started');
3368
3562
  let hasResponded = false;
3369
3563
  let timeoutId = null;
3370
3564
  const reportError = (error) => {
@@ -3374,7 +3568,7 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3374
3568
  clearTimeout(timeoutId);
3375
3569
  timeoutId = null;
3376
3570
  }
3377
- logger$2('processSchemaFiles error:', error);
3571
+ logger$4('processSchemaFiles error:', error);
3378
3572
  sendBack({
3379
3573
  type: 'error',
3380
3574
  error: error instanceof Error ? error : new Error(String(error))
@@ -3388,7 +3582,7 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3388
3582
  clearTimeout(timeoutId);
3389
3583
  timeoutId = null;
3390
3584
  }
3391
- logger$2('processSchemaFiles completed');
3585
+ logger$4('processSchemaFiles completed');
3392
3586
  sendBack({ type: ClientManagerEvents.PROCESS_SCHEMA_FILES_SUCCESS });
3393
3587
  }
3394
3588
  };
@@ -3400,7 +3594,7 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3400
3594
  // Schemas written (or confirmed present) this run — skip redundant loadSchemaFromFile later
3401
3595
  const schemasWrittenThisRun = new Set();
3402
3596
  // First, load the internal seed-protocol schema
3403
- logger$2('Loading internal seed-protocol schema');
3597
+ logger$4('Loading internal seed-protocol schema');
3404
3598
  const internalSchemaData = internalSchema;
3405
3599
  const db = BaseDb.getAppDb();
3406
3600
  const internalSchemaExists = db
@@ -3409,21 +3603,21 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3409
3603
  // Always mark internal schema as written so the DB/file loop does not re-apply it
3410
3604
  schemasWrittenThisRun.add(SEED_PROTOCOL_SCHEMA_NAME);
3411
3605
  if (internalSchemaExists) {
3412
- logger$2('Internal seed-protocol schema already in DB, skipping import');
3606
+ logger$4('Internal seed-protocol schema already in DB, skipping import');
3413
3607
  }
3414
3608
  else {
3415
3609
  try {
3416
3610
  // Import the internal schema to database (will create models and add to store)
3417
3611
  await importJsonSchema({ contents: JSON.stringify(internalSchemaData) }, internalSchemaData.version);
3418
- logger$2(`Loaded internal seed-protocol schema v${internalSchemaData.version}`);
3612
+ logger$4(`Loaded internal seed-protocol schema v${internalSchemaData.version}`);
3419
3613
  }
3420
3614
  catch (error) {
3421
3615
  // If schema already exists, that's fine - it means it was already loaded
3422
3616
  if (error?.message?.includes('already exists')) {
3423
- logger$2('Internal seed-protocol schema already loaded');
3617
+ logger$4('Internal seed-protocol schema already loaded');
3424
3618
  }
3425
3619
  else {
3426
- logger$2('Error loading internal seed-protocol schema:', error);
3620
+ logger$4('Error loading internal seed-protocol schema:', error);
3427
3621
  // Don't fail if internal schema can't be loaded - it might already be in DB
3428
3622
  }
3429
3623
  }
@@ -3436,7 +3630,7 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3436
3630
  : '';
3437
3631
  const schemaId = typeof context.schema === 'object' && context.schema?.id ? String(context.schema.id) : undefined;
3438
3632
  if (isInternalSchema(schemaName, schemaId)) {
3439
- logger$2('Schema is internal (Seed Protocol), already loaded - skipping syncSchemaFromSource');
3633
+ logger$4('Schema is internal (Seed Protocol), already loaded - skipping syncSchemaFromSource');
3440
3634
  }
3441
3635
  else {
3442
3636
  try {
@@ -3445,7 +3639,7 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3445
3639
  if (syncedName) {
3446
3640
  schemasWrittenThisRun.add(syncedName);
3447
3641
  }
3448
- logger$2(`Synced canonical schema from ${typeof context.schema === 'string' ? context.schema : 'object'}`);
3642
+ logger$4(`Synced canonical schema from ${typeof context.schema === 'string' ? context.schema : 'object'}`);
3449
3643
  }
3450
3644
  catch (error) {
3451
3645
  // A schema the app passed in failed (missing file, bad JSON, DB write). Re-running an
@@ -3464,7 +3658,7 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3464
3658
  if (parsed?.metadata?.name) {
3465
3659
  schemasWrittenThisRun.add(String(parsed.metadata.name));
3466
3660
  }
3467
- logger$2(`Loaded schema from ${resolvedPath}`);
3661
+ logger$4(`Loaded schema from ${resolvedPath}`);
3468
3662
  }
3469
3663
  catch (error) {
3470
3664
  throw userSchemaLoadError('schemaFile', context.schemaFile, error);
@@ -3472,14 +3666,14 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3472
3666
  }
3473
3667
  // Process minimal import files (files without $schema) - skip when canonical schema is set
3474
3668
  if (!context.schema) {
3475
- logger$2('Listing schema files');
3669
+ logger$4('Listing schema files');
3476
3670
  let schemaFiles = [];
3477
3671
  try {
3478
3672
  schemaFiles = await listSchemaFiles();
3479
- logger$2(`Found ${schemaFiles.length} schema files to process`);
3673
+ logger$4(`Found ${schemaFiles.length} schema files to process`);
3480
3674
  }
3481
3675
  catch (error) {
3482
- logger$2('Error listing schema files (continuing anyway):', error);
3676
+ logger$4('Error listing schema files (continuing anyway):', error);
3483
3677
  schemaFiles = [];
3484
3678
  }
3485
3679
  for (const schemaFile of schemaFiles) {
@@ -3490,19 +3684,19 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3490
3684
  }
3491
3685
  }
3492
3686
  catch (error) {
3493
- logger$2(`Error importing schema file ${schemaFile.filePath}:`, error);
3687
+ logger$4(`Error importing schema file ${schemaFile.filePath}:`, error);
3494
3688
  }
3495
3689
  }
3496
3690
  }
3497
3691
  // Then, load all schemas using the unified database-first approach
3498
- logger$2('Loading schemas from database and files');
3692
+ logger$4('Loading schemas from database and files');
3499
3693
  let allSchemasData = [];
3500
3694
  try {
3501
3695
  allSchemasData = await loadAllSchemasFromDb();
3502
- logger$2(`Loaded ${allSchemasData.length} schemas (${allSchemasData.filter(s => s.isDraft).length} drafts, ${allSchemasData.filter(s => !s.isDraft).length} published)`);
3696
+ logger$4(`Loaded ${allSchemasData.length} schemas (${allSchemasData.filter(s => s.isDraft).length} drafts, ${allSchemasData.filter(s => !s.isDraft).length} published)`);
3503
3697
  }
3504
3698
  catch (error) {
3505
- logger$2('Error loading schemas from database (continuing anyway):', error);
3699
+ logger$4('Error loading schemas from database (continuing anyway):', error);
3506
3700
  // Continue with empty array - this is not critical for initialization
3507
3701
  // Schemas can be loaded on-demand later
3508
3702
  allSchemasData = [];
@@ -3523,13 +3717,13 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3523
3717
  }
3524
3718
  }
3525
3719
  catch (error) {
3526
- logger$2('Error counting linked models (re-applying all schema files):', error);
3720
+ logger$4('Error counting linked models (re-applying all schema files):', error);
3527
3721
  }
3528
3722
  }
3529
3723
  for (const schemaData of allSchemasData) {
3530
3724
  const schema = schemaData.schema;
3531
3725
  const schemaName = schema.metadata?.name || 'unknown';
3532
- logger$2(`Processing schema: ${schemaName} v${schema.version} (${schemaData.isDraft ? 'draft' : 'published'}, source: ${schemaData.source})`);
3726
+ logger$4(`Processing schema: ${schemaName} v${schema.version} (${schemaData.isDraft ? 'draft' : 'published'}, source: ${schemaData.source})`);
3533
3727
  // For published schemas (not drafts), load models from file if available
3534
3728
  if (!schemaData.isDraft && schema.id) {
3535
3729
  // loadAllSchemasFromDb already picked the latest file for this schema (and read it)
@@ -3559,7 +3753,7 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3559
3753
  }
3560
3754
  }
3561
3755
  catch (error) {
3562
- logger$2(`Error loading schema file for ${schemaName}:`, error);
3756
+ logger$4(`Error loading schema file for ${schemaName}:`, error);
3563
3757
  }
3564
3758
  }
3565
3759
  else {
@@ -3608,11 +3802,11 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3608
3802
  };
3609
3803
  const modelDefinitions = await createModelsFromJson(importData, modelFileIds, propertyFileIds);
3610
3804
  Object.assign(allModels, modelDefinitions);
3611
- logger$2(`Loaded ${Object.keys(modelDefinitions).length} models from schema data for ${schemaName}`);
3805
+ logger$4(`Loaded ${Object.keys(modelDefinitions).length} models from schema data for ${schemaName}`);
3612
3806
  }
3613
3807
  }
3614
3808
  catch (error) {
3615
- logger$2(`Error creating models from schema data for ${schemaName}:`, error);
3809
+ logger$4(`Error creating models from schema data for ${schemaName}:`, error);
3616
3810
  }
3617
3811
  }
3618
3812
  }
@@ -3662,11 +3856,11 @@ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3662
3856
  };
3663
3857
  const modelDefinitions = await createModelsFromJson(importData, modelFileIds, propertyFileIds);
3664
3858
  Object.assign(allModels, modelDefinitions);
3665
- logger$2(`Loaded ${Object.keys(modelDefinitions).length} models from draft schema ${schemaName}`);
3859
+ logger$4(`Loaded ${Object.keys(modelDefinitions).length} models from draft schema ${schemaName}`);
3666
3860
  }
3667
3861
  }
3668
3862
  catch (error) {
3669
- logger$2(`Error creating models from draft schema ${schemaName}:`, error);
3863
+ logger$4(`Error creating models from draft schema ${schemaName}:`, error);
3670
3864
  }
3671
3865
  }
3672
3866
  // Schemas are now loaded on-demand via database queries (useSchemas hook uses loadAllSchemasFromDb)
@@ -3763,7 +3957,7 @@ const convertVerbatimItemStorageRow = async (localId, storage = {}) => {
3763
3957
  });
3764
3958
  };
3765
3959
 
3766
- const logger$1 = debug('seedSdk:db:migrateLegacyItemStorageRows');
3960
+ const logger$3 = debug('seedSdk:db:migrateLegacyItemStorageRows');
3767
3961
  /** appState key recording that legacy ItemStorage rows have been written to their files. */
3768
3962
  const LEGACY_ITEM_STORAGE_MIGRATION_KEY = 'legacyItemStorageFiles:v1';
3769
3963
  /** Runs in a row that left rows unconverted because a file write failed, before giving up. */
@@ -3917,7 +4111,7 @@ async function migrateLegacyItemStorageRows(options = {}) {
3917
4111
  });
3918
4112
  if (converted) {
3919
4113
  result.converted++;
3920
- logger$1(`wrote ${row.propertyName} of ${row.seedLocalId} to ${converted.filePath}`);
4114
+ logger$3(`wrote ${row.propertyName} of ${row.seedLocalId} to ${converted.filePath}`);
3921
4115
  }
3922
4116
  else {
3923
4117
  result.skipped++;
@@ -3925,7 +4119,7 @@ async function migrateLegacyItemStorageRows(options = {}) {
3925
4119
  }
3926
4120
  catch (error) {
3927
4121
  result.failed++;
3928
- logger$1(`converting ${row.propertyName} of ${row.seedLocalId} (${row.localId}) failed; left as is`, error);
4122
+ logger$3(`converting ${row.propertyName} of ${row.seedLocalId} (${row.localId}) failed; left as is`, error);
3929
4123
  }
3930
4124
  }
3931
4125
  const attempts = previousAttempts + 1;
@@ -3937,24 +4131,24 @@ async function migrateLegacyItemStorageRows(options = {}) {
3937
4131
  at: Date.now(),
3938
4132
  });
3939
4133
  if (result.candidates > 0) {
3940
- logger$1(`legacy ItemStorage rows: ${result.converted} converted, ${result.failed} failed, ` +
4134
+ logger$3(`legacy ItemStorage rows: ${result.converted} converted, ${result.failed} failed, ` +
3941
4135
  `${result.skipped} skipped of ${result.candidates}`);
3942
4136
  }
3943
4137
  return result;
3944
4138
  }
3945
- let running;
3946
- let deferredUntilLeader = false;
3947
- const runInBackground = () => {
3948
- running ?? (running = withDbTabLock('item-storage-migration', () => migrateLegacyItemStorageRows())
4139
+ let running$2;
4140
+ let deferredUntilLeader$2 = false;
4141
+ const runInBackground$2 = () => {
4142
+ running$2 ?? (running$2 = withDbTabLock('item-storage-migration', () => migrateLegacyItemStorageRows())
3949
4143
  .then(() => undefined)
3950
4144
  .catch((error) => {
3951
4145
  // Never fails init: rows stay as they were and publish converts them lazily.
3952
- logger$1('legacy ItemStorage migration failed', error);
4146
+ logger$3('legacy ItemStorage migration failed', error);
3953
4147
  })
3954
4148
  .finally(() => {
3955
- running = undefined;
4149
+ running$2 = undefined;
3956
4150
  }));
3957
- return running;
4151
+ return running$2;
3958
4152
  };
3959
4153
  /**
3960
4154
  * Starts {@link migrateLegacyItemStorageRows} in the background once client init has finished
@@ -3963,6 +4157,176 @@ const runInBackground = () => {
3963
4157
  * (`multiTab: 'off'`); a tab that isn't leader runs it if it takes over. Never throws.
3964
4158
  */
3965
4159
  function startLegacyItemStorageMigration() {
4160
+ if (isLeaderTab()) {
4161
+ void runInBackground$2();
4162
+ return;
4163
+ }
4164
+ if (deferredUntilLeader$2)
4165
+ return;
4166
+ deferredUntilLeader$2 = true;
4167
+ void whenLeaderTab().then(() => {
4168
+ deferredUntilLeader$2 = false;
4169
+ void runInBackground$2();
4170
+ });
4171
+ }
4172
+
4173
+ const logger$2 = debug('seedSdk:db:removeForeignAttestationRows');
4174
+ /** appState key recording that the cleanup below has run on this database. */
4175
+ const FOREIGN_ATTESTATION_CLEANUP_KEY = 'foreignAttestationRowsRemoved';
4176
+ /** Lowercased `attester` of a stored attestation JSON; NULL when the JSON is invalid or has none. */
4177
+ const attesterOf = (raw) => sql `(CASE WHEN json_valid(${raw}) THEN lower(json_extract(${raw}, '$.attester')) END)`;
4178
+ /**
4179
+ * Deletes rows that an SDK without the attester check (partitionByRefAttester) synced from other
4180
+ * accounts' attestations: versions whose attester isn't their seed's, and
4181
+ * properties whose attester isn't their version's, plus the properties of those versions. Sync
4182
+ * removes them too, but only for the seeds it reaches, and only online; this needs no network.
4183
+ *
4184
+ * Only rows synced from EAS on both sides are compared, by the `attester` in their stored
4185
+ * attestation, as sync compares them: local drafts and rows without a stored attestation are
4186
+ * kept, like partitionByRefAttester keeps children whose attesters aren't both known.
4187
+ */
4188
+ async function removeForeignAttestationRows() {
4189
+ const appDb = BaseDb.getAppDb();
4190
+ const foreignVersions = await appDb
4191
+ .select({ uid: versions.uid })
4192
+ .from(versions)
4193
+ .innerJoin(seeds, eq(seeds.localId, versions.seedLocalId))
4194
+ .where(and(isNotNull(versions.uid), sql `${attesterOf(versions.attestationRaw)} <> ${attesterOf(seeds.attestationRaw)}`));
4195
+ const foreignVersionUids = foreignVersions.map((row) => row.uid).filter(Boolean);
4196
+ const foreignProperties = await appDb
4197
+ .select({ localId: metadata$1.localId })
4198
+ .from(metadata$1)
4199
+ .innerJoin(versions, eq(versions.uid, metadata$1.versionUid))
4200
+ .where(and(isNotNull(metadata$1.uid), sql `${attesterOf(metadata$1.attestationRaw)} <> ${attesterOf(versions.attestationRaw)}`));
4201
+ const foreignPropertyLocalIds = foreignProperties.map((row) => row.localId).filter(Boolean);
4202
+ let propertyRows = foreignPropertyLocalIds.length;
4203
+ await writeInBatches(foreignPropertyLocalIds, (chunk) => appDb.delete(metadata$1).where(inArray(metadata$1.localId, chunk)));
4204
+ await writeInBatches(foreignVersionUids, async (chunk) => {
4205
+ const removed = await appDb
4206
+ .delete(metadata$1)
4207
+ .where(inArray(metadata$1.versionUid, chunk))
4208
+ .returning({ localId: metadata$1.localId });
4209
+ propertyRows += removed.length;
4210
+ });
4211
+ await writeInBatches(foreignVersionUids, (chunk) => appDb.delete(versions).where(inArray(versions.uid, chunk)));
4212
+ return { versionRows: foreignVersionUids.length, propertyRows };
4213
+ }
4214
+ /** Runs the cleanup unless this database has had it, and records that it has. */
4215
+ async function runOnce() {
4216
+ const appDb = BaseDb.getAppDb();
4217
+ const marker = await appDb
4218
+ .select()
4219
+ .from(appState)
4220
+ .where(eq(appState.key, FOREIGN_ATTESTATION_CLEANUP_KEY))
4221
+ .limit(1);
4222
+ if (marker.length > 0)
4223
+ return;
4224
+ const result = await removeForeignAttestationRows();
4225
+ const value = JSON.stringify({ ...result, at: Date.now() });
4226
+ await appDb
4227
+ .insert(appState)
4228
+ .values({ key: FOREIGN_ATTESTATION_CLEANUP_KEY, value })
4229
+ .onConflictDoUpdate({ target: appState.key, set: { value } });
4230
+ if (result.versionRows > 0 || result.propertyRows > 0) {
4231
+ logger$2(`removed ${result.versionRows} version and ${result.propertyRows} property rows from other attesters`);
4232
+ }
4233
+ }
4234
+ let running$1;
4235
+ let deferredUntilLeader$1 = false;
4236
+ const runInBackground$1 = () => {
4237
+ // Under the sync's lock, so it can't interleave with a sync writing the same rows.
4238
+ running$1 ?? (running$1 = withDbTabLock('eas-sync', runOnce, { timeoutMs: Infinity })
4239
+ .catch((error) => {
4240
+ // Never fails init: the rows stay until the next sync of their seeds removes them.
4241
+ logger$2('foreign attestation cleanup failed', error);
4242
+ })
4243
+ .finally(() => {
4244
+ running$1 = undefined;
4245
+ }));
4246
+ return running$1;
4247
+ };
4248
+ /**
4249
+ * Starts {@link removeForeignAttestationRows} in the background after client init, once per
4250
+ * database: a one-time data fix, so an init step rather than a SQL migration (it needs the
4251
+ * attester out of stored JSON, the same rule as sync, and must never block or fail init). Leader
4252
+ * tab only (docs/MULTI_TAB.md); a tab that isn't leader runs it if it takes over. Never throws.
4253
+ */
4254
+ function startForeignAttestationCleanup() {
4255
+ if (isLeaderTab()) {
4256
+ void runInBackground$1();
4257
+ return;
4258
+ }
4259
+ if (deferredUntilLeader$1)
4260
+ return;
4261
+ deferredUntilLeader$1 = true;
4262
+ void whenLeaderTab().then(() => {
4263
+ deferredUntilLeader$1 = false;
4264
+ void runInBackground$1();
4265
+ });
4266
+ }
4267
+
4268
+ const logger$1 = debug('seedSdk:db:canonicalizeModelTypes');
4269
+ /**
4270
+ * Model types in the local DB (`seeds.type`, `versions.seed_type`, `metadata.model_type`) are the
4271
+ * EAS name of the model (ADR 0008): `html_page` for HTMLPage. Rows written before that hold other
4272
+ * forms: drizzle's snake_case for local seeds (`image_v_2`), or the name an older SDK published,
4273
+ * for synced ones (`htmlpage`). This rewrites each stored type that matches exactly one local model
4274
+ * name (in the tightest tier, as sync matches) to that model's type. Types matching no model, or
4275
+ * several, are left alone. Returns the number of types rewritten.
4276
+ */
4277
+ async function canonicalizeModelTypes() {
4278
+ const appDb = BaseDb.getAppDb();
4279
+ const modelNames = (await appDb.selectDistinct({ name: models$1.name }).from(models$1))
4280
+ .map((row) => row.name)
4281
+ .filter(Boolean);
4282
+ if (modelNames.length === 0)
4283
+ return 0;
4284
+ const storedTypes = new Set();
4285
+ const collect = (rows) => {
4286
+ for (const row of rows)
4287
+ if (row.type)
4288
+ storedTypes.add(row.type);
4289
+ };
4290
+ collect(await appDb.selectDistinct({ type: seeds.type }).from(seeds).where(isNotNull(seeds.type)));
4291
+ collect(await appDb.selectDistinct({ type: versions.seedType }).from(versions).where(isNotNull(versions.seedType)));
4292
+ collect(await appDb.selectDistinct({ type: metadata$1.modelType }).from(metadata$1).where(isNotNull(metadata$1.modelType)));
4293
+ let rewritten = 0;
4294
+ for (const type of storedTypes) {
4295
+ const canonical = new Set([...localNamesInBestMatchTier(type, modelNames)].map((name) => easName(name)));
4296
+ if (canonical.size !== 1)
4297
+ continue;
4298
+ const [next] = canonical;
4299
+ if (!next || next === type)
4300
+ continue;
4301
+ await appDb.update(seeds).set({ type: next }).where(eq(seeds.type, type));
4302
+ await appDb.update(versions).set({ seedType: next }).where(eq(versions.seedType, type));
4303
+ await appDb.update(metadata$1).set({ modelType: next }).where(eq(metadata$1.modelType, type));
4304
+ logger$1(`model type ${type} → ${next}`);
4305
+ rewritten++;
4306
+ }
4307
+ return rewritten;
4308
+ }
4309
+ let running;
4310
+ let deferredUntilLeader = false;
4311
+ const runInBackground = () => {
4312
+ // Under the sync's lock: sync writes these columns too.
4313
+ running ?? (running = withDbTabLock('eas-sync', () => canonicalizeModelTypes(), { timeoutMs: Infinity })
4314
+ .then(() => undefined)
4315
+ .catch((error) => {
4316
+ // Never fails init: readers still match old types by name, only exact type filters miss them.
4317
+ logger$1('canonicalizing model types failed', error);
4318
+ })
4319
+ .finally(() => {
4320
+ running = undefined;
4321
+ }));
4322
+ return running;
4323
+ };
4324
+ /**
4325
+ * Starts {@link canonicalizeModelTypes} in the background after client init, in the leader tab
4326
+ * (docs/MULTI_TAB.md). It runs on every init: it reads only the distinct stored types, and a
4327
+ * model added since the last run may match types that matched nothing before. Never throws.
4328
+ */
4329
+ function startCanonicalizeModelTypes() {
3966
4330
  if (isLeaderTab()) {
3967
4331
  void runInBackground();
3968
4332
  return;
@@ -4204,6 +4568,10 @@ const clientManagerMachine = setup({
4204
4568
  // In the background after init: the app is usable at once, the properties table holds the
4205
4569
  // current storage settings by now, and a failure never fails init.
4206
4570
  () => startLegacyItemStorageMigration(),
4571
+ // Also once, in the background: rows an older sync stored from other accounts' attestations.
4572
+ () => startForeignAttestationCleanup(),
4573
+ // Stored model types to the ADR 0008 form (html_page), so exact type filters find old rows.
4574
+ () => startCanonicalizeModelTypes(),
4207
4575
  ],
4208
4576
  on: {
4209
4577
  [SAVE_APP_STATE_SUCCESS]: {
@@ -4295,12 +4663,6 @@ function msToUnixSeconds(ms) {
4295
4663
  return Math.floor(ms / 1000);
4296
4664
  return Math.floor(ms);
4297
4665
  }
4298
- function toSnakeCaseName(name) {
4299
- return name
4300
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
4301
- .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')
4302
- .toLowerCase();
4303
- }
4304
4666
  function parsePropertyValue(raw) {
4305
4667
  if (raw == null)
4306
4668
  return '';
@@ -4417,7 +4779,7 @@ function metadataRowToAttestation(row) {
4417
4779
  }, row.revokedAt);
4418
4780
  }
4419
4781
  const name = row.propertyName
4420
- ? toSnakeCaseName(row.propertyName)
4782
+ ? easName(row.propertyName)
4421
4783
  : 'unknown';
4422
4784
  const value = parsePropertyValue(row.propertyValue);
4423
4785
  const type = row.easDataType || 'string';
@@ -5124,12 +5486,14 @@ const clientInstance = {
5124
5486
  stop: () => {
5125
5487
  ensureInitialized();
5126
5488
  activeInitConfig = undefined;
5489
+ cancelAllRetriesWhenOnline();
5127
5490
  unregisterSeedQueryLocalSource();
5128
5491
  clientManager.stop();
5129
5492
  },
5130
5493
  unload: () => {
5131
5494
  ensureInitialized();
5132
5495
  activeInitConfig = undefined;
5496
+ cancelAllRetriesWhenOnline();
5133
5497
  unregisterSeedQueryLocalSource();
5134
5498
  clientManager.stop();
5135
5499
  subscription.unsubscribe();
@@ -5150,5 +5514,5 @@ var ClientManager$1 = /*#__PURE__*/Object.freeze({
5150
5514
  getClient: getClient
5151
5515
  });
5152
5516
 
5153
- export { requestEasSyncFromModelsInit as A, sendEasSyncClientRequest as B, ClientManager as C, startEasSyncActor as D, EAS_CHAIN_ID_APP_STATE_KEY as E, unregisterSeedQueryLocalSource as F, createModelsFromJson as G, getLatestSchemaVersion as H, INIT_CONFIG_CONFLICT_MESSAGE as I, jsonPropertiesToSchemaProperties as J, schema as K, ClientManager$1 as L, SEEDPROTOCOL_Seed_Protocol_v1 as S, assertLocalDbChain as a, importJsonSchema as b, convertVerbatimItemStorageRow as c, getContentHash as d, claimUnpublishedDrafts as e, createLocalQueryDataSource as f, getRefValueType as g, createSchema as h, isVerbatimItemStorageDraftRow as i, easSyncActor as j, getClient as k, loadSchemaFromFile as l, getPublishedSeedRecord as m, getSchemaNameFromId as n, isSeedQueryLocalSourceRegistered as o, listCompleteSchemaFiles as p, loadAllSchemasFromDb as q, readJsonImportFile as r, syncSchemaFromSource as s, transformImportToSchemaFile as t, mergeEasSyncRequestIntents as u, readSchema as v, waitForEasReadChain as w, registerSeedQueryLocalSource as x, requestEasSyncFromAddressChange as y, requestEasSyncFromEventBus as z };
5154
- //# sourceMappingURL=ClientManager-De-vKHGe.js.map
5517
+ export { requestEasSyncFromModelsInit as A, sendEasSyncClientRequest as B, ClientManager as C, setRetriesWhenOnlineEnabled as D, EAS_CHAIN_ID_APP_STATE_KEY as E, startEasSyncActor as F, unregisterSeedQueryLocalSource as G, createModelsFromJson as H, INIT_CONFIG_CONFLICT_MESSAGE as I, getLatestSchemaVersion as J, jsonPropertiesToSchemaProperties as K, schema as L, ClientManager$1 as M, SEEDPROTOCOL_Seed_Protocol_v1 as S, assertLocalDbChain as a, importJsonSchema as b, convertVerbatimItemStorageRow as c, getContentHash as d, claimUnpublishedDrafts as e, createLocalQueryDataSource as f, getRefValueType as g, createSchema as h, isVerbatimItemStorageDraftRow as i, easSyncActor as j, getClient as k, loadSchemaFromFile as l, getPublishedSeedRecord as m, getSchemaNameFromId as n, isSeedQueryLocalSourceRegistered as o, listCompleteSchemaFiles as p, loadAllSchemasFromDb as q, readJsonImportFile as r, syncSchemaFromSource as s, transformImportToSchemaFile as t, mergeEasSyncRequestIntents as u, readSchema as v, waitForEasReadChain as w, registerSeedQueryLocalSource as x, requestEasSyncFromAddressChange as y, requestEasSyncFromEventBus as z };
5518
+ //# sourceMappingURL=ClientManager-Dfu4jcgm.js.map