@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.
- package/dist/{BasePathResolver-DxdS0wJ0.js → BasePathResolver-DrzGVH9L.js} +232 -40
- package/dist/BasePathResolver-DrzGVH9L.js.map +1 -0
- package/dist/{ClientManager-De-vKHGe.js → ClientManager-Dfu4jcgm.js} +583 -219
- package/dist/ClientManager-Dfu4jcgm.js.map +1 -0
- package/dist/{Item-cq9VcsAY.js → Item-BUUFuywT.js} +175 -306
- package/dist/Item-BUUFuywT.js.map +1 -0
- package/dist/{ModelProperty-DuuoFXaC.js → ModelProperty-B6cnRvIz.js} +38 -39
- package/dist/ModelProperty-B6cnRvIz.js.map +1 -0
- package/dist/{Schema-Dc5EA8Yb.js → Schema-Bf-jOLq0.js} +37 -36
- package/dist/{Schema-Dc5EA8Yb.js.map → Schema-Bf-jOLq0.js.map} +1 -1
- package/dist/{SchemaValidationService-DViJ056F.js → SchemaValidationService-BNYMacsx.js} +7 -8
- package/dist/{SchemaValidationService-DViJ056F.js.map → SchemaValidationService-BNYMacsx.js.map} +1 -1
- package/dist/{backfillMetadataPropertyIds-DacVthS5.js → backfillMetadataPropertyIds-C2-s_jUa.js} +4 -5
- package/dist/{backfillMetadataPropertyIds-DacVthS5.js.map → backfillMetadataPropertyIds-C2-s_jUa.js.map} +1 -1
- package/dist/{configurePlatform-DlZ23w7e.js → configurePlatform-BzfirKuM.js} +3 -4
- package/dist/{configurePlatform-DlZ23w7e.js.map → configurePlatform-BzfirKuM.js.map} +1 -1
- package/dist/{detachRowsBeforeDelete-YRSZY6dO.js → detachRowsBeforeDelete-B0Daoz9Z.js} +3 -4
- package/dist/{detachRowsBeforeDelete-YRSZY6dO.js.map → detachRowsBeforeDelete-B0Daoz9Z.js.map} +1 -1
- package/dist/{eas-D_6ptL_F.js → eas-Dq5Kx_Nd.js} +14 -32
- package/dist/eas-Dq5Kx_Nd.js.map +1 -0
- package/dist/{errors-Cl8rbtKR.js → errors-DdWbxb_n.js} +42 -39
- package/dist/errors-DdWbxb_n.js.map +1 -0
- package/dist/{getItem-BcmJcUZB.js → getItem-9bYSErSj.js} +4 -5
- package/dist/{getItem-BcmJcUZB.js.map → getItem-9bYSErSj.js.map} +1 -1
- package/dist/{getPublishPayload-C278kK9P.js → getPublishPayload-DPy-uMrR.js} +28 -29
- package/dist/getPublishPayload-DPy-uMrR.js.map +1 -0
- package/dist/{getPublishUploads-DJ1UPh7i.js → getPublishUploads-J-143iSV.js} +10 -11
- package/dist/{getPublishUploads-DJ1UPh7i.js.map → getPublishUploads-J-143iSV.js.map} +1 -1
- package/dist/{index-DAl9U4rn.js → index-7y2f_iQz.js} +2 -2
- package/dist/index-7y2f_iQz.js.map +1 -0
- package/dist/{index-CqnU-5i0.js → index-C61Rxy0F.js} +9 -10
- package/dist/index-C61Rxy0F.js.map +1 -0
- package/dist/{index.browser-J6QZ9smP.js → index.browser-C1pisOEa.js} +10 -10
- package/dist/{index.browser-J6QZ9smP.js.map → index.browser-C1pisOEa.js.map} +1 -1
- package/dist/{index.node-C9VB9NlX.js → index.node-BAlST8pD.js} +6 -6
- package/dist/{index.node-C9VB9NlX.js.map → index.node-BAlST8pD.js.map} +1 -1
- package/dist/main.js +27 -28
- package/dist/main.js.map +1 -1
- package/dist/node.js +12 -13
- package/dist/node.js.map +1 -1
- package/dist/platform.browser.js +6 -7
- package/dist/platform.browser.js.map +1 -1
- package/dist/platform.node.js +5 -6
- package/dist/platform.node.js.map +1 -1
- package/dist/{property-Brgr5RdT.js → property-zFPMtR3T.js} +10 -14
- package/dist/property-zFPMtR3T.js.map +1 -0
- package/dist/{propertyValidationRules-CDRitiiK.js → propertyValidationRules-CeFVhdQg.js} +10 -10
- package/dist/{propertyValidationRules-CDRitiiK.js.map → propertyValidationRules-CeFVhdQg.js.map} +1 -1
- package/dist/{publishDraftGraph-C37ibxb5.js → publishDraftGraph-CuVq6iAR.js} +7 -6
- package/dist/{publishDraftGraph-C37ibxb5.js.map → publishDraftGraph-CuVq6iAR.js.map} +1 -1
- package/dist/{resetAmbiguousLegacyItemData-CcLFRREE.js → resetAmbiguousLegacyItemData-DsFTnMBz.js} +5 -6
- package/dist/resetAmbiguousLegacyItemData-DsFTnMBz.js.map +1 -0
- package/dist/{resolveModelForSyncedSeed-BTAeCOJE.js → resolveModelForSyncedSeed-RUsgAZXG.js} +2 -2
- package/dist/{resolveModelForSyncedSeed-BTAeCOJE.js.map → resolveModelForSyncedSeed-RUsgAZXG.js.map} +1 -1
- package/dist/src/Item/Item.d.ts.map +1 -1
- package/dist/src/ItemProperty/service/propertyMachine.d.ts +4 -4
- package/dist/src/Model/Model.d.ts +3 -2
- package/dist/src/Model/Model.d.ts.map +1 -1
- package/dist/src/ModelProperty/ModelProperty.d.ts +1 -1
- package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts +1 -1
- package/dist/src/Schema/service/schemaMachine.d.ts +2 -2
- package/dist/src/client/ClientManager.d.ts +6 -6
- package/dist/src/client/ClientManager.d.ts.map +1 -1
- package/dist/src/client/actors/addModelsToDb.d.ts.map +1 -1
- package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
- package/dist/src/client/clientManagerMachine.d.ts +2 -2
- package/dist/src/client/clientManagerMachine.d.ts.map +1 -1
- package/dist/src/db/canonicalizeModelTypes.d.ts +18 -0
- package/dist/src/db/canonicalizeModelTypes.d.ts.map +1 -0
- package/dist/src/db/read/getModelSchemas.d.ts +6 -0
- package/dist/src/db/read/getModelSchemas.d.ts.map +1 -1
- package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
- package/dist/src/db/read/getSchemaUidForModel.d.ts +6 -0
- package/dist/src/db/read/getSchemaUidForModel.d.ts.map +1 -1
- package/dist/src/db/read/resolveModelRecord.d.ts.map +1 -1
- package/dist/src/db/removeForeignAttestationRows.d.ts +27 -0
- package/dist/src/db/removeForeignAttestationRows.d.ts.map +1 -0
- package/dist/src/db/write/createMetadata.d.ts.map +1 -1
- package/dist/src/db/write/easSyncProcess.d.ts +9 -1
- package/dist/src/db/write/easSyncProcess.d.ts.map +1 -1
- package/dist/src/db/write/updateMetadata.d.ts.map +1 -1
- package/dist/src/eas.d.ts.map +1 -1
- package/dist/src/events/item/easSyncManager.d.ts.map +1 -1
- package/dist/src/events/item/syncDbWithEas.d.ts.map +1 -1
- package/dist/src/helpers/db.d.ts +20 -0
- package/dist/src/helpers/db.d.ts.map +1 -1
- package/dist/src/helpers/index.d.ts +3 -1
- package/dist/src/helpers/index.d.ts.map +1 -1
- package/dist/src/helpers/metadataPropertyNames.d.ts +12 -0
- package/dist/src/helpers/metadataPropertyNames.d.ts.map +1 -1
- package/dist/src/helpers/retryWhenOnline.d.ts +20 -0
- package/dist/src/helpers/retryWhenOnline.d.ts.map +1 -0
- package/dist/src/imports/json.d.ts +10 -0
- package/dist/src/imports/json.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/query/createLocalQueryDataSource.d.ts.map +1 -1
- package/dist/src/types/import.d.ts +4 -0
- package/dist/src/types/import.d.ts.map +1 -1
- package/dist/{syncDbWithEas-B-Ho0IOB.js → syncDbWithEas-CRpnkq00.js} +157 -61
- package/dist/syncDbWithEas-CRpnkq00.js.map +1 -0
- package/dist/{tabEvents-BTzckWrN.js → tabEvents-nBUq_1u1.js} +6 -6
- package/dist/{tabEvents-BTzckWrN.js.map → tabEvents-nBUq_1u1.js.map} +1 -1
- package/dist/{tolerantWebAccess-DOm2Xv-f.js → tolerantWebAccess-DIt3R8_u.js} +7 -8
- package/dist/{tolerantWebAccess-DOm2Xv-f.js.map → tolerantWebAccess-DIt3R8_u.js.map} +1 -1
- package/dist/{updateSeedRevokedAt-IHXnJI-i.js → updateSeedRevokedAt-DtTPSzpM.js} +3 -3
- package/dist/{updateSeedRevokedAt-IHXnJI-i.js.map → updateSeedRevokedAt-DtTPSzpM.js.map} +1 -1
- package/package.json +5 -5
- package/dist/BasePathResolver-DxdS0wJ0.js.map +0 -1
- package/dist/ClientManager-De-vKHGe.js.map +0 -1
- package/dist/Item-cq9VcsAY.js.map +0 -1
- package/dist/ModelProperty-DuuoFXaC.js.map +0 -1
- package/dist/eas-D_6ptL_F.js.map +0 -1
- package/dist/errors-Cl8rbtKR.js.map +0 -1
- package/dist/getPublishPayload-C278kK9P.js.map +0 -1
- package/dist/index-CqnU-5i0.js.map +0 -1
- package/dist/index-DAl9U4rn.js.map +0 -1
- package/dist/property-Brgr5RdT.js.map +0 -1
- package/dist/resetAmbiguousLegacyItemData-CcLFRREE.js.map +0 -1
- 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,
|
|
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 {
|
|
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,
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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$
|
|
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$
|
|
436
|
+
if (deferredUntilLeader$3.has(source))
|
|
299
437
|
return;
|
|
300
|
-
deferredUntilLeader$
|
|
438
|
+
deferredUntilLeader$3.add(source);
|
|
301
439
|
void whenLeaderTab().then(() => {
|
|
302
|
-
deferredUntilLeader$
|
|
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$
|
|
514
|
+
const logger$e = debug('seedSdk:services:events');
|
|
377
515
|
const handleServiceSaveState = (event) => {
|
|
378
516
|
const { state, serviceId } = event;
|
|
379
|
-
logger$
|
|
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$
|
|
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$
|
|
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-
|
|
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$
|
|
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$
|
|
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$
|
|
776
|
+
logger$c('result', result);
|
|
639
777
|
}
|
|
640
778
|
catch (error) {
|
|
641
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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 ${
|
|
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$
|
|
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
|
-
|
|
885
|
-
const
|
|
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$
|
|
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$
|
|
1057
|
+
logger$9('[client/actors] [addModelsToDb] Warning: Could not fetch schemas from EAS (background):', error.message);
|
|
912
1058
|
}
|
|
913
1059
|
else {
|
|
914
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
1814
|
+
logger$6(`STEP 3: skipping internal schema file: ${schemaFileInfo.filePath}`);
|
|
1662
1815
|
continue;
|
|
1663
1816
|
}
|
|
1664
1817
|
try {
|
|
1665
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
1887
|
+
logger$6(`Migrated schema: ${schemaName} v${schemaFile.version}`);
|
|
1735
1888
|
}
|
|
1736
1889
|
else {
|
|
1737
|
-
logger$
|
|
1890
|
+
logger$6(`Schema ${schemaName} already exists in database, skipping`);
|
|
1738
1891
|
}
|
|
1739
1892
|
}
|
|
1740
1893
|
catch (error) {
|
|
1741
|
-
logger$
|
|
1894
|
+
logger$6(`Error migrating schema file ${schemaFileInfo.filePath}:`, error);
|
|
1742
1895
|
}
|
|
1743
1896
|
}
|
|
1744
|
-
logger$
|
|
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$
|
|
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-
|
|
1876
|
-
const _ns_0 = _mod_0.
|
|
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$
|
|
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$
|
|
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$
|
|
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-
|
|
2224
|
-
const _ns_1 = _mod_1.
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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-
|
|
2309
|
-
const _ns_2 = _mod_2.
|
|
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$
|
|
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$
|
|
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$
|
|
2543
|
+
logger$5(`Restored schema file from schemaData for ${importData.name} v${version}`);
|
|
2351
2544
|
return await loadSchemaFromFile(existingFilePath);
|
|
2352
2545
|
}
|
|
2353
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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-
|
|
2488
|
-
const _ns_3 = _mod_3.
|
|
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-
|
|
2510
|
-
const _ns_4 = _mod_4.
|
|
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-
|
|
2514
|
-
const _ns_5 = _mod_5.
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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-
|
|
2713
|
-
const _ns_6 = _mod_6.
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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-
|
|
2881
|
-
const _ns_7 = _mod_7.
|
|
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-
|
|
2885
|
-
const _ns_8 = _mod_8.
|
|
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$
|
|
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$
|
|
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$
|
|
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-
|
|
2951
|
-
const _ns_9 = _mod_9.
|
|
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$
|
|
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-
|
|
2987
|
-
const _ns_10 = _mod_10.
|
|
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$
|
|
3189
|
+
logger$5(`Updated schemaData in database with actual property IDs for schema "${schemaName}"`);
|
|
2996
3190
|
}
|
|
2997
3191
|
else {
|
|
2998
|
-
logger$
|
|
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$
|
|
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$
|
|
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-
|
|
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$
|
|
3299
|
+
logger$5(`Creating schema model "${modelName}" with modelFileId: ${modelFileId}`);
|
|
3106
3300
|
}
|
|
3107
3301
|
else {
|
|
3108
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3617
|
+
logger$4('Internal seed-protocol schema already loaded');
|
|
3424
3618
|
}
|
|
3425
3619
|
else {
|
|
3426
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3669
|
+
logger$4('Listing schema files');
|
|
3476
3670
|
let schemaFiles = [];
|
|
3477
3671
|
try {
|
|
3478
3672
|
schemaFiles = await listSchemaFiles();
|
|
3479
|
-
logger$
|
|
3673
|
+
logger$4(`Found ${schemaFiles.length} schema files to process`);
|
|
3480
3674
|
}
|
|
3481
3675
|
catch (error) {
|
|
3482
|
-
logger$
|
|
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$
|
|
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$
|
|
3692
|
+
logger$4('Loading schemas from database and files');
|
|
3499
3693
|
let allSchemasData = [];
|
|
3500
3694
|
try {
|
|
3501
3695
|
allSchemasData = await loadAllSchemasFromDb();
|
|
3502
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3805
|
+
logger$4(`Loaded ${Object.keys(modelDefinitions).length} models from schema data for ${schemaName}`);
|
|
3612
3806
|
}
|
|
3613
3807
|
}
|
|
3614
3808
|
catch (error) {
|
|
3615
|
-
logger$
|
|
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$
|
|
3859
|
+
logger$4(`Loaded ${Object.keys(modelDefinitions).length} models from draft schema ${schemaName}`);
|
|
3666
3860
|
}
|
|
3667
3861
|
}
|
|
3668
3862
|
catch (error) {
|
|
3669
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
?
|
|
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,
|
|
5154
|
-
//# sourceMappingURL=ClientManager-
|
|
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
|