@seedprotocol/sdk 0.7.10 → 0.7.11-next.1396.g883ef47
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/README.md +1 -1
- package/dist/{BaseFileManager-DLou1TZS.js → BaseFileManager-D-pLXQAp.js} +2 -14
- package/dist/BaseFileManager-D-pLXQAp.js.map +1 -0
- package/dist/{Item-oPnmIehk.js → Item-BU9wIT7l.js} +2053 -412
- package/dist/Item-BU9wIT7l.js.map +1 -0
- package/dist/{ModelProperty-D2vJozmW.js → ModelProperty-Bv7KzP97.js} +40 -38
- package/dist/ModelProperty-Bv7KzP97.js.map +1 -0
- package/dist/{Schema-BMXRF4j9.js → Schema-C_4wv0RP.js} +22 -22
- package/dist/{Schema-BMXRF4j9.js.map → Schema-C_4wv0RP.js.map} +1 -1
- package/dist/{SchemaValidationService-CieGrj5c.js → SchemaValidationService-Dc-lpFQX.js} +9 -7
- package/dist/{SchemaValidationService-CieGrj5c.js.map → SchemaValidationService-Dc-lpFQX.js.map} +1 -1
- package/dist/arweaveContentTags-iW_HcWJA.js +1456 -0
- package/dist/arweaveContentTags-iW_HcWJA.js.map +1 -0
- package/dist/{configurePlatform-WkmvqeTO.js → configurePlatform-BH1YOZpg.js} +2 -2
- package/dist/{configurePlatform-WkmvqeTO.js.map → configurePlatform-BH1YOZpg.js.map} +1 -1
- package/dist/db/drizzle/0030_drop_property_uids.sql +1 -0
- package/dist/db/drizzle/meta/0030_snapshot.json +1638 -0
- package/dist/db/drizzle/meta/_journal.json +8 -1
- package/dist/{detachRowsBeforeDelete-BZr_0iZU.js → detachRowsBeforeDelete-Cie55SoG.js} +4 -6
- package/dist/detachRowsBeforeDelete-Cie55SoG.js.map +1 -0
- package/dist/dist/{BaseFileManager-DLou1TZS.d.ts → BaseFileManager-D-pLXQAp.d.ts} +432 -495
- package/dist/dist/BaseFileManager-D-pLXQAp.d.ts.map +1 -0
- package/dist/dist/{configurePlatform-WkmvqeTO.d.ts → configurePlatform-BH1YOZpg.d.ts} +1 -1
- package/dist/dist/configurePlatform-BH1YOZpg.d.ts.map +1 -0
- package/dist/dist/{index-BjWk0x4-.d.ts → index-2PaIhvOU.d.ts} +1 -64
- package/dist/dist/{index-BjWk0x4-.d.ts.map → index-2PaIhvOU.d.ts.map} +1 -1
- package/dist/dist/{index.node-BfiBriEq.d.ts → index.node-C3rI1upv.d.ts} +25 -281
- package/dist/dist/{index.node-BfiBriEq.d.ts.map → index.node-C3rI1upv.d.ts.map} +1 -1
- package/dist/dist/minimumUpgradeVersion-Dg2MguFf.d.ts +20 -0
- package/dist/dist/minimumUpgradeVersion-Dg2MguFf.d.ts.map +1 -0
- package/dist/dist/platform.node.d.ts +2 -2
- package/dist/download-DHWUPLlK.js +393 -0
- package/dist/download-DHWUPLlK.js.map +1 -0
- package/dist/{eas-FZPPqxqe.js → eas-MXEdKKVc.js} +12 -10
- package/dist/{eas-FZPPqxqe.js.map → eas-MXEdKKVc.js.map} +1 -1
- package/dist/ensureImageLocal-B6suz6FC.js +175 -0
- package/dist/ensureImageLocal-B6suz6FC.js.map +1 -0
- package/dist/{getPublishPayload-DKuEaq96.js → getPublishPayload-Dd3HCYeI.js} +8 -49
- package/dist/getPublishPayload-Dd3HCYeI.js.map +1 -0
- package/dist/{getPublishPendingDiff-CH5xLMjU.js → getPublishPendingDiff-kcTP52Kj.js} +13 -12
- package/dist/{getPublishPendingDiff-CH5xLMjU.js.map → getPublishPendingDiff-kcTP52Kj.js.map} +1 -1
- package/dist/{index-BjWk0x4-.js → index-2PaIhvOU.js} +2 -4
- package/dist/index-2PaIhvOU.js.map +1 -0
- package/dist/index-BXTyvnKb.js +28 -0
- package/dist/index-BXTyvnKb.js.map +1 -0
- package/dist/{index-DXRhFg1N.js → index-CmNqUKQN.js} +10 -8
- package/dist/index-CmNqUKQN.js.map +1 -0
- package/dist/{index-Gh42_47r.js → index-Xs_EDi-_.js} +18 -401
- package/dist/index-Xs_EDi-_.js.map +1 -0
- package/dist/{index.browser-DQstVvRV.js → index.browser-BkcOQGhw.js} +59 -102
- package/dist/index.browser-BkcOQGhw.js.map +1 -0
- package/dist/{index.node-BfiBriEq.js → index.node-C3rI1upv.js} +14 -36
- package/dist/index.node-C3rI1upv.js.map +1 -0
- package/dist/main.js +19 -19
- package/dist/{minimumUpgradeVersion-BDhHvHtr.js → minimumUpgradeVersion-Dg2MguFf.js} +38 -9
- package/dist/minimumUpgradeVersion-Dg2MguFf.js.map +1 -0
- package/dist/{modelPropertyRecordSchemas-DEe-lYhn.js → modelPropertyRecordSchemas-CEac3FB8.js} +10 -10
- package/dist/{modelPropertyRecordSchemas-DEe-lYhn.js.map → modelPropertyRecordSchemas-CEac3FB8.js.map} +1 -1
- package/dist/node.js +14 -12
- package/dist/node.js.map +1 -1
- package/dist/platform.browser.js +10 -12
- package/dist/platform.browser.js.map +1 -1
- package/dist/platform.node.js +5 -5
- package/dist/{property-iNW1N3vm.js → property-4jSqHieb.js} +9 -9
- package/dist/{property-iNW1N3vm.js.map → property-4jSqHieb.js.map} +1 -1
- package/dist/{errors-CnT0fSMK.js → publishErrors-DZXDX3B6.js} +1830 -245
- package/dist/publishErrors-DZXDX3B6.js.map +1 -0
- package/dist/{readHtmlBodyForStorageSeed-B7HgPmq6.js → readHtmlBodyForStorageSeed-Cva1DPzE.js} +10 -12
- package/dist/{readHtmlBodyForStorageSeed-B7HgPmq6.js.map → readHtmlBodyForStorageSeed-Cva1DPzE.js.map} +1 -1
- package/dist/seedSchema/index.ts +0 -1
- package/dist/src/Item/Item.d.ts.map +1 -1
- package/dist/src/ItemProperty/ItemProperty.d.ts +1 -0
- package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
- package/dist/src/ItemProperty/service/actors/initialize.d.ts.map +1 -1
- package/dist/src/Model/index.d.ts +0 -2
- package/dist/src/Model/index.d.ts.map +1 -1
- package/dist/src/browser/db/Db.d.ts.map +1 -1
- package/dist/src/browser/db/drizzleFiles.d.ts +3 -2
- package/dist/src/browser/db/drizzleFiles.d.ts.map +1 -1
- package/dist/src/browser/helpers/FileManager.d.ts +1 -3
- package/dist/src/browser/helpers/FileManager.d.ts.map +1 -1
- package/dist/src/browser/workers/FileDownloader.d.ts +1 -0
- package/dist/src/browser/workers/FileDownloader.d.ts.map +1 -1
- package/dist/src/browser/workers/ImageResizer.d.ts +1 -0
- package/dist/src/browser/workers/ImageResizer.d.ts.map +1 -1
- package/dist/src/client/actors/addModelsToDb.d.ts.map +1 -1
- package/dist/src/client/actors/saveAppState.d.ts.map +1 -1
- package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
- package/dist/src/db/minimumUpgradeVersion.d.ts +20 -5
- package/dist/src/db/minimumUpgradeVersion.d.ts.map +1 -1
- package/dist/src/db/read/metadataProperty.d.ts +1 -1
- package/dist/src/db/read/metadataProperty.d.ts.map +1 -1
- package/dist/src/db/read/resolveModelRecord.d.ts +0 -20
- package/dist/src/db/read/resolveModelRecord.d.ts.map +1 -1
- package/dist/src/db/write/createMetadata.d.ts +1 -8
- package/dist/src/db/write/createMetadata.d.ts.map +1 -1
- package/dist/src/db/write/removeLocalCopiesForAddresses.d.ts +0 -5
- package/dist/src/db/write/removeLocalCopiesForAddresses.d.ts.map +1 -1
- package/dist/src/db/write/updateItemPropertyValue.d.ts.map +1 -1
- package/dist/src/events/files/download.d.ts.map +1 -1
- package/dist/src/events/item/syncDbWithEas.d.ts.map +1 -1
- package/dist/src/helpers/easPropertyCanonical.d.ts +1 -1
- package/dist/src/helpers/easPropertyCanonical.d.ts.map +1 -1
- package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts +2 -3
- package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts.map +1 -1
- package/dist/src/helpers/entity/entityLifecycle.d.ts +0 -2
- package/dist/src/helpers/entity/entityLifecycle.d.ts.map +1 -1
- package/dist/src/helpers/environment.d.ts +0 -13
- package/dist/src/helpers/environment.d.ts.map +1 -1
- package/dist/src/helpers/index.d.ts +0 -4
- package/dist/src/helpers/index.d.ts.map +1 -1
- package/dist/src/helpers/itemStorageFile.d.ts +0 -11
- package/dist/src/helpers/itemStorageFile.d.ts.map +1 -1
- package/dist/src/helpers/propertySchemaUid.d.ts +7 -5
- package/dist/src/helpers/propertySchemaUid.d.ts.map +1 -1
- package/dist/src/helpers/storageDir.d.ts +0 -2
- package/dist/src/helpers/storageDir.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/node/db/Db.d.ts +0 -2
- package/dist/src/node/db/Db.d.ts.map +1 -1
- package/dist/src/node/helpers/FileManager.d.ts +0 -2
- package/dist/src/node/helpers/FileManager.d.ts.map +1 -1
- package/dist/src/node/index.d.ts +11 -3
- package/dist/src/node/index.d.ts.map +1 -1
- package/dist/src/outbox/index.d.ts +5 -5
- package/dist/src/outbox/index.d.ts.map +1 -1
- package/dist/src/seedSchema/index.d.ts +0 -1
- package/dist/src/seedSchema/index.d.ts.map +1 -1
- package/dist/{syncDbWithEas-FuyqoEKk.js → syncDbWithEas-V_9gIyHp.js} +25 -23
- package/dist/syncDbWithEas-V_9gIyHp.js.map +1 -0
- package/dist/{tolerantWebAccess-zEseJ2zT.js → tolerantWebAccess-D6hFxREe.js} +11 -13
- package/dist/{tolerantWebAccess-zEseJ2zT.js.map → tolerantWebAccess-D6hFxREe.js.map} +1 -1
- package/dist/{updateSeedRevokedAt-B9JRwr5T.js → updateSeedRevokedAt-CsAoUEEt.js} +4 -3
- package/dist/{updateSeedRevokedAt-B9JRwr5T.js.map → updateSeedRevokedAt-CsAoUEEt.js.map} +1 -1
- package/package.json +7 -12
- package/dist/BaseFileManager-DLou1TZS.js.map +0 -1
- package/dist/Item-oPnmIehk.js.map +0 -1
- package/dist/ModelProperty-D2vJozmW.js.map +0 -1
- package/dist/detachRowsBeforeDelete-BZr_0iZU.js.map +0 -1
- package/dist/dist/BaseFileManager-DLou1TZS.d.ts.map +0 -1
- package/dist/dist/configurePlatform-WkmvqeTO.d.ts.map +0 -1
- package/dist/dist/minimumUpgradeVersion-BDhHvHtr.d.ts +0 -9
- package/dist/dist/minimumUpgradeVersion-BDhHvHtr.d.ts.map +0 -1
- package/dist/errors-CnT0fSMK.js.map +0 -1
- package/dist/getPublishPayload-DKuEaq96.js.map +0 -1
- package/dist/getPublishUploads-DivYpgNw.js +0 -783
- package/dist/getPublishUploads-DivYpgNw.js.map +0 -1
- package/dist/index-BjWk0x4-.js.map +0 -1
- package/dist/index-CZMWKXLM.js +0 -27
- package/dist/index-CZMWKXLM.js.map +0 -1
- package/dist/index-DXRhFg1N.js.map +0 -1
- package/dist/index-Gh42_47r.js.map +0 -1
- package/dist/index.browser-DQstVvRV.js.map +0 -1
- package/dist/index.node-BfiBriEq.js.map +0 -1
- package/dist/minimumUpgradeVersion-BDhHvHtr.js.map +0 -1
- package/dist/publishGraph-CRn_N3NT.js +0 -311
- package/dist/publishGraph-CRn_N3NT.js.map +0 -1
- package/dist/seedSchema/PropertyUidSchema.ts +0 -16
- package/dist/src/db/read/getItemProperty.d.ts +0 -6
- package/dist/src/db/read/getItemProperty.d.ts.map +0 -1
- package/dist/src/db/read/isSeedRevoked.d.ts +0 -7
- package/dist/src/db/read/isSeedRevoked.d.ts.map +0 -1
- package/dist/src/db/write/saveMetadata.d.ts +0 -3
- package/dist/src/db/write/saveMetadata.d.ts.map +0 -1
- package/dist/src/db/write/updateMetadata.d.ts +0 -13
- package/dist/src/db/write/updateMetadata.d.ts.map +0 -1
- package/dist/src/seedSchema/PropertyUidSchema.d.ts +0 -64
- package/dist/src/seedSchema/PropertyUidSchema.d.ts.map +0 -1
- package/dist/src/types/fileManager.d.ts +0 -13
- package/dist/src/types/fileManager.d.ts.map +0 -1
- package/dist/syncDbWithEas-FuyqoEKk.js.map +0 -1
- package/dist/tabEvents-DT5ez6io.js +0 -3830
- package/dist/tabEvents-DT5ez6io.js.map +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { fromCallback, setup, assign, and as and$1, stateIn, createActor,
|
|
1
|
+
import { Z as ZERO_BYTES32, ad as PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP, n as normalizeDataType, m as INTERNAL_DATA_TYPES, M as ModelPropertyDataTypes, g as normalizePropertyRecordSchema, k as generateId, ae as TAB_ID, a3 as onTabMessage, O as whenLeaderTab, H as isLeaderTab, a2 as postTabMessage, r as INTERNAL_PROPERTY_NAMES, u as convertTxIdToImage, i as isDataType, aa as getExtensionForMimeType, af as normalizeMimeType, U as sniffMimeType, I as ImageSize, h as eventEmitter, ag as timingMarks, a6 as tryWithTabLock, ah as withTabLock, X as createSessionObjectUrl, ab as INTERNAL_STORAGE_MODEL_FILE_IDS, ai as revokeSessionObjectUrl, aj as timed, N as watchEntityRows, ak as isSessionObjectUrl, al as timingNow, am as timingSince, an as timingLogger, ao as timedDb } from './arweaveContentTags-iW_HcWJA.js';
|
|
2
|
+
import { waitFor, fromCallback, setup, assign, and as and$1, stateIn, createActor, isMachineSnapshot } from 'xstate';
|
|
3
3
|
import debug from 'debug';
|
|
4
4
|
import pluralize from 'pluralize';
|
|
5
|
-
import { easName, isValidEasAttestationUid, isPlaceholderUid,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { DEFAULT_NAME_MATCH_LEVEL, isNameMatchLevel, compareNameMatchTiers, localNamesInBestMatchTier, easName, isValidEasAttestationUid, isPlaceholderUid, getEasSchemaUidForExactDefinition, getSchemaUidForModelFromCache, setSchemaUidForModel, cachedSchemaLookup, getEasModelSchemasForNames } from '@seedprotocol/eas';
|
|
6
|
+
import { eq, getTableColumns, and, inArray, isNotNull, desc, sql, or, isNull, asc, lt, max, count, gt, ne } from 'drizzle-orm';
|
|
7
|
+
import { B as BaseDb, q as schemas, d as models, j as modelSchemas, v as versions, s as seeds, l as properties, p as pendingWrites, a as appState, m as metadata, t as isSqliteBusyError, r as BaseFileManager, b as modelUids, o as publishProcesses, e as arweaveL1FinalizeJobs, h as htmlEmbeddedImageCoPublish } from './BaseFileManager-D-pLXQAp.js';
|
|
8
8
|
import { BehaviorSubject } from 'rxjs';
|
|
9
9
|
import { orderBy } from 'lodash-es';
|
|
10
|
+
import { alias } from 'drizzle-orm/sqlite-core';
|
|
10
11
|
import { normalizeRelationPropertyValue, resolveSeedIdsFromRefString, BaseArweaveClient } from '@seedprotocol/arweave';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -66,6 +67,663 @@ function differsFromOriginals(originals, next, event, ignore = []) {
|
|
|
66
67
|
});
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
var MachineIds;
|
|
71
|
+
(function (MachineIds) {
|
|
72
|
+
MachineIds["CLIENT_MANAGER"] = "@seedSdk/clientManager";
|
|
73
|
+
MachineIds["EAS_SYNC_MANAGER"] = "@seedSdk/easSyncManager";
|
|
74
|
+
MachineIds["GLOBAL"] = "@seedSdk/global";
|
|
75
|
+
MachineIds["INTERNAL"] = "@seedSdk/internal";
|
|
76
|
+
MachineIds["EAS"] = "@seedSdk/eas";
|
|
77
|
+
MachineIds["ITEM"] = "@seedSdk/item";
|
|
78
|
+
MachineIds["ALL_ITEMS"] = "@seedSdk/allItems";
|
|
79
|
+
MachineIds["MODEL"] = "@seedSdk/model";
|
|
80
|
+
MachineIds["FILE_SYSTEM"] = "@seedSdk/fileSystem";
|
|
81
|
+
})(MachineIds || (MachineIds = {}));
|
|
82
|
+
var ClientManagerState;
|
|
83
|
+
(function (ClientManagerState) {
|
|
84
|
+
ClientManagerState["UNINITIALIZED"] = "uninitialized";
|
|
85
|
+
ClientManagerState["PLATFORM_CLASSES_INIT"] = "platformClassesInit";
|
|
86
|
+
ClientManagerState["FILE_SYSTEM_INIT"] = "fileSystemInit";
|
|
87
|
+
ClientManagerState["DB_INIT"] = "dbInit";
|
|
88
|
+
ClientManagerState["SAVE_CONFIG"] = "saveConfig";
|
|
89
|
+
ClientManagerState["ADD_MODELS_TO_DB"] = "addModelsToDb";
|
|
90
|
+
ClientManagerState["PROCESS_SCHEMA_FILES"] = "processSchemaFiles";
|
|
91
|
+
ClientManagerState["IDLE"] = "idle";
|
|
92
|
+
ClientManagerState["INIT_FAILED"] = "initFailed";
|
|
93
|
+
})(ClientManagerState || (ClientManagerState = {}));
|
|
94
|
+
var ClientManagerEvents;
|
|
95
|
+
(function (ClientManagerEvents) {
|
|
96
|
+
ClientManagerEvents["UPDATE_CONTEXT"] = "updateContext";
|
|
97
|
+
ClientManagerEvents["SAVE_APP_STATE_SUCCESS"] = "saveAppStateSuccess";
|
|
98
|
+
ClientManagerEvents["SAVE_APP_STATE_ERROR"] = "saveAppStateError";
|
|
99
|
+
ClientManagerEvents["SET_ADDRESSES"] = "setAddresses";
|
|
100
|
+
})(ClientManagerEvents || (ClientManagerEvents = {}));
|
|
101
|
+
|
|
102
|
+
// Set by ClientManager when it loads. Importing it here would pull in most of the SDK, and
|
|
103
|
+
// readiness is read from inside that graph (entity loads, downloads).
|
|
104
|
+
let getClientActor;
|
|
105
|
+
/** @internal ClientManager registers the getter for its current actor (init after stop replaces it). */
|
|
106
|
+
function registerClientActor(getter) {
|
|
107
|
+
getClientActor = getter;
|
|
108
|
+
}
|
|
109
|
+
// Client states in which the app database is open: dbInit finishes before saveConfig starts.
|
|
110
|
+
const DB_OPEN_STATES = new Set([
|
|
111
|
+
ClientManagerState.SAVE_CONFIG,
|
|
112
|
+
ClientManagerState.PROCESS_SCHEMA_FILES,
|
|
113
|
+
ClientManagerState.ADD_MODELS_TO_DB,
|
|
114
|
+
ClientManagerState.IDLE,
|
|
115
|
+
]);
|
|
116
|
+
/** Whether a clientManager snapshot is a running client that finished init. */
|
|
117
|
+
function isClientReadySnapshot(snapshot) {
|
|
118
|
+
return snapshot.status === 'active' && snapshot.value === ClientManagerState.IDLE;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Whether `client.init` finished and the app database is open. False before init, while it runs,
|
|
122
|
+
* and after `client.stop()`.
|
|
123
|
+
*/
|
|
124
|
+
function isClientReady() {
|
|
125
|
+
const actor = getClientActor?.();
|
|
126
|
+
return !!actor && isClientReadySnapshot(actor.getSnapshot()) && BaseDb.isAppDbReady();
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The app database, once client init has opened it. Waits up to `timeoutMs` for init to get that
|
|
130
|
+
* far; returns undefined when it doesn't (no init, a failed or stopped client).
|
|
131
|
+
*/
|
|
132
|
+
async function whenDbReady(timeoutMs = 30000) {
|
|
133
|
+
if (BaseDb.isAppDbReady())
|
|
134
|
+
return BaseDb.getAppDb();
|
|
135
|
+
const actor = getClientActor?.();
|
|
136
|
+
if (!actor)
|
|
137
|
+
return undefined;
|
|
138
|
+
try {
|
|
139
|
+
await waitFor(actor, (snapshot) => DB_OPEN_STATES.has(snapshot.value), {
|
|
140
|
+
timeout: timeoutMs,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
return BaseDb.isAppDbReady() ? BaseDb.getAppDb() : undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const refreshers = new Set();
|
|
150
|
+
function addCachedPropertyRefresher(fn) {
|
|
151
|
+
refreshers.add(fn);
|
|
152
|
+
}
|
|
153
|
+
/** Give cached instances of these properties their rows' current definitions. */
|
|
154
|
+
async function refreshCachedProperties(propertyFileIds) {
|
|
155
|
+
if (propertyFileIds.length === 0)
|
|
156
|
+
return;
|
|
157
|
+
for (const refresher of refreshers)
|
|
158
|
+
await refresher(propertyFileIds);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const level = (value) => (isNameMatchLevel(value) ? value : undefined);
|
|
162
|
+
const tighter = (a, b) => a === undefined || compareNameMatchTiers(b, a) < 0 ? b : a;
|
|
163
|
+
/** The levels in one schema file for one of its models, or undefined when the file lacks the model. */
|
|
164
|
+
const modelLevelsFromFile = (file, modelName) => {
|
|
165
|
+
const modelDef = file.models?.[modelName];
|
|
166
|
+
if (!modelDef)
|
|
167
|
+
return undefined;
|
|
168
|
+
const model = level(modelDef.nameMatch) ?? level(file.nameMatch) ?? DEFAULT_NAME_MATCH_LEVEL;
|
|
169
|
+
const properties = new Map();
|
|
170
|
+
for (const [propertyName, def] of Object.entries(modelDef.properties ?? {})) {
|
|
171
|
+
properties.set(propertyName, level(def?.nameMatch) ?? model);
|
|
172
|
+
}
|
|
173
|
+
return { model, properties };
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Builds NameMatchLevels from the schema files that own each model row. Exported for tests;
|
|
177
|
+
* use {@link nameMatchLevels}.
|
|
178
|
+
*/
|
|
179
|
+
const nameMatchLevelsFromRows = (rows) => {
|
|
180
|
+
const byFileId = new Map();
|
|
181
|
+
const byName = new Map();
|
|
182
|
+
const parsed = new Map();
|
|
183
|
+
for (const row of rows) {
|
|
184
|
+
if (!row.schemaData)
|
|
185
|
+
continue;
|
|
186
|
+
if (!parsed.has(row.schemaData)) {
|
|
187
|
+
try {
|
|
188
|
+
parsed.set(row.schemaData, JSON.parse(row.schemaData));
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
parsed.set(row.schemaData, undefined);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const file = parsed.get(row.schemaData);
|
|
195
|
+
const levels = file && modelLevelsFromFile(file, row.modelName);
|
|
196
|
+
if (!levels)
|
|
197
|
+
continue;
|
|
198
|
+
if (row.modelFileId)
|
|
199
|
+
byFileId.set(row.modelFileId, levels);
|
|
200
|
+
const seen = byName.get(row.modelName);
|
|
201
|
+
if (!seen) {
|
|
202
|
+
byName.set(row.modelName, { model: levels.model, properties: new Map(levels.properties) });
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
seen.model = tighter(seen.model, levels.model);
|
|
206
|
+
for (const [name, value] of levels.properties)
|
|
207
|
+
seen.properties.set(name, tighter(seen.properties.get(name), value));
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
model: (modelName) => byName.get(modelName)?.model ?? DEFAULT_NAME_MATCH_LEVEL,
|
|
211
|
+
property: (modelName, propertyName, modelFileId) => {
|
|
212
|
+
const levels = (modelFileId ? byFileId.get(modelFileId) : undefined) ?? byName.get(modelName);
|
|
213
|
+
return levels?.properties.get(propertyName) ?? levels?.model ?? DEFAULT_NAME_MATCH_LEVEL;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
const DEFAULT_LEVELS = {
|
|
218
|
+
model: () => DEFAULT_NAME_MATCH_LEVEL,
|
|
219
|
+
property: () => DEFAULT_NAME_MATCH_LEVEL,
|
|
220
|
+
};
|
|
221
|
+
let current;
|
|
222
|
+
let loading;
|
|
223
|
+
/** Reads the levels from the schema files stored with each model's schema row (schemas.schema_data). */
|
|
224
|
+
const loadNameMatchLevels = async (db = BaseDb.getAppDb()) => {
|
|
225
|
+
if (!db)
|
|
226
|
+
return DEFAULT_LEVELS;
|
|
227
|
+
const load = (async () => {
|
|
228
|
+
const rows = (await db
|
|
229
|
+
.select({ modelName: models.name, modelFileId: models.schemaFileId, schemaData: schemas.schemaData })
|
|
230
|
+
.from(models)
|
|
231
|
+
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
232
|
+
.innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id)));
|
|
233
|
+
return nameMatchLevelsFromRows(rows);
|
|
234
|
+
})();
|
|
235
|
+
loading = load;
|
|
236
|
+
try {
|
|
237
|
+
const levels = await load;
|
|
238
|
+
if (loading === load) {
|
|
239
|
+
current = levels;
|
|
240
|
+
previous = undefined;
|
|
241
|
+
}
|
|
242
|
+
return levels;
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
if (loading === load)
|
|
246
|
+
loading = undefined;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
/** The levels, loaded once and reused until a schema write clears them. */
|
|
250
|
+
const nameMatchLevels = async (db = BaseDb.getAppDb()) => current ?? loading ?? loadNameMatchLevels(db);
|
|
251
|
+
/**
|
|
252
|
+
* The last loaded levels, for synchronous callers (Model.findByModelType). Init loads them before
|
|
253
|
+
* it reports ready. After a schema write clears them, this returns the levels from before the
|
|
254
|
+
* write until the reload that clear starts finishes.
|
|
255
|
+
*/
|
|
256
|
+
const currentNameMatchLevels = () => current ?? previous ?? DEFAULT_LEVELS;
|
|
257
|
+
let previous;
|
|
258
|
+
let reloadScheduled = false;
|
|
259
|
+
/**
|
|
260
|
+
* Called when a schema row or its models are written. The next {@link nameMatchLevels} read
|
|
261
|
+
* reloads, and a reload starts on the next tick (one per burst of writes) for synchronous callers.
|
|
262
|
+
*/
|
|
263
|
+
const clearNameMatchLevels = () => {
|
|
264
|
+
if (current)
|
|
265
|
+
previous = current;
|
|
266
|
+
current = undefined;
|
|
267
|
+
loading = undefined;
|
|
268
|
+
if (reloadScheduled)
|
|
269
|
+
return;
|
|
270
|
+
reloadScheduled = true;
|
|
271
|
+
setTimeout(() => {
|
|
272
|
+
reloadScheduled = false;
|
|
273
|
+
if (current || loading || !BaseDb.isAppDbReady())
|
|
274
|
+
return;
|
|
275
|
+
loadNameMatchLevels().catch(() => {
|
|
276
|
+
// The next nameMatchLevels() read retries.
|
|
277
|
+
});
|
|
278
|
+
}, 0);
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* `nameMatch` values in a schema file that aren't a level. They are ignored (the next level up
|
|
282
|
+
* applies); schema loads warn about them.
|
|
283
|
+
*/
|
|
284
|
+
const invalidNameMatchValues = (file) => {
|
|
285
|
+
const problems = [];
|
|
286
|
+
const check = (where, value) => {
|
|
287
|
+
if (value !== undefined && !isNameMatchLevel(value)) {
|
|
288
|
+
problems.push(`${where} nameMatch ${JSON.stringify(value)} is not 'exact', 'normalized' or 'fuzzy'`);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
check('schema', file.nameMatch);
|
|
292
|
+
for (const [modelName, modelDef] of Object.entries(file.models ?? {})) {
|
|
293
|
+
check(modelName, modelDef?.nameMatch);
|
|
294
|
+
for (const [propertyName, def] of Object.entries(modelDef?.properties ?? {})) {
|
|
295
|
+
check(`${modelName}.${propertyName}`, def?.nameMatch);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return problems;
|
|
299
|
+
};
|
|
300
|
+
/** Warns about {@link invalidNameMatchValues}. */
|
|
301
|
+
const warnOnInvalidNameMatch = (file) => {
|
|
302
|
+
const problems = invalidNameMatchValues(file);
|
|
303
|
+
for (const problem of problems) {
|
|
304
|
+
console.warn(`[seed] schema ${file.metadata?.name ?? ''}: ${problem}; it is ignored.`);
|
|
305
|
+
}
|
|
306
|
+
return problems;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* SQLite builds before 3.32 (and builds that keep the old default) allow at most 999 bound
|
|
311
|
+
* parameters per statement (SQLITE_MAX_VARIABLE_NUMBER). sqlite-wasm and libsql allow 32766, but a
|
|
312
|
+
* statement that binds one parameter per synced item still has to stay under 999 to work everywhere.
|
|
313
|
+
*/
|
|
314
|
+
const SQLITE_DEFAULT_MAX_VARIABLE_NUMBER = 999;
|
|
315
|
+
/**
|
|
316
|
+
* Values per `IN (...)` list. Leaves room under {@link SQLITE_DEFAULT_MAX_VARIABLE_NUMBER} for the
|
|
317
|
+
* statement's other parameters (a second list, `SET` values, other conditions).
|
|
318
|
+
*/
|
|
319
|
+
const IN_LIST_BATCH = 400;
|
|
320
|
+
/**
|
|
321
|
+
* Rows per multi-row INSERT into `table` so the statement binds at most
|
|
322
|
+
* {@link SQLITE_DEFAULT_MAX_VARIABLE_NUMBER} parameters even with every column set.
|
|
323
|
+
*/
|
|
324
|
+
const rowsPerInsert = (table) => Math.max(1, Math.floor(SQLITE_DEFAULT_MAX_VARIABLE_NUMBER / Object.keys(getTableColumns(table)).length));
|
|
325
|
+
/** Splits `values` into chunks of at most `size` (one empty list gives no chunks). */
|
|
326
|
+
const chunkValues = (values, size = IN_LIST_BATCH) => {
|
|
327
|
+
if (!Number.isInteger(size) || size < 1) {
|
|
328
|
+
throw new Error(`chunkValues: size must be a positive integer, got ${size}`);
|
|
329
|
+
}
|
|
330
|
+
const chunks = [];
|
|
331
|
+
for (let i = 0; i < values.length; i += size) {
|
|
332
|
+
chunks.push(values.slice(i, i + size));
|
|
333
|
+
}
|
|
334
|
+
return chunks;
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Runs `query` once per chunk of `values` (see {@link chunkValues}), one after another, and
|
|
338
|
+
* concatenates the rows. Use it for `inArray(column, values)` reads whose list can be large.
|
|
339
|
+
*/
|
|
340
|
+
const selectInBatches = async (values, query, size = IN_LIST_BATCH) => {
|
|
341
|
+
const rows = [];
|
|
342
|
+
for (const chunk of chunkValues(values, size)) {
|
|
343
|
+
rows.push(...(await query(chunk)));
|
|
344
|
+
}
|
|
345
|
+
return rows;
|
|
346
|
+
};
|
|
347
|
+
/** Runs `write` once per chunk of `values`, one after another (deletes / updates with an `IN` list). */
|
|
348
|
+
const writeInBatches = async (values, write, size = IN_LIST_BATCH) => {
|
|
349
|
+
for (const chunk of chunkValues(values, size)) {
|
|
350
|
+
await write(chunk);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* The local seed row for each seed attestation uid. A uid recorded on a seed's versions
|
|
356
|
+
* (`versions.seed_uid`) belongs to that seed: its current uid, or an old one from before it was
|
|
357
|
+
* unpublished and published again (the seed row then holds the new uid, the old version rows keep
|
|
358
|
+
* the old one). A uid no version records resolves by `seeds.uid`. Uids with no local seed are left
|
|
359
|
+
* out of the map.
|
|
360
|
+
*
|
|
361
|
+
* Going through versions first also resolves a republished seed's old uid to the item when an
|
|
362
|
+
* older sync left a second, version-less seed row with that uid (migration 0024 removes those).
|
|
363
|
+
*/
|
|
364
|
+
const findSeedRowsByUid = async (uids,
|
|
365
|
+
/** The database or transaction to read; the app database by default. */
|
|
366
|
+
db = BaseDb.getAppDb()) => {
|
|
367
|
+
const result = new Map();
|
|
368
|
+
const appDb = db;
|
|
369
|
+
const wanted = [...new Set(uids.filter(Boolean))];
|
|
370
|
+
if (!appDb || wanted.length === 0)
|
|
371
|
+
return result;
|
|
372
|
+
const owners = await selectInBatches(wanted, (chunk) => appDb
|
|
373
|
+
.selectDistinct({ seedUid: versions.seedUid, seedLocalId: versions.seedLocalId })
|
|
374
|
+
.from(versions)
|
|
375
|
+
.where(and(inArray(versions.seedUid, chunk), isNotNull(versions.seedLocalId))));
|
|
376
|
+
const ownerLocalIds = [...new Set(owners.map((o) => o.seedLocalId))];
|
|
377
|
+
const ownerRows = await selectInBatches(ownerLocalIds, (chunk) => appDb.select().from(seeds).where(inArray(seeds.localId, chunk)));
|
|
378
|
+
const ownerByLocalId = new Map(ownerRows.map((row) => [row.localId, row]));
|
|
379
|
+
for (const { seedUid, seedLocalId } of owners) {
|
|
380
|
+
const row = ownerByLocalId.get(seedLocalId);
|
|
381
|
+
if (!seedUid || !row)
|
|
382
|
+
continue;
|
|
383
|
+
// Versions of two seeds recording one uid shouldn't happen; the seed that holds it wins.
|
|
384
|
+
const current = result.get(seedUid);
|
|
385
|
+
if (!current || (current.uid !== seedUid && row.uid === seedUid))
|
|
386
|
+
result.set(seedUid, row);
|
|
387
|
+
}
|
|
388
|
+
const rest = wanted.filter((uid) => !result.has(uid));
|
|
389
|
+
const byUid = await selectInBatches(rest, (chunk) => appDb.select().from(seeds).where(inArray(seeds.uid, chunk)));
|
|
390
|
+
for (const row of byUid) {
|
|
391
|
+
if (row.uid && !result.has(row.uid))
|
|
392
|
+
result.set(row.uid, row);
|
|
393
|
+
}
|
|
394
|
+
return result;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The local seed row for an item known by local id, seed uid or both. The local id wins: it names
|
|
399
|
+
* one row, while a uid can be an old one the seed held before it was unpublished and published
|
|
400
|
+
* again. A uid (when the local id is absent or matches nothing) resolves as
|
|
401
|
+
* {@link findSeedRowsByUid} resolves it: the seed whose versions record it (`versions.seed_uid`),
|
|
402
|
+
* else the row with that `seeds.uid`. The zero uid matches nothing.
|
|
403
|
+
*/
|
|
404
|
+
const findSeedRow = async ({ localId, uid },
|
|
405
|
+
/** The database or transaction to read; the app database by default. */
|
|
406
|
+
db = BaseDb.getAppDb()) => {
|
|
407
|
+
if (!db)
|
|
408
|
+
return undefined;
|
|
409
|
+
if (localId) {
|
|
410
|
+
const [row] = await db.select().from(seeds).where(eq(seeds.localId, localId)).limit(1);
|
|
411
|
+
if (row?.localId)
|
|
412
|
+
return row;
|
|
413
|
+
}
|
|
414
|
+
if (!uid || uid === ZERO_BYTES32)
|
|
415
|
+
return undefined;
|
|
416
|
+
const row = (await findSeedRowsByUid([uid], db)).get(uid);
|
|
417
|
+
return row?.localId ? row : undefined;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
var findSeedRow$1 = /*#__PURE__*/Object.freeze({
|
|
421
|
+
__proto__: null,
|
|
422
|
+
findSeedRow: findSeedRow
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Thrown when a model name exists in more than one schema and nothing says which one is meant.
|
|
427
|
+
* Model names are only unique within a schema; pass `schemaName` (or `modelFileId`) to choose.
|
|
428
|
+
*/
|
|
429
|
+
class AmbiguousModelError extends Error {
|
|
430
|
+
constructor(modelName, schemaNames) {
|
|
431
|
+
const names = [...new Set(schemaNames)].sort();
|
|
432
|
+
super(`Model "${modelName}" exists in schemas ${names.map((n) => `"${n}"`).join(', ')}; ` +
|
|
433
|
+
`pass schemaName (or modelFileId) to choose one.`);
|
|
434
|
+
this.name = 'AmbiguousModelError';
|
|
435
|
+
this.modelName = modelName;
|
|
436
|
+
this.schemaNames = names;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const columns = {
|
|
441
|
+
id: models.id,
|
|
442
|
+
name: models.name,
|
|
443
|
+
schemaFileId: models.schemaFileId,
|
|
444
|
+
};
|
|
445
|
+
/**
|
|
446
|
+
* Resolve a models row from a model name plus whatever scope is known, most specific first:
|
|
447
|
+
* modelFileId, modelId, propertyFileId, then schemaId/schemaName + name, then the name alone (not
|
|
448
|
+
* with `strict` and a schema scope). The name alone is only accepted when it is unambiguous;
|
|
449
|
+
* otherwise this throws AmbiguousModelError.
|
|
450
|
+
*/
|
|
451
|
+
const resolveModelRecord = async (modelName, scope = {}, db = BaseDb.getAppDb()) => {
|
|
452
|
+
if (!db)
|
|
453
|
+
return undefined;
|
|
454
|
+
if (scope.modelFileId) {
|
|
455
|
+
const rows = await db
|
|
456
|
+
.select(columns)
|
|
457
|
+
.from(models)
|
|
458
|
+
.where(eq(models.schemaFileId, scope.modelFileId))
|
|
459
|
+
.limit(1);
|
|
460
|
+
if (rows.length > 0)
|
|
461
|
+
return rows[0];
|
|
462
|
+
}
|
|
463
|
+
if (scope.modelId) {
|
|
464
|
+
const rows = await db.select(columns).from(models).where(eq(models.id, scope.modelId)).limit(1);
|
|
465
|
+
if (rows.length > 0)
|
|
466
|
+
return rows[0];
|
|
467
|
+
}
|
|
468
|
+
if (scope.propertyFileId) {
|
|
469
|
+
const rows = await db
|
|
470
|
+
.select(columns)
|
|
471
|
+
.from(properties)
|
|
472
|
+
.innerJoin(models, eq(properties.modelId, models.id))
|
|
473
|
+
.where(eq(properties.schemaFileId, scope.propertyFileId))
|
|
474
|
+
.limit(1);
|
|
475
|
+
if (rows.length > 0)
|
|
476
|
+
return rows[0];
|
|
477
|
+
}
|
|
478
|
+
if (!modelName)
|
|
479
|
+
return undefined;
|
|
480
|
+
if (scope.schemaId || scope.schemaName) {
|
|
481
|
+
const rows = await db
|
|
482
|
+
.select(columns)
|
|
483
|
+
.from(models)
|
|
484
|
+
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
485
|
+
.innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
|
|
486
|
+
.where(and(eq(models.name, modelName), scope.schemaId ? eq(schemas.id, scope.schemaId) : eq(schemas.name, scope.schemaName)))
|
|
487
|
+
.limit(1);
|
|
488
|
+
if (rows.length > 0)
|
|
489
|
+
return rows[0];
|
|
490
|
+
if (scope.strict)
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
return resolveModelRecordByNameOnly(modelName, db);
|
|
494
|
+
};
|
|
495
|
+
/**
|
|
496
|
+
* Name of the schema a models row is linked to (the newest version's when several are), or
|
|
497
|
+
* undefined for an unlinked stub.
|
|
498
|
+
*/
|
|
499
|
+
const getModelSchemaName = async (modelId, db = BaseDb.getAppDb()) => {
|
|
500
|
+
if (!db)
|
|
501
|
+
return undefined;
|
|
502
|
+
const rows = await db
|
|
503
|
+
.select({ schemaName: schemas.name })
|
|
504
|
+
.from(modelSchemas)
|
|
505
|
+
.innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
|
|
506
|
+
.where(eq(modelSchemas.modelId, modelId))
|
|
507
|
+
.orderBy(desc(schemas.version))
|
|
508
|
+
.limit(1);
|
|
509
|
+
return rows[0]?.schemaName ?? undefined;
|
|
510
|
+
};
|
|
511
|
+
/** The model a property row (by its schemaFileId) belongs to, and the property's data type. */
|
|
512
|
+
const resolvePropertyModelRecord = async (propertyFileId, db = BaseDb.getAppDb()) => {
|
|
513
|
+
if (!db || !propertyFileId)
|
|
514
|
+
return undefined;
|
|
515
|
+
const rows = await db
|
|
516
|
+
.select({ ...columns, dataType: properties.dataType })
|
|
517
|
+
.from(properties)
|
|
518
|
+
.innerJoin(models, eq(properties.modelId, models.id))
|
|
519
|
+
.where(eq(properties.schemaFileId, propertyFileId))
|
|
520
|
+
.limit(1);
|
|
521
|
+
const row = rows[0];
|
|
522
|
+
return row ? { id: row.id, name: row.name, schemaFileId: row.schemaFileId, dataType: row.dataType ?? '' } : undefined;
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* Name-only resolution. Rows linked to a schema win over unlinked stubs (null-schemaFileId rows
|
|
526
|
+
* from ref resolution); if more than one linked row has the name it is ambiguous and this throws.
|
|
527
|
+
* `label` names the lookup in the error (defaults to the model name).
|
|
528
|
+
*/
|
|
529
|
+
const resolveModelRecordByNameOnly = async (modelName, db, label = Array.isArray(modelName) ? modelName.join(', ') : modelName) => {
|
|
530
|
+
const rows = (await db
|
|
531
|
+
.select({ ...columns, schemaName: schemas.name })
|
|
532
|
+
.from(models)
|
|
533
|
+
.leftJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
534
|
+
.leftJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
|
|
535
|
+
.where(Array.isArray(modelName) ? inArray(models.name, modelName) : eq(models.name, modelName))
|
|
536
|
+
.orderBy(desc(models.id)));
|
|
537
|
+
if (rows.length === 0)
|
|
538
|
+
return undefined;
|
|
539
|
+
const linked = rows.filter((r) => r.schemaName);
|
|
540
|
+
const linkedIds = new Set(linked.map((r) => r.id));
|
|
541
|
+
if (linkedIds.size > 1) {
|
|
542
|
+
throw new AmbiguousModelError(label, linked.map((r) => r.schemaName));
|
|
543
|
+
}
|
|
544
|
+
const chosen = linked[0] ?? rows[0];
|
|
545
|
+
return { id: chosen.id, name: chosen.name, schemaFileId: chosen.schemaFileId };
|
|
546
|
+
};
|
|
547
|
+
/**
|
|
548
|
+
* The model names a model name or EAS model type can refer to: the name itself, else the names
|
|
549
|
+
* that match it in the tightest tier (ADR 0008, as Model.findByModelType matches): `new_model` →
|
|
550
|
+
* "New model", `sync_storage_post` → "SyncStoragePost", `htmlpage` → "HTMLPage". Each name at its
|
|
551
|
+
* nameMatch level (ADR 0009).
|
|
552
|
+
*/
|
|
553
|
+
const modelNamesForNameOrType = async (nameOrType, db) => {
|
|
554
|
+
const rows = (await db.selectDistinct({ name: models.name }).from(models));
|
|
555
|
+
const names = rows.map((r) => r.name);
|
|
556
|
+
if (names.includes(nameOrType))
|
|
557
|
+
return [nameOrType];
|
|
558
|
+
const levels = await nameMatchLevels(db);
|
|
559
|
+
return [...localNamesInBestMatchTier(nameOrType, names, { level: (name) => levels.model(name) })];
|
|
560
|
+
};
|
|
561
|
+
/**
|
|
562
|
+
* resolveModelRecord for a model name **or** an EAS model type (snake_case model name, what sync
|
|
563
|
+
* stores in seeds.type): "New model" resolves from `new_model`. Scope and ambiguity behave as in
|
|
564
|
+
* resolveModelRecord: modelFileId/modelId first, then the schema, then the name(s) alone, which throws
|
|
565
|
+
* AmbiguousModelError when models in several schemas match.
|
|
566
|
+
*/
|
|
567
|
+
const resolveModelRecordByNameOrType = async (nameOrType, scope = {}, db = BaseDb.getAppDb()) => {
|
|
568
|
+
if (!db)
|
|
569
|
+
return undefined;
|
|
570
|
+
if (scope.modelFileId || scope.modelId || scope.propertyFileId) {
|
|
571
|
+
const byId = await resolveModelRecord(undefined, scope, db);
|
|
572
|
+
if (byId)
|
|
573
|
+
return byId;
|
|
574
|
+
}
|
|
575
|
+
if (!nameOrType)
|
|
576
|
+
return undefined;
|
|
577
|
+
const names = await modelNamesForNameOrType(nameOrType, db);
|
|
578
|
+
if (names.length === 0)
|
|
579
|
+
return undefined;
|
|
580
|
+
if (names.length === 1) {
|
|
581
|
+
return resolveModelRecord(names[0], { ...scope, modelFileId: undefined, modelId: undefined, propertyFileId: undefined }, db);
|
|
582
|
+
}
|
|
583
|
+
// Several model names map to this type (e.g. "New model" and "NewModel" in different schemas).
|
|
584
|
+
if (scope.schemaId || scope.schemaName) {
|
|
585
|
+
const rows = (await db
|
|
586
|
+
.select(columns)
|
|
587
|
+
.from(models)
|
|
588
|
+
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
589
|
+
.innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
|
|
590
|
+
.where(and(inArray(models.name, names), scope.schemaId ? eq(schemas.id, scope.schemaId) : eq(schemas.name, scope.schemaName))));
|
|
591
|
+
if (new Set(rows.map((r) => r.id)).size === 1)
|
|
592
|
+
return rows[0];
|
|
593
|
+
if (scope.strict && rows.length === 0)
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
return resolveModelRecordByNameOnly(names, db, nameOrType);
|
|
597
|
+
};
|
|
598
|
+
/**
|
|
599
|
+
* The model name that a model name or EAS model type (`seeds.type`, `metadata.model_type`) refers
|
|
600
|
+
* to, resolved as {@link resolveModelRecordByNameOrType} resolves it: `blog_post` → "Blog Post"
|
|
601
|
+
* through the model's easName at its nameMatch level, never by re-casing the type. Undefined when
|
|
602
|
+
* no model matches; throws AmbiguousModelError when models in several schemas do.
|
|
603
|
+
*/
|
|
604
|
+
const modelNameForType = async (nameOrType, scope = {}, db = BaseDb.getAppDb()) => (await resolveModelRecordByNameOrType(nameOrType, scope, db))?.name;
|
|
605
|
+
/**
|
|
606
|
+
* Work out which model (schemaFileId) an item belongs to from whatever is at hand: an explicit
|
|
607
|
+
* modelFileId, the seed's model_file_id, or a metadata row's property_id → properties.model_id.
|
|
608
|
+
*/
|
|
609
|
+
const resolveItemModelFileId = async (hints, db = BaseDb.getAppDb()) => {
|
|
610
|
+
if (hints.modelFileId)
|
|
611
|
+
return hints.modelFileId;
|
|
612
|
+
if (!db)
|
|
613
|
+
return undefined;
|
|
614
|
+
if (hints.seedLocalId || hints.seedUid) {
|
|
615
|
+
const seed = await findSeedRow({ localId: hints.seedLocalId, uid: hints.seedUid }, db);
|
|
616
|
+
if (seed?.modelFileId)
|
|
617
|
+
return seed.modelFileId;
|
|
618
|
+
}
|
|
619
|
+
if (hints.propertyId) {
|
|
620
|
+
const rows = await db
|
|
621
|
+
.select({ modelFileId: models.schemaFileId })
|
|
622
|
+
.from(properties)
|
|
623
|
+
.innerJoin(models, eq(properties.modelId, models.id))
|
|
624
|
+
.where(eq(properties.id, hints.propertyId))
|
|
625
|
+
.limit(1);
|
|
626
|
+
if (rows[0]?.modelFileId)
|
|
627
|
+
return rows[0].modelFileId;
|
|
628
|
+
}
|
|
629
|
+
return undefined;
|
|
630
|
+
};
|
|
631
|
+
/**
|
|
632
|
+
* Resolve a ref (e.g. a Relation's target model) by name from the schema(s) its owning model belongs
|
|
633
|
+
* to, so "Tag" means the owner schema's Tag rather than any Tag. Falls back to resolveModelRecord.
|
|
634
|
+
*/
|
|
635
|
+
const resolveRefModelRecord = async (refModelName, owner, db = BaseDb.getAppDb()) => {
|
|
636
|
+
if (!db || !refModelName)
|
|
637
|
+
return undefined;
|
|
638
|
+
const ownerRow = owner.modelFileId || owner.modelId ? await resolveModelRecord(undefined, owner, db) : undefined;
|
|
639
|
+
const schemaFilter = ownerRow
|
|
640
|
+
? inArray(modelSchemas.schemaId,
|
|
641
|
+
// any schema the owner is linked to
|
|
642
|
+
db.select({ schemaId: modelSchemas.schemaId }).from(modelSchemas).where(eq(modelSchemas.modelId, ownerRow.id)))
|
|
643
|
+
: owner.schemaId
|
|
644
|
+
? eq(modelSchemas.schemaId, owner.schemaId)
|
|
645
|
+
: owner.schemaName
|
|
646
|
+
? eq(schemas.name, owner.schemaName)
|
|
647
|
+
: undefined;
|
|
648
|
+
if (schemaFilter) {
|
|
649
|
+
const rows = await db
|
|
650
|
+
.select(columns)
|
|
651
|
+
.from(models)
|
|
652
|
+
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
653
|
+
.innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
|
|
654
|
+
.where(and(eq(models.name, refModelName), schemaFilter))
|
|
655
|
+
.limit(1);
|
|
656
|
+
if (rows.length > 0)
|
|
657
|
+
return rows[0];
|
|
658
|
+
}
|
|
659
|
+
return resolveModelRecord(refModelName, {}, db);
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
var resolveModelRecord$1 = /*#__PURE__*/Object.freeze({
|
|
663
|
+
__proto__: null,
|
|
664
|
+
getModelSchemaName: getModelSchemaName,
|
|
665
|
+
modelNameForType: modelNameForType,
|
|
666
|
+
resolveItemModelFileId: resolveItemModelFileId,
|
|
667
|
+
resolveModelRecord: resolveModelRecord,
|
|
668
|
+
resolveModelRecordByNameOrType: resolveModelRecordByNameOrType,
|
|
669
|
+
resolvePropertyModelRecord: resolvePropertyModelRecord,
|
|
670
|
+
resolveRefModelRecord: resolveRefModelRecord
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Lowercase + trim an address for persist and compare.
|
|
675
|
+
* Does not checksum or validate — test and placeholder addresses must keep working.
|
|
676
|
+
*/
|
|
677
|
+
function normalizeHexAddress(addr) {
|
|
678
|
+
return addr.trim().toLowerCase();
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Lowercase, drop empties, and dedupe (first-seen order).
|
|
682
|
+
*/
|
|
683
|
+
function normalizeAddressList(addrs) {
|
|
684
|
+
if (!addrs?.length)
|
|
685
|
+
return [];
|
|
686
|
+
const seen = new Set();
|
|
687
|
+
const out = [];
|
|
688
|
+
for (const addr of addrs) {
|
|
689
|
+
if (addr == null || addr === '')
|
|
690
|
+
continue;
|
|
691
|
+
const normalized = normalizeHexAddress(String(addr));
|
|
692
|
+
if (!normalized || seen.has(normalized))
|
|
693
|
+
continue;
|
|
694
|
+
seen.add(normalized);
|
|
695
|
+
out.push(normalized);
|
|
696
|
+
}
|
|
697
|
+
return out;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Lowercase a publisher stamp. Empty / null → undefined (omit on write).
|
|
701
|
+
*/
|
|
702
|
+
function normalizePublisher(publisher) {
|
|
703
|
+
if (publisher == null || publisher === '')
|
|
704
|
+
return undefined;
|
|
705
|
+
const normalized = normalizeHexAddress(publisher);
|
|
706
|
+
return normalized || undefined;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Normalizes AddressConfiguration to { owned, watched }.
|
|
710
|
+
* - string[] -> { owned: addresses, watched: [] }
|
|
711
|
+
* - { owned, watched? } -> { owned, watched: watched ?? [] }
|
|
712
|
+
* Addresses are lowercased and deduped.
|
|
713
|
+
*/
|
|
714
|
+
function normalizeAddressConfig(addresses) {
|
|
715
|
+
if (!addresses) {
|
|
716
|
+
return { owned: [], watched: [] };
|
|
717
|
+
}
|
|
718
|
+
if (Array.isArray(addresses)) {
|
|
719
|
+
return { owned: normalizeAddressList(addresses), watched: [] };
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
owned: normalizeAddressList(addresses.owned),
|
|
723
|
+
watched: normalizeAddressList(addresses.watched),
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
69
727
|
/** Ref model name when propDef is a List of Relation, else undefined. */
|
|
70
728
|
function listRelationRef(propDef) {
|
|
71
729
|
if (!propDef)
|
|
@@ -159,123 +817,1205 @@ function metadataStorageName(key, def) {
|
|
|
159
817
|
return toMetadataPropertyName(key, def?.dataType, def?.refValueType);
|
|
160
818
|
}
|
|
161
819
|
/**
|
|
162
|
-
* Every name a row of this property may be stored under: its storage name, its key, and for an
|
|
163
|
-
* Image, File, Html or Relation (single or List) `<key>Id`, which older writers and sync used.
|
|
820
|
+
* Every name a row of this property may be stored under: its storage name, its key, and for an
|
|
821
|
+
* Image, File, Html or Relation (single or List) `<key>Id`, which older writers and sync used.
|
|
822
|
+
*/
|
|
823
|
+
function metadataStoredNameForms(key, def) {
|
|
824
|
+
const forms = [metadataStorageName(key, def), key];
|
|
825
|
+
if (!ID_SUFFIX_EXCLUDE.has(key) && needsMetadataIdSuffix(def?.dataType, def?.refValueType))
|
|
826
|
+
forms.push(`${key}Id`);
|
|
827
|
+
return [...new Set(forms)];
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* The schema key a stored metadata name belongs to among `defs` (keyed by schema key): an exact key
|
|
831
|
+
* first, then any of a key's stored forms. Undefined when no property matches. For rows without
|
|
832
|
+
* `property_id`; rows with one take the key from their properties row.
|
|
833
|
+
*/
|
|
834
|
+
function propertyKeyFromStoredName(defs, storedName) {
|
|
835
|
+
if (!storedName)
|
|
836
|
+
return undefined;
|
|
837
|
+
if (Object.prototype.hasOwnProperty.call(defs, storedName))
|
|
838
|
+
return storedName;
|
|
839
|
+
for (const [key, def] of Object.entries(defs)) {
|
|
840
|
+
if (metadataStoredNameForms(key, def).includes(storedName))
|
|
841
|
+
return key;
|
|
842
|
+
}
|
|
843
|
+
return undefined;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* The names a property's rows may be stored under, for matching rows that have no property_id and
|
|
847
|
+
* outbox ops (which name a property, not its id): the name with and without its `Id` for an Image,
|
|
848
|
+
* File, Html or Relation, or when `dataType` is unknown.
|
|
849
|
+
*/
|
|
850
|
+
function getMetadataPropertyNamesForQuery(propertyName, dataType, refValueType) {
|
|
851
|
+
if (ID_SUFFIX_EXCLUDE.has(propertyName))
|
|
852
|
+
return [propertyName];
|
|
853
|
+
if (propertyName.endsWith('Ids'))
|
|
854
|
+
return [propertyName];
|
|
855
|
+
if (propertyName.endsWith('Id')) {
|
|
856
|
+
const base = propertyName.slice(0, -2);
|
|
857
|
+
return [propertyName, base];
|
|
858
|
+
}
|
|
859
|
+
const needsId = needsMetadataIdSuffix(dataType, refValueType) ||
|
|
860
|
+
(dataType === undefined && refValueType === undefined);
|
|
861
|
+
if (needsId) {
|
|
862
|
+
return [propertyName, `${propertyName}Id`];
|
|
863
|
+
}
|
|
864
|
+
return [propertyName];
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* The name an item exposes a property under, from the name it is stored under: its alias when it
|
|
868
|
+
* has one (List of relation), else `tagIds` → `tags`, `authorId` → `author`. Names in
|
|
869
|
+
* PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP keep their `Id`.
|
|
870
|
+
*/
|
|
871
|
+
function toItemPropertyKey(storedName, alias) {
|
|
872
|
+
if (alias)
|
|
873
|
+
return alias;
|
|
874
|
+
if (storedName.endsWith('Ids'))
|
|
875
|
+
return pluralize(storedName.slice(0, -3));
|
|
876
|
+
if (storedName.endsWith('Id') && !PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP.has(storedName)) {
|
|
877
|
+
return storedName.slice(0, -2);
|
|
878
|
+
}
|
|
879
|
+
return storedName;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Schema property name from metadata name (strip Id/Ids suffix).
|
|
883
|
+
*/
|
|
884
|
+
function toSchemaPropertyName(propertyName) {
|
|
885
|
+
if (propertyName.endsWith('Ids'))
|
|
886
|
+
return propertyName.slice(0, -3);
|
|
887
|
+
if (propertyName.endsWith('Id'))
|
|
888
|
+
return propertyName.slice(0, -2);
|
|
889
|
+
return undefined;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Alternate property name for instance lookup (base <-> Id variant).
|
|
893
|
+
*/
|
|
894
|
+
function getAlternatePropertyNameForInstanceLookup(propertyName) {
|
|
895
|
+
if (ID_SUFFIX_EXCLUDE.has(propertyName))
|
|
896
|
+
return undefined;
|
|
897
|
+
if (propertyName.endsWith('Id'))
|
|
898
|
+
return propertyName.slice(0, -2);
|
|
899
|
+
return `${propertyName}Id`;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* The EAS property name publish writes for an item property (ADR 0008 §3), already in EAS form:
|
|
903
|
+
*
|
|
904
|
+
* - List of relations: its `<name><Model>Ids` storage name (`author_identity_ids`).
|
|
905
|
+
* - Single Relation, Image, File or Html: its schema key (`author`), never the local `<name>Id`
|
|
906
|
+
* storage name. The key is `propertyDef.name` when known, else `propertyName` without its `Id`.
|
|
907
|
+
* - Anything else: the property name.
|
|
908
|
+
*/
|
|
909
|
+
function easPropertyNameForPublish(propertyName, propertyDef) {
|
|
910
|
+
const listRelationName = listRelationEasPropertyName(propertyName, propertyDef ?? undefined);
|
|
911
|
+
if (listRelationName)
|
|
912
|
+
return easName(listRelationName);
|
|
913
|
+
if (propertyDef &&
|
|
914
|
+
normalizeDataType(propertyDef.dataType) !== 'List' &&
|
|
915
|
+
needsMetadataIdSuffix(propertyDef.dataType, propertyDef.refValueType) &&
|
|
916
|
+
!ID_SUFFIX_EXCLUDE.has(propertyName)) {
|
|
917
|
+
const key = propertyDef.name ?? (propertyName.endsWith('Id') ? propertyName.slice(0, -2) : propertyName);
|
|
918
|
+
return easName(key);
|
|
919
|
+
}
|
|
920
|
+
return easName(propertyName);
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* The EAS schema definition publish attests a property under (`string title`, `bytes32 author`),
|
|
924
|
+
* or undefined when its data type has no EAS type. Look schemas up by this, not by name: a name
|
|
925
|
+
* can have schemas of several types (Optimism Sepolia has `bytes32 storage_transaction_id` beside
|
|
926
|
+
* `string storage_transaction_id`).
|
|
927
|
+
*/
|
|
928
|
+
function easSchemaDefinitionForProperty(propertyName, propertyDef) {
|
|
929
|
+
if (!propertyDef?.dataType)
|
|
930
|
+
return undefined;
|
|
931
|
+
const dataType = normalizeDataType(String(propertyDef.dataType));
|
|
932
|
+
const easType = INTERNAL_DATA_TYPES[dataType]?.eas;
|
|
933
|
+
if (!easType)
|
|
934
|
+
return undefined;
|
|
935
|
+
return `${easType} ${easPropertyNameForPublish(propertyName, propertyDef)}`;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/** Schema file spellings that normalizePropertyRecordSchema maps to internal fields. */
|
|
939
|
+
const FILE_ALIAS_KEYS = ['type', 'model', 'items', 'storage'];
|
|
940
|
+
/**
|
|
941
|
+
* Fields an instance or a row carries that aren't part of the definition. Keys starting with `_`
|
|
942
|
+
* (`_dbId`, `_propertyFileId`, `_originalValues`, ...) are dropped too.
|
|
943
|
+
*/
|
|
944
|
+
const NON_DEFINITION_KEYS = new Set([
|
|
945
|
+
...FILE_ALIAS_KEYS,
|
|
946
|
+
'name',
|
|
947
|
+
'modelId',
|
|
948
|
+
'modelName',
|
|
949
|
+
'refModelId',
|
|
950
|
+
'refModelName',
|
|
951
|
+
'schemaName',
|
|
952
|
+
'isEdited',
|
|
953
|
+
'TObject',
|
|
954
|
+
]);
|
|
955
|
+
const parseDefinition = (json) => {
|
|
956
|
+
if (!json)
|
|
957
|
+
return {};
|
|
958
|
+
try {
|
|
959
|
+
const parsed = JSON.parse(json);
|
|
960
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
return {};
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
/** A definition in any accepted shape, reduced to what the column stores. */
|
|
967
|
+
const toStoredDefinition = (def) => {
|
|
968
|
+
const normalized = normalizePropertyRecordSchema(def ?? {});
|
|
969
|
+
const hasRef = normalized.dataType === ModelPropertyDataTypes.Relation || normalized.dataType === ModelPropertyDataTypes.List;
|
|
970
|
+
const out = {};
|
|
971
|
+
for (const [key, value] of Object.entries(normalized)) {
|
|
972
|
+
if (value === undefined || typeof value === 'function')
|
|
973
|
+
continue;
|
|
974
|
+
if (key.startsWith('_') || NON_DEFINITION_KEYS.has(key))
|
|
975
|
+
continue;
|
|
976
|
+
if (key === 'ref' && !hasRef)
|
|
977
|
+
continue;
|
|
978
|
+
out[key] = value;
|
|
979
|
+
}
|
|
980
|
+
return out;
|
|
981
|
+
};
|
|
982
|
+
/**
|
|
983
|
+
* The `definition` column value for a definition in any accepted shape (schema file property,
|
|
984
|
+
* Model.create property, ModelProperty context). With `previous` (the row's current value), fields
|
|
985
|
+
* the new definition doesn't have are kept: a write that only knows the columns (a creation
|
|
986
|
+
* payload, a row-built instance) must not drop the validation a schema file or code stored.
|
|
987
|
+
*/
|
|
988
|
+
function propertyDefinitionColumn(def, previous) {
|
|
989
|
+
const stored = toStoredDefinition(def);
|
|
990
|
+
return JSON.stringify(previous ? { ...toStoredDefinition(parseDefinition(previous)), ...stored } : stored);
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* A row as a {@link PropertyDefinition}. `refModelName` is the name of the row's `ref_model_id`
|
|
994
|
+
* model; without one, a Relation or List keeps the definition's `ref` (a target not stored yet).
|
|
995
|
+
*/
|
|
996
|
+
function propertyDefinitionFromRow(row, refModelName) {
|
|
997
|
+
const stored = toStoredDefinition(parseDefinition(row.definition));
|
|
998
|
+
const dataType = normalizeDataType(row.dataType);
|
|
999
|
+
const hasRef = dataType === ModelPropertyDataTypes.Relation || dataType === ModelPropertyDataTypes.List;
|
|
1000
|
+
const ref = refModelName ?? (hasRef ? stored.ref : undefined);
|
|
1001
|
+
const def = {
|
|
1002
|
+
...stored,
|
|
1003
|
+
id: row.schemaFileId ?? undefined,
|
|
1004
|
+
_dbId: row.id,
|
|
1005
|
+
name: row.name,
|
|
1006
|
+
modelId: row.modelId,
|
|
1007
|
+
dataType,
|
|
1008
|
+
ref,
|
|
1009
|
+
refModelName: ref,
|
|
1010
|
+
refModelId: row.refModelId ?? undefined,
|
|
1011
|
+
refValueType: row.refValueType ? normalizeDataType(row.refValueType) : undefined,
|
|
1012
|
+
storageType: row.storageType ?? undefined,
|
|
1013
|
+
localStorageDir: row.localStorageDir ?? undefined,
|
|
1014
|
+
filenameSuffix: row.filenameSuffix ?? undefined,
|
|
1015
|
+
required: row.required ?? stored.required ?? undefined,
|
|
1016
|
+
};
|
|
1017
|
+
for (const key of Object.keys(def))
|
|
1018
|
+
if (def[key] === undefined)
|
|
1019
|
+
delete def[key];
|
|
1020
|
+
return def;
|
|
1021
|
+
}
|
|
1022
|
+
const refModels = alias(models, 'ref_model');
|
|
1023
|
+
const selectDefinitionRows = (db) => db
|
|
1024
|
+
.select({ property: properties, refModelName: refModels.name })
|
|
1025
|
+
.from(properties)
|
|
1026
|
+
.leftJoin(refModels, eq(refModels.id, properties.refModelId));
|
|
1027
|
+
/**
|
|
1028
|
+
* The complete definition of one property, from its row: by row id (`metadata.property_id`), or by
|
|
1029
|
+
* model row id and property name. Undefined when there's no such row.
|
|
1030
|
+
*/
|
|
1031
|
+
async function getPropertyDefinition(target, db = BaseDb.getAppDb()) {
|
|
1032
|
+
if (!db)
|
|
1033
|
+
throw new Error('Database not available');
|
|
1034
|
+
const where = typeof target === 'number'
|
|
1035
|
+
? eq(properties.id, target)
|
|
1036
|
+
: and(eq(properties.modelId, target.modelId), eq(properties.name, target.name));
|
|
1037
|
+
const [row] = await selectDefinitionRows(db).where(where).limit(1);
|
|
1038
|
+
return row ? propertyDefinitionFromRow(row.property, row.refModelName) : undefined;
|
|
1039
|
+
}
|
|
1040
|
+
/** {@link getPropertyDefinition} for every property of one model, by property name, in one read. */
|
|
1041
|
+
async function getPropertyDefinitions(modelId, db = BaseDb.getAppDb()) {
|
|
1042
|
+
if (!db)
|
|
1043
|
+
throw new Error('Database not available');
|
|
1044
|
+
const rows = await selectDefinitionRows(db).where(eq(properties.modelId, modelId));
|
|
1045
|
+
return new Map(rows.map((row) => [
|
|
1046
|
+
row.property.name,
|
|
1047
|
+
propertyDefinitionFromRow(row.property, row.refModelName),
|
|
1048
|
+
]));
|
|
1049
|
+
}
|
|
1050
|
+
/** {@link getPropertyDefinition} for each of these property file ids that has a row, by file id. */
|
|
1051
|
+
async function getPropertyDefinitionsByFileId(propertyFileIds, db = BaseDb.getAppDb()) {
|
|
1052
|
+
if (!db)
|
|
1053
|
+
throw new Error('Database not available');
|
|
1054
|
+
if (propertyFileIds.length === 0)
|
|
1055
|
+
return new Map();
|
|
1056
|
+
const rows = await selectDefinitionRows(db).where(inArray(properties.schemaFileId, propertyFileIds));
|
|
1057
|
+
return new Map(rows.map((row) => [
|
|
1058
|
+
row.property.schemaFileId,
|
|
1059
|
+
propertyDefinitionFromRow(row.property, row.refModelName),
|
|
1060
|
+
]));
|
|
1061
|
+
}
|
|
1062
|
+
/** Fields only a definition holds (no column), which code-defined properties can set. */
|
|
1063
|
+
const DEFINITION_ONLY_FIELDS = ['validation', 'htmlEmbeddedDataUriPolicy', 'description'];
|
|
1064
|
+
/** The {@link DEFINITION_ONLY_FIELDS} a definition sets. */
|
|
1065
|
+
const definitionOnlyFields = (def) => Object.fromEntries(DEFINITION_ONLY_FIELDS.filter((key) => def?.[key] !== undefined).map((key) => [key, def[key]]));
|
|
1066
|
+
/**
|
|
1067
|
+
* Stores code-defined fields ({@link definitionOnlyFields}) in the row's definition, when the row
|
|
1068
|
+
* exists and its definition differs. For properties whose row was written before the code that
|
|
1069
|
+
* defines them ran (a schema file's row, an earlier run), so their rules persist at create time.
|
|
1070
|
+
*/
|
|
1071
|
+
async function persistDefinitionFields(where, fields, db = BaseDb.getAppDb()) {
|
|
1072
|
+
if (!db || Object.keys(fields).length === 0)
|
|
1073
|
+
return;
|
|
1074
|
+
const condition = 'id' in where ? eq(properties.id, where.id) : eq(properties.schemaFileId, where.schemaFileId);
|
|
1075
|
+
const [row] = await db.select({ id: properties.id, definition: properties.definition }).from(properties).where(condition).limit(1);
|
|
1076
|
+
if (!row)
|
|
1077
|
+
return;
|
|
1078
|
+
const next = propertyDefinitionColumn(fields, row.definition);
|
|
1079
|
+
if (row.definition && JSON.stringify(toStoredDefinition(parseDefinition(row.definition))) === next)
|
|
1080
|
+
return;
|
|
1081
|
+
await db.update(properties).set({ definition: next }).where(eq(properties.id, row.id));
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
var propertyDefinition = /*#__PURE__*/Object.freeze({
|
|
1085
|
+
__proto__: null,
|
|
1086
|
+
DEFINITION_ONLY_FIELDS: DEFINITION_ONLY_FIELDS,
|
|
1087
|
+
definitionOnlyFields: definitionOnlyFields,
|
|
1088
|
+
getPropertyDefinition: getPropertyDefinition,
|
|
1089
|
+
getPropertyDefinitions: getPropertyDefinitions,
|
|
1090
|
+
getPropertyDefinitionsByFileId: getPropertyDefinitionsByFileId,
|
|
1091
|
+
persistDefinitionFields: persistDefinitionFields,
|
|
1092
|
+
propertyDefinitionColumn: propertyDefinitionColumn,
|
|
1093
|
+
propertyDefinitionFromRow: propertyDefinitionFromRow
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* The writers of definition rows: `schemas`, `models`, `properties` and `model_schemas` (SDK audit
|
|
1098
|
+
* T9). One upsert per table. Every function takes the database to write, normally a transaction
|
|
1099
|
+
* from {@link inDefinitionsTransaction}, and reads only through it: in the browser a query on
|
|
1100
|
+
* `BaseDb.getAppDb()` waits for an open transaction to end.
|
|
1101
|
+
*
|
|
1102
|
+
* The lookups here decide which row a definition is (a schema by file id, then by name; a model by
|
|
1103
|
+
* file id, then an adoptable row of that name; a property by file id, then by name in its model).
|
|
1104
|
+
* The unique keys (`schema_file_id` on all three tables, `properties(name, model_id)`,
|
|
1105
|
+
* `model_schemas(model_id, schema_id)`) are enforced by the database, and inserts resolve a
|
|
1106
|
+
* conflict with ON CONFLICT instead of a second lookup.
|
|
1107
|
+
*/
|
|
1108
|
+
const logger$b = debug('seedSdk:db:write:definitions');
|
|
1109
|
+
/**
|
|
1110
|
+
* Runs one user action on definitions (an apply, a save, a rename) in one transaction. `fn` must
|
|
1111
|
+
* write through `tx` only and have no other side effects: it is retried whole when another tab
|
|
1112
|
+
* holds the database.
|
|
1113
|
+
*/
|
|
1114
|
+
const inDefinitionsTransaction = (fn) => BaseDb.transaction(fn);
|
|
1115
|
+
// ---------------------------------------------------------------------------------------------
|
|
1116
|
+
// model_schemas
|
|
1117
|
+
/** Links a model to a schema. Returns true when this call inserted the link. */
|
|
1118
|
+
async function linkModelToSchemaRow(db, modelId, schemaId) {
|
|
1119
|
+
const inserted = await db
|
|
1120
|
+
.insert(modelSchemas)
|
|
1121
|
+
.values({ modelId, schemaId })
|
|
1122
|
+
.onConflictDoNothing()
|
|
1123
|
+
.returning({ id: modelSchemas.id });
|
|
1124
|
+
return inserted.length > 0;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Writes a schema row and returns it with `inserted` set when it is new.
|
|
1128
|
+
*
|
|
1129
|
+
* The row is the one with `schemaFileId`, else one of that name: a draft write takes a draft of
|
|
1130
|
+
* that name; a published write takes a published row, else a draft (which it publishes). A
|
|
1131
|
+
* published row keeps its schemaFileId against another id of the same version, and keeps its newer
|
|
1132
|
+
* version against an older version's file. With `rowId` that row is written, found by the caller
|
|
1133
|
+
* (a rename finds it by the old name), and takes `schemaFileId`.
|
|
1134
|
+
*/
|
|
1135
|
+
async function upsertSchemaRow(db, input, options = {}) {
|
|
1136
|
+
// The stored schema file may change its nameMatch levels (ADR 0009).
|
|
1137
|
+
clearNameMatchLevels();
|
|
1138
|
+
const { schemaFileId, schemaData, isDraft, isEdited, contentHash } = input;
|
|
1139
|
+
const update = async (existing, updates) => {
|
|
1140
|
+
if (input.name && existing.name !== input.name)
|
|
1141
|
+
updates.name = input.name;
|
|
1142
|
+
if (schemaData !== undefined)
|
|
1143
|
+
updates.schemaData = schemaData;
|
|
1144
|
+
if (contentHash !== undefined && existing.contentHash !== contentHash)
|
|
1145
|
+
updates.contentHash = contentHash;
|
|
1146
|
+
if (input.version !== undefined && existing.version !== input.version)
|
|
1147
|
+
updates.version = input.version;
|
|
1148
|
+
if (isDraft !== undefined && existing.isDraft !== isDraft)
|
|
1149
|
+
updates.isDraft = isDraft;
|
|
1150
|
+
if (isEdited !== undefined && existing.isEdited !== isEdited)
|
|
1151
|
+
updates.isEdited = isEdited;
|
|
1152
|
+
if (input.updatedAt && existing.updatedAt !== input.updatedAt)
|
|
1153
|
+
updates.updatedAt = input.updatedAt;
|
|
1154
|
+
if (Object.keys(updates).length > 0) {
|
|
1155
|
+
await db.update(schemas).set(updates).where(eq(schemas.id, existing.id));
|
|
1156
|
+
}
|
|
1157
|
+
return { ...existing, ...updates, inserted: false };
|
|
1158
|
+
};
|
|
1159
|
+
if (options.rowId !== undefined) {
|
|
1160
|
+
const [row] = await db.select().from(schemas).where(eq(schemas.id, options.rowId)).limit(1);
|
|
1161
|
+
if (!row)
|
|
1162
|
+
throw new Error(`Schema row ${options.rowId} not found`);
|
|
1163
|
+
return update(row, schemaFileId && row.schemaFileId !== schemaFileId ? { schemaFileId } : {});
|
|
1164
|
+
}
|
|
1165
|
+
if (schemaFileId) {
|
|
1166
|
+
const [byFileId] = await db.select().from(schemas).where(eq(schemas.schemaFileId, schemaFileId)).limit(1);
|
|
1167
|
+
if (byFileId)
|
|
1168
|
+
return update(byFileId, {});
|
|
1169
|
+
}
|
|
1170
|
+
if (isDraft === true) {
|
|
1171
|
+
const [byName] = await db.select().from(schemas).where(eq(schemas.name, input.name)).limit(1);
|
|
1172
|
+
if (byName?.isDraft === true) {
|
|
1173
|
+
const updates = { updatedAt: input.updatedAt || Date.now() };
|
|
1174
|
+
if (schemaFileId && byName.schemaFileId !== schemaFileId)
|
|
1175
|
+
updates.schemaFileId = schemaFileId;
|
|
1176
|
+
return update(byName, updates);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
// By name: a published write prefers a published row, then takes a draft of that name.
|
|
1180
|
+
let [existing] = await db
|
|
1181
|
+
.select()
|
|
1182
|
+
.from(schemas)
|
|
1183
|
+
.where(isDraft === false ? and(eq(schemas.name, input.name), eq(schemas.isDraft, false)) : eq(schemas.name, input.name))
|
|
1184
|
+
.limit(1);
|
|
1185
|
+
if (!existing && isDraft === false) {
|
|
1186
|
+
[existing] = await db
|
|
1187
|
+
.select()
|
|
1188
|
+
.from(schemas)
|
|
1189
|
+
.where(and(eq(schemas.name, input.name), eq(schemas.isDraft, true)))
|
|
1190
|
+
.limit(1);
|
|
1191
|
+
}
|
|
1192
|
+
if (existing) {
|
|
1193
|
+
// A complete schema file can bring a version under its own id (imported files may not keep it).
|
|
1194
|
+
const isOtherVersionFile = !!schemaFileId &&
|
|
1195
|
+
!!existing.schemaFileId &&
|
|
1196
|
+
existing.schemaFileId !== schemaFileId &&
|
|
1197
|
+
existing.isDraft !== true &&
|
|
1198
|
+
input.version !== undefined &&
|
|
1199
|
+
input.version !== existing.version;
|
|
1200
|
+
if (isOtherVersionFile && input.version < existing.version) {
|
|
1201
|
+
// An older version's file: rolling the row back to it would leave the newer version's id
|
|
1202
|
+
// pointing at the older version's data.
|
|
1203
|
+
logger$b(`Keeping schema "${input.name}" at v${existing.version}; not replacing it with older v${input.version}`);
|
|
1204
|
+
return { ...existing, inserted: false };
|
|
1205
|
+
}
|
|
1206
|
+
const updates = {};
|
|
1207
|
+
// A published row keeps its id unless this is a newer version's file; a draft or id-less row
|
|
1208
|
+
// takes the new one.
|
|
1209
|
+
if (schemaFileId &&
|
|
1210
|
+
existing.schemaFileId !== schemaFileId &&
|
|
1211
|
+
(existing.isDraft === true || !existing.schemaFileId || isOtherVersionFile)) {
|
|
1212
|
+
updates.schemaFileId = schemaFileId;
|
|
1213
|
+
}
|
|
1214
|
+
return update(existing, updates);
|
|
1215
|
+
}
|
|
1216
|
+
const [row] = await db
|
|
1217
|
+
.insert(schemas)
|
|
1218
|
+
.values({
|
|
1219
|
+
name: input.name,
|
|
1220
|
+
version: input.version,
|
|
1221
|
+
schemaFileId: schemaFileId || null,
|
|
1222
|
+
schemaData: schemaData || null,
|
|
1223
|
+
contentHash: contentHash ?? null,
|
|
1224
|
+
isDraft: isDraft ?? false,
|
|
1225
|
+
isEdited: isEdited ?? false,
|
|
1226
|
+
createdAt: input.createdAt,
|
|
1227
|
+
updatedAt: input.updatedAt,
|
|
1228
|
+
})
|
|
1229
|
+
.returning();
|
|
1230
|
+
return { ...row, inserted: true };
|
|
1231
|
+
}
|
|
1232
|
+
// ---------------------------------------------------------------------------------------------
|
|
1233
|
+
// models
|
|
1234
|
+
/**
|
|
1235
|
+
* Writes the models row with `schemaFileId`, or inserts a name-only row without one. An existing
|
|
1236
|
+
* row takes `name`; `isEdited` is set on insert only.
|
|
1237
|
+
*/
|
|
1238
|
+
async function upsertModelRow(db, input) {
|
|
1239
|
+
const values = { name: input.name, schemaFileId: input.schemaFileId || null, isEdited: input.isEdited ?? false };
|
|
1240
|
+
if (!values.schemaFileId) {
|
|
1241
|
+
const [row] = await db.insert(models).values(values).returning();
|
|
1242
|
+
return row;
|
|
1243
|
+
}
|
|
1244
|
+
const [row] = await db
|
|
1245
|
+
.insert(models)
|
|
1246
|
+
.values(values)
|
|
1247
|
+
.onConflictDoUpdate({ target: models.schemaFileId, set: { name: input.name } })
|
|
1248
|
+
.returning();
|
|
1249
|
+
return row;
|
|
1250
|
+
}
|
|
1251
|
+
/** Name-only models rows for the names that have none yet. Returns every row of those names. */
|
|
1252
|
+
async function insertModelStubs(db, modelNames) {
|
|
1253
|
+
const columns = { id: models.id, name: models.name, schemaFileId: models.schemaFileId };
|
|
1254
|
+
const rows = modelNames.length
|
|
1255
|
+
? await db.select(columns).from(models).where(inArray(models.name, modelNames))
|
|
1256
|
+
: [];
|
|
1257
|
+
const missing = [...new Set(modelNames)].filter((name) => !rows.some((row) => row.name === name));
|
|
1258
|
+
if (missing.length > 0) {
|
|
1259
|
+
rows.push(...(await db.insert(models).values(missing.map((name) => ({ name }))).returning(columns)));
|
|
1260
|
+
}
|
|
1261
|
+
return rows;
|
|
1262
|
+
}
|
|
1263
|
+
const isLinkedTo = async (db, modelId, schemaId) => {
|
|
1264
|
+
const links = await db
|
|
1265
|
+
.select({ schemaId: modelSchemas.schemaId })
|
|
1266
|
+
.from(modelSchemas)
|
|
1267
|
+
.where(eq(modelSchemas.modelId, modelId));
|
|
1268
|
+
return {
|
|
1269
|
+
any: links.length > 0,
|
|
1270
|
+
other: links.some((link) => link.schemaId !== schemaId),
|
|
1271
|
+
};
|
|
1272
|
+
};
|
|
1273
|
+
/**
|
|
1274
|
+
* Finds or creates the models row for `modelName` in a schema, by `modelFileId` (preferred) or
|
|
1275
|
+
* name. Model names are only unique per schema, so a same-name row is reused only when no other
|
|
1276
|
+
* schema claims it: a null-schemaFileId stub (from ref resolution or init), or for a ref without a
|
|
1277
|
+
* file id, this schema's row. A row with a file id that no schema links is a deleted schema's
|
|
1278
|
+
* leftover and is not adopted. An adopted row takes `modelFileId`.
|
|
1279
|
+
*/
|
|
1280
|
+
async function findOrCreateModelRow(db, modelName, modelFileId, schemaId) {
|
|
1281
|
+
if (modelFileId) {
|
|
1282
|
+
const [byFileId] = await db.select().from(models).where(eq(models.schemaFileId, modelFileId)).limit(1);
|
|
1283
|
+
if (byFileId) {
|
|
1284
|
+
if (byFileId.name === modelName)
|
|
1285
|
+
return byFileId;
|
|
1286
|
+
await db.update(models).set({ name: modelName }).where(eq(models.id, byFileId.id));
|
|
1287
|
+
return { ...byFileId, name: modelName };
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
const byName = await db.select().from(models).where(eq(models.name, modelName));
|
|
1291
|
+
let chosen;
|
|
1292
|
+
if (byName.length > 0) {
|
|
1293
|
+
if (!modelFileId && schemaId && byName.length > 1) {
|
|
1294
|
+
for (const candidate of byName) {
|
|
1295
|
+
const links = await isLinkedTo(db, candidate.id, schemaId);
|
|
1296
|
+
if (links.any && !links.other) {
|
|
1297
|
+
chosen = candidate;
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
chosen ?? (chosen = byName.find((row) => !row.schemaFileId));
|
|
1303
|
+
if (!chosen && modelFileId) {
|
|
1304
|
+
// Every same-name row has another file id: adopt only one no other schema claims.
|
|
1305
|
+
for (const candidate of byName) {
|
|
1306
|
+
const links = await isLinkedTo(db, candidate.id, schemaId);
|
|
1307
|
+
if (candidate.schemaFileId && !links.any)
|
|
1308
|
+
continue;
|
|
1309
|
+
if (!links.other) {
|
|
1310
|
+
chosen = candidate;
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
else if (!chosen) {
|
|
1316
|
+
chosen = (byName.length === 1 ? byName[0] : undefined) || byName.find((row) => !!row.schemaFileId) || byName[0];
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (chosen && modelFileId && chosen.schemaFileId !== modelFileId) {
|
|
1320
|
+
// Adopt a stub or an unclaimed same-name row by giving it this file id.
|
|
1321
|
+
await db.update(models).set({ schemaFileId: modelFileId, isEdited: false }).where(eq(models.id, chosen.id));
|
|
1322
|
+
logger$b(`Adopted existing model "${modelName}" (id: ${chosen.id}) with schemaFileId "${modelFileId}"`);
|
|
1323
|
+
return { ...chosen, schemaFileId: modelFileId, isEdited: false };
|
|
1324
|
+
}
|
|
1325
|
+
if (chosen)
|
|
1326
|
+
return chosen;
|
|
1327
|
+
return upsertModelRow(db, { name: modelName, schemaFileId: modelFileId });
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Finds or creates the models row a Relation property on `ownerModelId` points to: the ref model
|
|
1331
|
+
* in the owner's schema(s) first, since model names are only unique per schema.
|
|
1332
|
+
*/
|
|
1333
|
+
async function findOrCreateRefModelRow(db, refModelName, ownerModelId) {
|
|
1334
|
+
try {
|
|
1335
|
+
const ref = await resolveRefModelRecord(refModelName, { modelId: ownerModelId }, db);
|
|
1336
|
+
if (ref)
|
|
1337
|
+
return ref;
|
|
1338
|
+
}
|
|
1339
|
+
catch (error) {
|
|
1340
|
+
// Not in the owner's schema and defined by several others: pick/create like an import does
|
|
1341
|
+
if (!(error instanceof AmbiguousModelError))
|
|
1342
|
+
throw error;
|
|
1343
|
+
}
|
|
1344
|
+
const [ownerLink] = await db
|
|
1345
|
+
.select({ schemaId: modelSchemas.schemaId })
|
|
1346
|
+
.from(modelSchemas)
|
|
1347
|
+
.where(eq(modelSchemas.modelId, ownerModelId))
|
|
1348
|
+
.limit(1);
|
|
1349
|
+
return findOrCreateModelRow(db, refModelName, undefined, ownerLink?.schemaId ?? undefined);
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Renames a model. With `schemaNameOrId` only that schema's model is renamed; without it the name
|
|
1353
|
+
* alone is used, which throws AmbiguousModelError when several schemas define it. When the new
|
|
1354
|
+
* name already exists in the scope that row is returned unchanged. Unscoped, a missing model is
|
|
1355
|
+
* inserted under the new name.
|
|
1356
|
+
*/
|
|
1357
|
+
async function renameModelRow(db, oldName, newName, schemaNameOrId) {
|
|
1358
|
+
const scope = schemaNameOrId === undefined
|
|
1359
|
+
? {}
|
|
1360
|
+
: typeof schemaNameOrId === 'number'
|
|
1361
|
+
? { schemaId: schemaNameOrId, strict: true }
|
|
1362
|
+
: { schemaName: schemaNameOrId, strict: true };
|
|
1363
|
+
const model = await resolveModelRecord(oldName, scope, db);
|
|
1364
|
+
if (!model) {
|
|
1365
|
+
if (schemaNameOrId !== undefined)
|
|
1366
|
+
throw new Error(`Model "${oldName}" not found in schema "${schemaNameOrId}"`);
|
|
1367
|
+
return upsertModelRow(db, { name: newName });
|
|
1368
|
+
}
|
|
1369
|
+
const existingWithNewName = await resolveModelRecord(newName, scope, db);
|
|
1370
|
+
if (existingWithNewName)
|
|
1371
|
+
return existingWithNewName;
|
|
1372
|
+
await db.update(models).set({ name: newName }).where(eq(models.id, model.id));
|
|
1373
|
+
return { ...model, name: newName };
|
|
1374
|
+
}
|
|
1375
|
+
const propertyRowFields = [
|
|
1376
|
+
'name',
|
|
1377
|
+
'modelId',
|
|
1378
|
+
'dataType',
|
|
1379
|
+
'schemaFileId',
|
|
1380
|
+
'refModelId',
|
|
1381
|
+
'refValueType',
|
|
1382
|
+
'required',
|
|
1383
|
+
'isEdited',
|
|
1384
|
+
'storageType',
|
|
1385
|
+
'localStorageDir',
|
|
1386
|
+
'filenameSuffix',
|
|
1387
|
+
'definition',
|
|
1388
|
+
];
|
|
1389
|
+
/**
|
|
1390
|
+
* Writes the properties row for a property definition. The row is the one with `schemaFileId`,
|
|
1391
|
+
* else the one named `previousName` or `name` in the model. A row with that schema_file_id on
|
|
1392
|
+
* another model is left alone. The rules for fields the input leaves out are on
|
|
1393
|
+
* {@link PropertyRowInput}; an unchanged row is not written.
|
|
1394
|
+
*/
|
|
1395
|
+
async function upsertPropertyRow(db, input, options = {}) {
|
|
1396
|
+
let existing;
|
|
1397
|
+
if (input.schemaFileId) {
|
|
1398
|
+
[existing] = await db.select().from(properties).where(eq(properties.schemaFileId, input.schemaFileId)).limit(1);
|
|
1399
|
+
if (existing && existing.modelId !== input.modelId) {
|
|
1400
|
+
logger$b(`Property ${input.name} (${input.schemaFileId}) belongs to model row ${existing.modelId}, not ${input.modelId}; left as is`);
|
|
1401
|
+
return { row: existing, changed: false };
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
for (const name of [input.previousName, input.name]) {
|
|
1405
|
+
if (existing || !name)
|
|
1406
|
+
continue;
|
|
1407
|
+
[existing] = await db
|
|
1408
|
+
.select()
|
|
1409
|
+
.from(properties)
|
|
1410
|
+
.where(and(eq(properties.name, name), eq(properties.modelId, input.modelId)))
|
|
1411
|
+
.limit(1);
|
|
1412
|
+
}
|
|
1413
|
+
if (existing && options.keepEditedRow && existing.isEdited === true) {
|
|
1414
|
+
logger$b(`Keeping locally edited property row ${existing.name} (id ${existing.id})`);
|
|
1415
|
+
return { row: existing, changed: false };
|
|
1416
|
+
}
|
|
1417
|
+
let refModelId = input.refModelId ?? null;
|
|
1418
|
+
if (input.refModelName && input.refModelId == null) {
|
|
1419
|
+
refModelId = (await findOrCreateRefModelRow(db, input.refModelName, input.modelId)).id ?? null;
|
|
1420
|
+
}
|
|
1421
|
+
const values = {
|
|
1422
|
+
name: input.name,
|
|
1423
|
+
modelId: input.modelId,
|
|
1424
|
+
dataType: input.dataType ? normalizeDataType(input.dataType) : (existing?.dataType ?? ''),
|
|
1425
|
+
schemaFileId: (options.keepStoredSchemaFileId && existing?.schemaFileId) || input.schemaFileId || existing?.schemaFileId || null,
|
|
1426
|
+
refModelId,
|
|
1427
|
+
refValueType: input.refValueType ? normalizeDataType(input.refValueType) : null,
|
|
1428
|
+
required: input.required ?? existing?.required ?? false,
|
|
1429
|
+
isEdited: input.isEdited ?? existing?.isEdited ?? false,
|
|
1430
|
+
storageType: input.storage ? (input.storage.storageType ?? null) : (existing?.storageType ?? null),
|
|
1431
|
+
localStorageDir: input.storage ? (input.storage.localStorageDir ?? null) : (existing?.localStorageDir ?? null),
|
|
1432
|
+
filenameSuffix: input.storage ? (input.storage.filenameSuffix ?? null) : (existing?.filenameSuffix ?? null),
|
|
1433
|
+
definition: input.definition
|
|
1434
|
+
? propertyDefinitionColumn(input.definition, input.replaceDefinition ? null : existing?.definition)
|
|
1435
|
+
: (existing?.definition ?? null),
|
|
1436
|
+
};
|
|
1437
|
+
if (existing) {
|
|
1438
|
+
const unchanged = propertyRowFields.every((field) => (existing[field] ?? null) === (values[field] ?? null));
|
|
1439
|
+
if (unchanged)
|
|
1440
|
+
return { row: existing, changed: false };
|
|
1441
|
+
await db.update(properties).set(values).where(eq(properties.id, existing.id));
|
|
1442
|
+
return { row: { ...existing, ...values }, changed: true };
|
|
1443
|
+
}
|
|
1444
|
+
const { name, modelId, ...rest } = values;
|
|
1445
|
+
const [row] = await db
|
|
1446
|
+
.insert(properties)
|
|
1447
|
+
.values(values)
|
|
1448
|
+
.onConflictDoUpdate(values.schemaFileId
|
|
1449
|
+
? { target: properties.schemaFileId, set: { name, ...rest } }
|
|
1450
|
+
: { target: [properties.name, properties.modelId], set: rest })
|
|
1451
|
+
.returning();
|
|
1452
|
+
return { row, changed: true };
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
var definitions = /*#__PURE__*/Object.freeze({
|
|
1456
|
+
__proto__: null,
|
|
1457
|
+
findOrCreateModelRow: findOrCreateModelRow,
|
|
1458
|
+
findOrCreateRefModelRow: findOrCreateRefModelRow,
|
|
1459
|
+
inDefinitionsTransaction: inDefinitionsTransaction,
|
|
1460
|
+
insertModelStubs: insertModelStubs,
|
|
1461
|
+
linkModelToSchemaRow: linkModelToSchemaRow,
|
|
1462
|
+
renameModelRow: renameModelRow,
|
|
1463
|
+
upsertModelRow: upsertModelRow,
|
|
1464
|
+
upsertPropertyRow: upsertPropertyRow,
|
|
1465
|
+
upsertSchemaRow: upsertSchemaRow
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
const logger$a = debug('seedSdk:helpers:db');
|
|
1469
|
+
/**
|
|
1470
|
+
* Posts one message and closes the channel. A message already posted is still delivered after its
|
|
1471
|
+
* sender closes, and an open BroadcastChannel keeps a Node process alive (each was a MessagePort
|
|
1472
|
+
* handle that stopped scripts exiting).
|
|
1473
|
+
*/
|
|
1474
|
+
const postBroadcast = (channelName, message) => {
|
|
1475
|
+
const channel = new BroadcastChannel(channelName);
|
|
1476
|
+
try {
|
|
1477
|
+
channel.postMessage(message);
|
|
1478
|
+
}
|
|
1479
|
+
finally {
|
|
1480
|
+
channel.close();
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
/**
|
|
1484
|
+
* The `properties` storage columns for a property definition, in either the internal shape
|
|
1485
|
+
* (`storageType`, `localStorageDir`, `filenameSuffix`) or the schema-file shape
|
|
1486
|
+
* (`storage: { type, path, extension }`). All null when the definition has no storage config.
|
|
1487
|
+
*/
|
|
1488
|
+
function propertyStorageColumns(def) {
|
|
1489
|
+
const normalized = normalizePropertyRecordSchema((def ?? {}));
|
|
1490
|
+
return {
|
|
1491
|
+
storageType: normalized.storageType ?? null,
|
|
1492
|
+
localStorageDir: normalized.localStorageDir ?? null,
|
|
1493
|
+
filenameSuffix: normalized.filenameSuffix ?? null,
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
/** True when the definition carries any storage setting (so writing its columns can't wipe them by omission). */
|
|
1497
|
+
const hasStorageSettings = (columns) => columns.storageType != null || columns.localStorageDir != null || columns.filenameSuffix != null;
|
|
1498
|
+
/**
|
|
1499
|
+
* A model's ItemStorage properties as stored in `properties` (storage_type 'ItemStorage'), with
|
|
1500
|
+
* their storage path and extension. Reads the table, not ModelProperty instances, so it doesn't
|
|
1501
|
+
* depend on whether (or from where) the model's property instances have loaded.
|
|
1502
|
+
*/
|
|
1503
|
+
async function getItemStoragePropertiesForModel(modelNameOrType, scope = {}) {
|
|
1504
|
+
const db = BaseDb.getAppDb();
|
|
1505
|
+
if (!db || !modelNameOrType)
|
|
1506
|
+
return [];
|
|
1507
|
+
const modelRecord = await resolveModelRecordByNameOrType(modelNameOrType, scope, db);
|
|
1508
|
+
if (!modelRecord)
|
|
1509
|
+
return [];
|
|
1510
|
+
return db
|
|
1511
|
+
.select({
|
|
1512
|
+
id: properties.id,
|
|
1513
|
+
name: properties.name,
|
|
1514
|
+
localStorageDir: properties.localStorageDir,
|
|
1515
|
+
filenameSuffix: properties.filenameSuffix,
|
|
1516
|
+
})
|
|
1517
|
+
.from(properties)
|
|
1518
|
+
.where(and(eq(properties.modelId, modelRecord.id), eq(properties.storageType, 'ItemStorage')));
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Moves a renamed property's pending outbox edits (ADR 0019) to its new name, so they apply as the
|
|
1522
|
+
* property's. Its metadata rows need no move: readers match them by property_id (decision D4).
|
|
1523
|
+
* Returns the number of ops renamed.
|
|
1524
|
+
*/
|
|
1525
|
+
async function renamePendingWritesForProperty(modelName, oldPropertyName, newPropertyName) {
|
|
1526
|
+
const db = BaseDb.getAppDb();
|
|
1527
|
+
if (!db)
|
|
1528
|
+
return 0;
|
|
1529
|
+
const renamed = await db
|
|
1530
|
+
.update(pendingWrites)
|
|
1531
|
+
.set({ propertyName: newPropertyName })
|
|
1532
|
+
.where(and(eq(pendingWrites.propertyName, oldPropertyName), eq(pendingWrites.status, 'pending'), inArray(pendingWrites.seedLocalId, db.select({ localId: seeds.localId }).from(seeds).where(eq(seeds.type, easName(modelName))))))
|
|
1533
|
+
.returning({ opId: pendingWrites.opId });
|
|
1534
|
+
return renamed.length;
|
|
1535
|
+
}
|
|
1536
|
+
/** Tells React's useSchemas a schemas row was inserted (its live query may not re-run). */
|
|
1537
|
+
const broadcastSchemasInvalidate = () => {
|
|
1538
|
+
if (typeof BroadcastChannel === 'undefined')
|
|
1539
|
+
return;
|
|
1540
|
+
try {
|
|
1541
|
+
postBroadcast('seed-schemas-invalidate', {});
|
|
1542
|
+
}
|
|
1543
|
+
catch (_) { }
|
|
1544
|
+
};
|
|
1545
|
+
/** Tells React's useModels a model was linked to a schema (its live query may not re-run). */
|
|
1546
|
+
const broadcastModelsInvalidate = (schema) => {
|
|
1547
|
+
if (typeof BroadcastChannel === 'undefined')
|
|
1548
|
+
return;
|
|
1549
|
+
try {
|
|
1550
|
+
postBroadcast('seed-models-invalidate', {
|
|
1551
|
+
schemaName: schema.name ?? undefined,
|
|
1552
|
+
schemaFileId: schema.schemaFileId ?? undefined,
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
catch (_) { }
|
|
1556
|
+
};
|
|
1557
|
+
/**
|
|
1558
|
+
* Writes a schema row ({@link upsertSchemaRow} has the matching rules) and returns it.
|
|
1559
|
+
* @param schemaFileId - Optional schemaFileId from JSON file for change tracking
|
|
1560
|
+
* @param schemaData - Optional full schema content as JSON string (SchemaFileFormat)
|
|
1561
|
+
* @param isDraft - Optional flag indicating if schema is in draft state (default: false)
|
|
1562
|
+
* @param isEdited - Optional flag indicating if schema has been edited locally (default: false)
|
|
1563
|
+
* @param options.db - Write on this transaction; the caller then tells React after it commits.
|
|
1564
|
+
* Without it the write runs in its own transaction.
|
|
1565
|
+
* @param options.contentHash - The applied file's hash (applySchemaFile); written with schemaData
|
|
1566
|
+
*/
|
|
1567
|
+
const addSchemaToDb = async (schema, schemaFileId, schemaData, isDraft, isEdited, options = {}) => {
|
|
1568
|
+
if (!options.db && !BaseDb.getAppDb()) {
|
|
1569
|
+
throw new Error('Database not found');
|
|
1570
|
+
}
|
|
1571
|
+
const input = {
|
|
1572
|
+
name: schema.name,
|
|
1573
|
+
version: schema.version,
|
|
1574
|
+
createdAt: schema.createdAt,
|
|
1575
|
+
updatedAt: schema.updatedAt,
|
|
1576
|
+
schemaFileId,
|
|
1577
|
+
schemaData,
|
|
1578
|
+
isDraft,
|
|
1579
|
+
isEdited,
|
|
1580
|
+
contentHash: options.contentHash,
|
|
1581
|
+
};
|
|
1582
|
+
if (options.db) {
|
|
1583
|
+
const { inserted: _inserted, ...row } = await upsertSchemaRow(options.db, input);
|
|
1584
|
+
return row;
|
|
1585
|
+
}
|
|
1586
|
+
const { inserted, ...row } = await inDefinitionsTransaction((tx) => upsertSchemaRow(tx, input));
|
|
1587
|
+
if (inserted)
|
|
1588
|
+
broadcastSchemasInvalidate();
|
|
1589
|
+
return row;
|
|
1590
|
+
};
|
|
1591
|
+
/**
|
|
1592
|
+
* Renames a model ({@link renameModelRow}), in its own transaction unless `db` is given.
|
|
1593
|
+
* @param schemaNameOrId - Schema name or id to scope the rename (required when multiple schemas have models with the same name)
|
|
164
1594
|
*/
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
1595
|
+
const renameModelInDb = async (oldName, newName, schemaNameOrId, db) => {
|
|
1596
|
+
if (db)
|
|
1597
|
+
return renameModelRow(db, oldName, newName, schemaNameOrId);
|
|
1598
|
+
if (!BaseDb.getAppDb())
|
|
1599
|
+
throw new Error('Database not found');
|
|
1600
|
+
return inDefinitionsTransaction((tx) => renameModelRow(tx, oldName, newName, schemaNameOrId));
|
|
1601
|
+
};
|
|
171
1602
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
1603
|
+
* Writes a schema file's models, their property rows and their model_schemas links.
|
|
1604
|
+
* @param models - Models to write besides those in schemaFileData.propertyDefinitions
|
|
1605
|
+
* @param schema - Optional schema to link models to (a row, or written first when it has no id)
|
|
1606
|
+
* @param modelRenames - Optional map of old model names to new model names
|
|
1607
|
+
* @param schemaFileData - The file's ids and property definitions: { schemaFileId, modelFileIds: Map<modelName, id>, propertyFileIds: Map<modelName, Map<propertyName, id>>, propertyDefinitions }
|
|
1608
|
+
* @param options.db - Write on this transaction; the caller refreshes cached ModelProperty
|
|
1609
|
+
* instances with the returned ids once it has committed. Without it the write runs in its own
|
|
1610
|
+
* transaction and refreshes them itself.
|
|
1611
|
+
* @returns The schemaFileIds of the property rows this call changed
|
|
175
1612
|
*/
|
|
176
|
-
|
|
177
|
-
if (!
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
1613
|
+
const addModelsToDb = async (models, schema, modelRenames, schemaFileData, options = {}) => {
|
|
1614
|
+
if (!options.db) {
|
|
1615
|
+
if (!BaseDb.getAppDb())
|
|
1616
|
+
throw new Error('Database not found');
|
|
1617
|
+
const changed = await inDefinitionsTransaction((tx) => addModelsToDb(models, schema, modelRenames, schemaFileData, { db: tx }));
|
|
1618
|
+
// A cached instance is otherwise never re-read from its row (createById returns it as is)
|
|
1619
|
+
await refreshCachedProperties(changed);
|
|
1620
|
+
return changed;
|
|
1621
|
+
}
|
|
1622
|
+
const db = options.db;
|
|
1623
|
+
// Model rows and their schema links decide which schema file's nameMatch applies (ADR 0009).
|
|
1624
|
+
clearNameMatchLevels();
|
|
1625
|
+
// The schema may already be a row (with id)
|
|
1626
|
+
let schemaRecord;
|
|
1627
|
+
if (schema) {
|
|
1628
|
+
schemaRecord =
|
|
1629
|
+
schema.id !== null && schema.id !== undefined
|
|
1630
|
+
? schema
|
|
1631
|
+
: await addSchemaToDb(schema, schemaFileData?.schemaFileId, undefined, undefined, undefined, { db });
|
|
1632
|
+
}
|
|
1633
|
+
if (modelRenames && schemaRecord) {
|
|
1634
|
+
const schemaNameOrId = schemaRecord.name ?? schemaRecord.id;
|
|
1635
|
+
for (const [oldName, newName] of modelRenames.entries()) {
|
|
1636
|
+
await renameModelRow(db, oldName, newName, schemaNameOrId);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
const modelRecords = new Map();
|
|
1640
|
+
const modelProcessingErrors = [];
|
|
1641
|
+
// Property rows this call changed, so cached ModelProperty instances can be given the new values
|
|
1642
|
+
const updatedPropertyFileIds = [];
|
|
1643
|
+
const _mod_0 = await import('./arweaveContentTags-iW_HcWJA.js');
|
|
1644
|
+
const _ns_0 = _mod_0.ap;
|
|
1645
|
+
const { generateId } = _ns_0;
|
|
1646
|
+
const modelNames = [...new Set([...Object.keys(models), ...(schemaFileData?.propertyDefinitions?.keys() ?? [])])];
|
|
1647
|
+
for (const modelName of modelNames) {
|
|
1648
|
+
try {
|
|
1649
|
+
const modelFileId = schemaFileData?.modelFileIds?.get(modelName);
|
|
1650
|
+
const modelRecord = modelRecords.get(modelName) ?? (await findOrCreateModelRow(db, modelName, modelFileId, schemaRecord?.id));
|
|
1651
|
+
modelRecords.set(modelName, modelRecord);
|
|
1652
|
+
const definitions = schemaFileData?.propertyDefinitions?.get(modelName) ?? {};
|
|
1653
|
+
for (const [propertyName, propertyValues] of Object.entries(definitions)) {
|
|
1654
|
+
if (!propertyValues) {
|
|
1655
|
+
throw new Error(`Property values not found for ${propertyName}`);
|
|
1656
|
+
}
|
|
1657
|
+
// IDs are assigned before this is called; a generated one doesn't replace a stored id.
|
|
1658
|
+
const propertyFileId = schemaFileData?.propertyFileIds?.get(modelName)?.get(propertyName);
|
|
1659
|
+
let refModelId = null;
|
|
1660
|
+
const refModelName = propertyValues.ref || propertyValues.refModelName;
|
|
1661
|
+
if (refModelName) {
|
|
1662
|
+
let refModel = modelRecords.get(refModelName);
|
|
1663
|
+
if (!refModel) {
|
|
1664
|
+
const refModelFileId = schemaFileData?.modelFileIds?.get(refModelName);
|
|
1665
|
+
refModel = await findOrCreateModelRow(db, refModelName, refModelFileId, schemaRecord?.id);
|
|
1666
|
+
modelRecords.set(refModelName, refModel);
|
|
1667
|
+
}
|
|
1668
|
+
refModelId = refModel.id ?? null;
|
|
1669
|
+
}
|
|
1670
|
+
// ADR 0008: a rename needs the property's id. A name that matches no stored property by id
|
|
1671
|
+
// or name is a new property. The schema file is the source of truth for every column, and
|
|
1672
|
+
// a property edited locally keeps its row.
|
|
1673
|
+
const { row, changed } = await upsertPropertyRow(db, {
|
|
1674
|
+
modelId: modelRecord.id,
|
|
1675
|
+
name: propertyName,
|
|
1676
|
+
dataType: propertyValues.dataType,
|
|
1677
|
+
schemaFileId: propertyFileId ?? generateId(),
|
|
1678
|
+
refModelId,
|
|
1679
|
+
refValueType: propertyValues.refValueType ?? null,
|
|
1680
|
+
required: propertyValues.required ?? false,
|
|
1681
|
+
storage: propertyStorageColumns(propertyValues),
|
|
1682
|
+
isEdited: false,
|
|
1683
|
+
definition: schemaFileData?.propertyFileDefinitions?.get(modelName)?.[propertyName] ?? propertyValues,
|
|
1684
|
+
replaceDefinition: true,
|
|
1685
|
+
}, { keepStoredSchemaFileId: !propertyFileId, keepEditedRow: true });
|
|
1686
|
+
if (changed && row?.schemaFileId)
|
|
1687
|
+
updatedPropertyFileIds.push(row.schemaFileId);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
catch (error) {
|
|
1691
|
+
const errorMessage = error?.message || String(error);
|
|
1692
|
+
logger$a(`Error processing model "${modelName}": ${errorMessage}`);
|
|
1693
|
+
modelProcessingErrors.push({ modelName, error: errorMessage });
|
|
1694
|
+
// Continue processing other models even if one fails
|
|
1695
|
+
}
|
|
184
1696
|
}
|
|
185
|
-
|
|
186
|
-
}
|
|
1697
|
+
if (modelProcessingErrors.length > 0) {
|
|
1698
|
+
console.warn(`[addModelsToDb] Errors occurred while processing ${modelProcessingErrors.length} model(s):`, modelProcessingErrors.map(e => `${e.modelName}: ${e.error}`).join('; '));
|
|
1699
|
+
}
|
|
1700
|
+
if (schemaRecord?.id) {
|
|
1701
|
+
for (const [modelName, record] of modelRecords) {
|
|
1702
|
+
if (!record.id)
|
|
1703
|
+
continue;
|
|
1704
|
+
if (await linkModelToSchemaRow(db, record.id, schemaRecord.id)) {
|
|
1705
|
+
logger$a(`Linked model ${modelName} (id: ${record.id}) to schema (id: ${schemaRecord.id})`);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
return updatedPropertyFileIds;
|
|
1710
|
+
};
|
|
187
1711
|
/**
|
|
188
|
-
* The
|
|
189
|
-
*
|
|
190
|
-
*
|
|
1712
|
+
* The models linked to a schema row (model_schemas) with their property rows, in schema-file
|
|
1713
|
+
* shape: `{ [modelName]: { id, properties: { [name]: { dataType, schemaFileId, ref, ... } } } }`.
|
|
1714
|
+
* Properties carry their row's definition (validation, description, ...); model-level fields only a
|
|
1715
|
+
* schema file holds aren't here, see {@link mergeDbModelsIntoSchemaModels}.
|
|
1716
|
+
* @param schemaId - The ID of the schema record in the database
|
|
191
1717
|
*/
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return [propertyName];
|
|
197
|
-
if (propertyName.endsWith('Id')) {
|
|
198
|
-
const base = propertyName.slice(0, -2);
|
|
199
|
-
return [propertyName, base];
|
|
1718
|
+
const loadModelsFromDbForSchema = async (schemaId, db = BaseDb.getAppDb()) => {
|
|
1719
|
+
if (!db) {
|
|
1720
|
+
logger$a('Database not found, cannot load models from DB');
|
|
1721
|
+
return {};
|
|
200
1722
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
1723
|
+
try {
|
|
1724
|
+
const linked = await db
|
|
1725
|
+
.select({ modelId: modelSchemas.modelId, modelName: models.name, schemaFileId: models.schemaFileId })
|
|
1726
|
+
.from(modelSchemas)
|
|
1727
|
+
.innerJoin(models, eq(modelSchemas.modelId, models.id))
|
|
1728
|
+
.where(eq(modelSchemas.schemaId, schemaId));
|
|
1729
|
+
const modelIds = linked.map((row) => row.modelId).filter((id) => id != null);
|
|
1730
|
+
if (modelIds.length === 0)
|
|
1731
|
+
return {};
|
|
1732
|
+
const refModels = alias(models, 'ref_models');
|
|
1733
|
+
const propertyRows = await db
|
|
1734
|
+
.select({ property: properties, refName: refModels.name })
|
|
1735
|
+
.from(properties)
|
|
1736
|
+
.leftJoin(refModels, eq(properties.refModelId, refModels.id))
|
|
1737
|
+
.where(inArray(properties.modelId, modelIds));
|
|
1738
|
+
const models$1 = {};
|
|
1739
|
+
const nameById = new Map();
|
|
1740
|
+
for (const { modelId, modelName, schemaFileId } of linked) {
|
|
1741
|
+
if (!modelId || !modelName)
|
|
1742
|
+
continue;
|
|
1743
|
+
nameById.set(modelId, modelName);
|
|
1744
|
+
// The id (schemaFileId) lets Model.create find the existing row instead of adding a "Post 1".
|
|
1745
|
+
models$1[modelName] = { properties: {}, ...(schemaFileId ? { id: schemaFileId } : {}) };
|
|
1746
|
+
}
|
|
1747
|
+
for (const { property: prop, refName } of propertyRows) {
|
|
1748
|
+
const modelName = nameById.get(prop.modelId);
|
|
1749
|
+
if (!modelName)
|
|
1750
|
+
continue;
|
|
1751
|
+
// The row's definition without its row keys; `required` stays the file's
|
|
1752
|
+
const { id: _id, _dbId, name: _name, modelId: _modelId, refModelId: _refModelId, refModelName: _refModelName, required: _required, ...definition } = propertyDefinitionFromRow(prop, refName);
|
|
1753
|
+
models$1[modelName].properties[prop.name] = {
|
|
1754
|
+
...definition,
|
|
1755
|
+
...(prop.schemaFileId ? { schemaFileId: prop.schemaFileId } : {}),
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
logger$a(`Loaded ${Object.keys(models$1).length} models from database for schema ${schemaId}`);
|
|
1759
|
+
return models$1;
|
|
205
1760
|
}
|
|
206
|
-
|
|
207
|
-
}
|
|
1761
|
+
catch (error) {
|
|
1762
|
+
logger$a(`Error loading models from database for schema ${schemaId}:`, error);
|
|
1763
|
+
return {};
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
208
1766
|
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
1767
|
+
* Merges the models {@link loadModelsFromDbForSchema} read from the database into a schema's
|
|
1768
|
+
* models. The database decides which models and properties exist and their stored fields; the
|
|
1769
|
+
* schema keeps what only it holds (nameMatch, description, validation, ...), found by id first so
|
|
1770
|
+
* a property renamed in the database keeps them. A schema property whose id a database property
|
|
1771
|
+
* has is dropped under its old name.
|
|
212
1772
|
*/
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
1773
|
+
const mergeDbModelsIntoSchemaModels = (schemaModels, dbModels) => {
|
|
1774
|
+
const merged = { ...(schemaModels ?? {}) };
|
|
1775
|
+
for (const [modelName, dbModel] of Object.entries(dbModels)) {
|
|
1776
|
+
const schemaModel = schemaModels?.[modelName];
|
|
1777
|
+
if (!schemaModel) {
|
|
1778
|
+
merged[modelName] = dbModel;
|
|
1779
|
+
continue;
|
|
1780
|
+
}
|
|
1781
|
+
const schemaProps = schemaModel.properties ?? {};
|
|
1782
|
+
const dbProps = dbModel.properties ?? {};
|
|
1783
|
+
const schemaNameById = new Map();
|
|
1784
|
+
for (const [name, prop] of Object.entries(schemaProps)) {
|
|
1785
|
+
const id = prop?.id ?? prop?.schemaFileId;
|
|
1786
|
+
if (id)
|
|
1787
|
+
schemaNameById.set(id, name);
|
|
1788
|
+
}
|
|
1789
|
+
const properties = {};
|
|
1790
|
+
const matched = new Set();
|
|
1791
|
+
for (const [name, dbProp] of Object.entries(dbProps)) {
|
|
1792
|
+
const byId = dbProp?.schemaFileId ? schemaNameById.get(dbProp.schemaFileId) : undefined;
|
|
1793
|
+
const byName = schemaProps[name];
|
|
1794
|
+
const byNameId = byName?.id ?? byName?.schemaFileId;
|
|
1795
|
+
// By name only when the ids don't say it's another property.
|
|
1796
|
+
const schemaName = byId ?? (byName && !(byNameId && dbProp?.schemaFileId && byNameId !== dbProp.schemaFileId) ? name : undefined);
|
|
1797
|
+
const schemaProp = schemaName ? schemaProps[schemaName] : undefined;
|
|
1798
|
+
if (schemaName && schemaProp)
|
|
1799
|
+
matched.add(schemaName);
|
|
1800
|
+
// The database's dataType and ref replace the file's type and model.
|
|
1801
|
+
const { type, model, ...fromSchema } = schemaProp ?? {};
|
|
1802
|
+
properties[name] = {
|
|
1803
|
+
...fromSchema,
|
|
1804
|
+
...(dbProp?.dataType === undefined && type !== undefined ? { type } : {}),
|
|
1805
|
+
...(dbProp?.ref === undefined && model !== undefined ? { model } : {}),
|
|
1806
|
+
...dbProp,
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
const unmatched = Object.fromEntries(Object.entries(schemaProps).filter(([name]) => !matched.has(name)));
|
|
1810
|
+
merged[modelName] = { ...schemaModel, ...dbModel, properties: { ...unmatched, ...properties } };
|
|
220
1811
|
}
|
|
221
|
-
return
|
|
222
|
-
}
|
|
1812
|
+
return merged;
|
|
1813
|
+
};
|
|
223
1814
|
/**
|
|
224
|
-
*
|
|
1815
|
+
* Saves a ModelProperty's edit to its properties row (is_edited true) without updating the schema
|
|
1816
|
+
* file, in one transaction. A context without `required` or storage settings leaves the row's.
|
|
1817
|
+
* @param property - The ModelPropertyMachineContext with updated values
|
|
225
1818
|
*/
|
|
226
|
-
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
1819
|
+
const savePropertyToDb = async (property) => {
|
|
1820
|
+
if (!BaseDb.getAppDb()) {
|
|
1821
|
+
throw new Error('Database not found');
|
|
1822
|
+
}
|
|
1823
|
+
const name = property.name;
|
|
1824
|
+
if (!name) {
|
|
1825
|
+
throw new Error('Property name is required');
|
|
1826
|
+
}
|
|
1827
|
+
const schemaFileId = property._propertyFileId || (typeof property.id === 'string' ? property.id : undefined);
|
|
1828
|
+
const storage = propertyStorageColumns(property);
|
|
1829
|
+
await inDefinitionsTransaction(async (tx) => {
|
|
1830
|
+
// modelId, then the model of the property row (by its schemaFileId), then modelName in the
|
|
1831
|
+
// property's schema. context.modelName can also hold the model's schemaFileId.
|
|
1832
|
+
const modelRecord = await resolveModelRecord(property.modelName, {
|
|
1833
|
+
modelId: typeof property.modelId === 'number' ? property.modelId : undefined,
|
|
1834
|
+
propertyFileId: schemaFileId,
|
|
1835
|
+
modelFileId: property.modelName,
|
|
1836
|
+
schemaName: property._schemaName,
|
|
1837
|
+
}, tx);
|
|
1838
|
+
if (!modelRecord) {
|
|
1839
|
+
throw new Error(`Model not found in database (modelId=${property.modelId}, modelName=${property.modelName})`);
|
|
1840
|
+
}
|
|
1841
|
+
await upsertPropertyRow(tx, {
|
|
1842
|
+
modelId: modelRecord.id,
|
|
1843
|
+
name,
|
|
1844
|
+
dataType: property.dataType,
|
|
1845
|
+
schemaFileId,
|
|
1846
|
+
previousName: property._originalValues?.name,
|
|
1847
|
+
refModelName: property.refModelName,
|
|
1848
|
+
refModelId: property.refModelName ? undefined : (property.refModelId ?? null),
|
|
1849
|
+
refValueType: property.refValueType ?? null,
|
|
1850
|
+
required: property.required,
|
|
1851
|
+
storage: hasStorageSettings(storage) ? storage : undefined,
|
|
1852
|
+
isEdited: true,
|
|
1853
|
+
definition: property,
|
|
1854
|
+
});
|
|
1855
|
+
});
|
|
1856
|
+
logger$a(`Saved property ${property.modelName}:${property.name}`);
|
|
1857
|
+
};
|
|
1858
|
+
function parseAddressConfig(value) {
|
|
1859
|
+
if (!value)
|
|
1860
|
+
return null;
|
|
1861
|
+
try {
|
|
1862
|
+
const parsed = JSON.parse(value);
|
|
1863
|
+
if (Array.isArray(parsed)) {
|
|
1864
|
+
return normalizeAddressConfig(parsed);
|
|
1865
|
+
}
|
|
1866
|
+
if (parsed && typeof parsed === 'object' && Array.isArray(parsed.owned)) {
|
|
1867
|
+
return normalizeAddressConfig({
|
|
1868
|
+
owned: parsed.owned,
|
|
1869
|
+
watched: Array.isArray(parsed.watched) ? parsed.watched : [],
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1874
|
+
catch {
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
232
1877
|
}
|
|
1878
|
+
const getOwnedAddressesFromDb = async () => {
|
|
1879
|
+
const config = await getAddressConfigFromDb();
|
|
1880
|
+
return config?.owned ?? [];
|
|
1881
|
+
};
|
|
1882
|
+
const getWatchedAddressesFromDb = async () => {
|
|
1883
|
+
const config = await getAddressConfigFromDb();
|
|
1884
|
+
return config?.watched ?? [];
|
|
1885
|
+
};
|
|
233
1886
|
/**
|
|
234
|
-
*
|
|
1887
|
+
* Returns persisted owned or watched addresses for list filters.
|
|
1888
|
+
* Does not include `getAdditionalSyncAddresses` (those are extra EAS indexers).
|
|
235
1889
|
*/
|
|
236
|
-
|
|
237
|
-
if (
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
1890
|
+
const getAddressesForItemsFilter = async (addressFilter) => {
|
|
1891
|
+
if (addressFilter === 'owned') {
|
|
1892
|
+
return getOwnedAddressesFromDb();
|
|
1893
|
+
}
|
|
1894
|
+
return getWatchedAddressesFromDb();
|
|
1895
|
+
};
|
|
243
1896
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* - List of relations: its `<name><Model>Ids` storage name (`author_identity_ids`).
|
|
247
|
-
* - Single Relation, Image, File or Html: its schema key (`author`), never the local `<name>Id`
|
|
248
|
-
* storage name. The key is `propertyDef.name` when known, else `propertyName` without its `Id`.
|
|
249
|
-
* - Anything else: the property name.
|
|
1897
|
+
* Returns owned + watched addresses. Use for EAS sync and file download.
|
|
250
1898
|
*/
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
if (
|
|
254
|
-
return
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1899
|
+
const getAllAddressesFromDb = async () => {
|
|
1900
|
+
const config = await getAddressConfigFromDb();
|
|
1901
|
+
if (!config)
|
|
1902
|
+
return [];
|
|
1903
|
+
return [...config.owned, ...config.watched];
|
|
1904
|
+
};
|
|
1905
|
+
async function getAddressConfigFromDb() {
|
|
1906
|
+
// Before init opens the database (or with no init) there are no stored addresses to read
|
|
1907
|
+
const appDb = await whenDbReady();
|
|
1908
|
+
if (!appDb)
|
|
1909
|
+
return null;
|
|
1910
|
+
const appStatesRecords = await appDb
|
|
1911
|
+
.select()
|
|
1912
|
+
.from(appState)
|
|
1913
|
+
.where(eq(appState.key, 'addresses'))
|
|
1914
|
+
.limit(1);
|
|
1915
|
+
if (!appStatesRecords || appStatesRecords.length === 0) {
|
|
1916
|
+
return null;
|
|
261
1917
|
}
|
|
262
|
-
|
|
1918
|
+
const addressArrayString = appStatesRecords[0].value;
|
|
1919
|
+
return parseAddressConfig(addressArrayString);
|
|
263
1920
|
}
|
|
264
1921
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
1922
|
+
* Writes a runtime-created model: its models row (is_edited true when new), its model_schemas link
|
|
1923
|
+
* and the properties given, in one transaction. Returns the model's row id.
|
|
1924
|
+
* @param modelFileId - The model file ID (schema_file_id)
|
|
1925
|
+
* @param data - Model data including modelName, schemaId, and optional properties
|
|
269
1926
|
*/
|
|
270
|
-
function
|
|
271
|
-
if (!
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
1927
|
+
async function writeModelToDb(modelFileId, data) {
|
|
1928
|
+
if (!BaseDb.getAppDb())
|
|
1929
|
+
throw new Error('Database not available');
|
|
1930
|
+
if (!data.schemaId || !Number.isInteger(data.schemaId) || data.schemaId <= 0) {
|
|
1931
|
+
throw new Error(`Invalid schemaId: ${data.schemaId}. Schema record must exist before creating join entry.`);
|
|
1932
|
+
}
|
|
1933
|
+
const _mod_1 = await import('./arweaveContentTags-iW_HcWJA.js');
|
|
1934
|
+
const _ns_1 = _mod_1.ap;
|
|
1935
|
+
const { generateId } = _ns_1;
|
|
1936
|
+
const { linked, schema, modelId } = await inDefinitionsTransaction(async (tx) => {
|
|
1937
|
+
const [schema] = await tx
|
|
1938
|
+
.select({ id: schemas.id, name: schemas.name, schemaFileId: schemas.schemaFileId })
|
|
1939
|
+
.from(schemas)
|
|
1940
|
+
.where(eq(schemas.id, data.schemaId))
|
|
1941
|
+
.limit(1);
|
|
1942
|
+
if (!schema) {
|
|
1943
|
+
throw new Error(`Schema with id ${data.schemaId} does not exist in database. Cannot create join record.`);
|
|
1944
|
+
}
|
|
1945
|
+
const model = await upsertModelRow(tx, { name: data.modelName, schemaFileId: modelFileId, isEdited: true });
|
|
1946
|
+
const linked = await linkModelToSchemaRow(tx, model.id, data.schemaId);
|
|
1947
|
+
for (const [propName, propData] of Object.entries(data.properties ?? {})) {
|
|
1948
|
+
const refModelName = propData.refModelName || propData.ref;
|
|
1949
|
+
const storage = propertyStorageColumns(propData);
|
|
1950
|
+
// A stored row keeps its id; the given or a generated one is for a new row.
|
|
1951
|
+
await upsertPropertyRow(tx, {
|
|
1952
|
+
modelId: model.id,
|
|
1953
|
+
name: propName,
|
|
1954
|
+
dataType: propData.dataType,
|
|
1955
|
+
schemaFileId: propData.id ?? generateId(),
|
|
1956
|
+
refModelName,
|
|
1957
|
+
refModelId: refModelName ? undefined : (propData.refModelId ?? null),
|
|
1958
|
+
refValueType: propData.refValueType ?? null,
|
|
1959
|
+
required: propData.required,
|
|
1960
|
+
storage: hasStorageSettings(storage) ? storage : undefined,
|
|
1961
|
+
definition: propData,
|
|
1962
|
+
}, { keepStoredSchemaFileId: true });
|
|
1963
|
+
}
|
|
1964
|
+
return { linked, schema, modelId: model.id };
|
|
1965
|
+
});
|
|
1966
|
+
if (linked)
|
|
1967
|
+
broadcastModelsInvalidate(schema);
|
|
1968
|
+
logger$a(`Wrote model ${data.modelName} (${modelFileId}) to database`);
|
|
1969
|
+
return modelId;
|
|
278
1970
|
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Writes a property's row by its file id ({@link upsertPropertyRow}), in its own transaction.
|
|
1973
|
+
* `required` and storage settings the data leaves out keep the row's.
|
|
1974
|
+
* @param propertyFileId - The property file ID (schema_file_id)
|
|
1975
|
+
* @param data - Property data including modelId, name, dataType, and other property fields
|
|
1976
|
+
* @param isEdited - Optional flag indicating if property has been edited locally. Left unset, an update
|
|
1977
|
+
* keeps the row's flag (a load or creation write must not clear an unsaved edit) and an insert stores false.
|
|
1978
|
+
*/
|
|
1979
|
+
async function writePropertyToDb(propertyFileId, data, isEdited) {
|
|
1980
|
+
if (!BaseDb.getAppDb())
|
|
1981
|
+
throw new Error('Database not available');
|
|
1982
|
+
const refModelName = data.refModelName || data.ref;
|
|
1983
|
+
const storage = propertyStorageColumns(data);
|
|
1984
|
+
await inDefinitionsTransaction((tx) => upsertPropertyRow(tx, {
|
|
1985
|
+
modelId: data.modelId,
|
|
1986
|
+
name: data.name,
|
|
1987
|
+
dataType: data.dataType,
|
|
1988
|
+
schemaFileId: propertyFileId,
|
|
1989
|
+
refModelName,
|
|
1990
|
+
refModelId: refModelName ? undefined : (data.refModelId ?? null),
|
|
1991
|
+
refValueType: data.refValueType ?? null,
|
|
1992
|
+
required: data.required,
|
|
1993
|
+
storage: hasStorageSettings(storage) ? storage : undefined,
|
|
1994
|
+
isEdited,
|
|
1995
|
+
definition: data,
|
|
1996
|
+
}));
|
|
1997
|
+
logger$a(`Wrote property ${data.name} (${propertyFileId})`);
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
var db = /*#__PURE__*/Object.freeze({
|
|
2001
|
+
__proto__: null,
|
|
2002
|
+
addModelsToDb: addModelsToDb,
|
|
2003
|
+
addSchemaToDb: addSchemaToDb,
|
|
2004
|
+
broadcastSchemasInvalidate: broadcastSchemasInvalidate,
|
|
2005
|
+
getAddressesForItemsFilter: getAddressesForItemsFilter,
|
|
2006
|
+
getAllAddressesFromDb: getAllAddressesFromDb,
|
|
2007
|
+
getItemStoragePropertiesForModel: getItemStoragePropertiesForModel,
|
|
2008
|
+
getOwnedAddressesFromDb: getOwnedAddressesFromDb,
|
|
2009
|
+
getWatchedAddressesFromDb: getWatchedAddressesFromDb,
|
|
2010
|
+
loadModelsFromDbForSchema: loadModelsFromDbForSchema,
|
|
2011
|
+
mergeDbModelsIntoSchemaModels: mergeDbModelsIntoSchemaModels,
|
|
2012
|
+
propertyStorageColumns: propertyStorageColumns,
|
|
2013
|
+
renameModelInDb: renameModelInDb,
|
|
2014
|
+
renamePendingWritesForProperty: renamePendingWritesForProperty,
|
|
2015
|
+
savePropertyToDb: savePropertyToDb,
|
|
2016
|
+
writeModelToDb: writeModelToDb,
|
|
2017
|
+
writePropertyToDb: writePropertyToDb
|
|
2018
|
+
});
|
|
279
2019
|
|
|
280
2020
|
/**
|
|
281
2021
|
* Proxy [[GetOwnProperty]] that stays compatible with the target.
|
|
@@ -896,7 +2636,6 @@ var metadataProperty = /*#__PURE__*/Object.freeze({
|
|
|
896
2636
|
METADATA_PROPERTY_GROUP_KEY_SQL: METADATA_PROPERTY_GROUP_KEY_SQL,
|
|
897
2637
|
findMetadataPropertyByName: findMetadataPropertyByName,
|
|
898
2638
|
matchMetadataProperty: matchMetadataProperty,
|
|
899
|
-
metadataPropertyGroupKeys: metadataPropertyGroupKeys,
|
|
900
2639
|
metadataPropertyKeyColumn: metadataPropertyKeyColumn,
|
|
901
2640
|
metadataPropertyNameForms: metadataPropertyNameForms,
|
|
902
2641
|
metadataRowsOfProperty: metadataRowsOfProperty,
|
|
@@ -2235,9 +3974,7 @@ const getItemsData = async ({ modelName, modelFileId, schemaName, deleted, inclu
|
|
|
2235
3974
|
conditions.push(eq(seeds.type, easName(modelName)));
|
|
2236
3975
|
}
|
|
2237
3976
|
if (!modelFileId && schemaName && modelName) {
|
|
2238
|
-
const
|
|
2239
|
-
const _ns_0 = _mod_0.bh;
|
|
2240
|
-
const { resolveModelRecord } = _ns_0;
|
|
3977
|
+
const { resolveModelRecord } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
2241
3978
|
modelFileId = (await resolveModelRecord(modelName, { schemaName }))?.schemaFileId ?? undefined;
|
|
2242
3979
|
if (!modelFileId)
|
|
2243
3980
|
return []; // that schema has no such model
|
|
@@ -2324,6 +4061,33 @@ async function getLatestVersionRow(seedLocalId) {
|
|
|
2324
4061
|
return rows[0] ?? null;
|
|
2325
4062
|
}
|
|
2326
4063
|
|
|
4064
|
+
const logger$6 = debug('seedSdk:helpers:propertySchemaUid');
|
|
4065
|
+
/**
|
|
4066
|
+
* UID of the EAS schema a property publishes under, or undefined when EAS has none, the property's
|
|
4067
|
+
* type has no EAS type, or the lookup fails. Reads `getEasSchemaUidForExactDefinition`, which
|
|
4068
|
+
* answers with a UID publish registered in this session before the indexer has it, and caches
|
|
4069
|
+
* found UIDs and misses, so creating an item and then saving its properties asks EAS once per
|
|
4070
|
+
* definition. May call EAS, so run it before a transaction, not inside one. Callers store the UID
|
|
4071
|
+
* on the metadata row; publish resolves schemas itself, so a missing one costs nothing there.
|
|
4072
|
+
*/
|
|
4073
|
+
async function lookupPropertySchemaUid(propertyName, propertyDef) {
|
|
4074
|
+
const definition = easSchemaDefinitionForProperty(propertyName, propertyDef);
|
|
4075
|
+
if (!definition)
|
|
4076
|
+
return undefined;
|
|
4077
|
+
try {
|
|
4078
|
+
return await getEasSchemaUidForExactDefinition(definition);
|
|
4079
|
+
}
|
|
4080
|
+
catch (error) {
|
|
4081
|
+
logger$6(`EAS schema lookup for "${definition}" failed (offline?): ${error}`);
|
|
4082
|
+
return undefined;
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
var propertySchemaUid = /*#__PURE__*/Object.freeze({
|
|
4087
|
+
__proto__: null,
|
|
4088
|
+
lookupPropertySchemaUid: lookupPropertySchemaUid
|
|
4089
|
+
});
|
|
4090
|
+
|
|
2327
4091
|
const initialize = fromCallback(({ sendBack, input: { context } }) => {
|
|
2328
4092
|
const { isRelation, propertyName, storageTransactionId, propertyRecordSchema, } = context;
|
|
2329
4093
|
let { schemaUid } = context;
|
|
@@ -2334,21 +4098,10 @@ const initialize = fromCallback(({ sendBack, input: { context } }) => {
|
|
|
2334
4098
|
(!propertyRecordSchema ||
|
|
2335
4099
|
!propertyRecordSchema.storageType ||
|
|
2336
4100
|
propertyRecordSchema.storageType !== 'ItemStorage')) {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
schemaUid = await getEasSchemaUidForSchemaDefinition({ schemaText });
|
|
2342
|
-
if (schemaUid) {
|
|
2343
|
-
sendBack({ type: 'updateContext', schemaUid });
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
catch (error) {
|
|
2347
|
-
// If schema fetch fails, continue without schemaUid - it's not required for local metadata
|
|
2348
|
-
// Log error in development but don't throw
|
|
2349
|
-
if (process.env.NODE_ENV === 'development') {
|
|
2350
|
-
console.warn(`Failed to fetch schemaUid for property ${propertyName}:`, error);
|
|
2351
|
-
}
|
|
4101
|
+
// Undefined when the lookup fails: local metadata doesn't need it.
|
|
4102
|
+
schemaUid = await lookupPropertySchemaUid(propertyName, propertyRecordSchema);
|
|
4103
|
+
if (schemaUid) {
|
|
4104
|
+
sendBack({ type: 'updateContext', schemaUid });
|
|
2352
4105
|
}
|
|
2353
4106
|
}
|
|
2354
4107
|
if (isRelation) {
|
|
@@ -2483,7 +4236,7 @@ const getRelationValueData = async (propertyValue) => {
|
|
|
2483
4236
|
|
|
2484
4237
|
// Dynamic import to break circular dependency: schema/index -> ... -> resolveRelatedValue -> schema/index
|
|
2485
4238
|
// import { ModelPropertyDataTypes } from '@/schema'
|
|
2486
|
-
const logger$
|
|
4239
|
+
const logger$5 = debug('seedSdk:property:actors:resolveRelatedValue');
|
|
2487
4240
|
const storageTransactionIdToContentUrl = new Map();
|
|
2488
4241
|
const refResolvedValueToContentUrl = new Map();
|
|
2489
4242
|
const seedUidToContentUrl = new Map();
|
|
@@ -2491,14 +4244,14 @@ const resolveRelatedValue = fromCallback(({ sendBack, input: { context } }) => {
|
|
|
2491
4244
|
const { isRelation, propertyValue, propertyName, propertyRecordSchema, populatedFromDb, } = context;
|
|
2492
4245
|
const _resolveRelatedValue = async () => {
|
|
2493
4246
|
// Use dynamic import to break circular dependency
|
|
2494
|
-
const schemaMod = await import('./index-
|
|
4247
|
+
const schemaMod = await import('./index-CmNqUKQN.js');
|
|
2495
4248
|
const { ModelPropertyDataTypes } = schemaMod;
|
|
2496
4249
|
if (!propertyValue || !isRelation || populatedFromDb) {
|
|
2497
4250
|
return;
|
|
2498
4251
|
}
|
|
2499
4252
|
if (Array.isArray(propertyValue)) {
|
|
2500
4253
|
// TODO: Handle array of seedUids
|
|
2501
|
-
logger$
|
|
4254
|
+
logger$5(`${propertyName} value is an array of seedUids`);
|
|
2502
4255
|
return;
|
|
2503
4256
|
}
|
|
2504
4257
|
if (typeof propertyValue === 'object') {
|
|
@@ -2510,7 +4263,7 @@ const resolveRelatedValue = fromCallback(({ sendBack, input: { context } }) => {
|
|
|
2510
4263
|
parsedValue = JSON.parse(relationKey);
|
|
2511
4264
|
}
|
|
2512
4265
|
catch {
|
|
2513
|
-
logger$
|
|
4266
|
+
logger$5(`${propertyName} value is not a JSON string.`);
|
|
2514
4267
|
}
|
|
2515
4268
|
if (!parsedValue && seedUidToContentUrl.has(relationKey)) {
|
|
2516
4269
|
const contentUrl = seedUidToContentUrl.get(relationKey);
|
|
@@ -2525,7 +4278,7 @@ const resolveRelatedValue = fromCallback(({ sendBack, input: { context } }) => {
|
|
|
2525
4278
|
return true;
|
|
2526
4279
|
}
|
|
2527
4280
|
if (Array.isArray(parsedValue)) {
|
|
2528
|
-
logger$
|
|
4281
|
+
logger$5(`${propertyName} value is a stringified array of seedUids`);
|
|
2529
4282
|
return;
|
|
2530
4283
|
}
|
|
2531
4284
|
const storageTransactionId = await getStorageTransactionIdForSeedUid(relationKey);
|
|
@@ -2823,6 +4576,33 @@ function parseListPropertyValueFromStorage(raw) {
|
|
|
2823
4576
|
return trimmed.split(',').map((s) => s.trim()).filter(Boolean);
|
|
2824
4577
|
}
|
|
2825
4578
|
|
|
4579
|
+
/** Subdirectory of the files root that holds a storage seed's file, by its data type. */
|
|
4580
|
+
const getStorageSubdirForDataType = (dataType) => {
|
|
4581
|
+
switch (normalizeDataType(dataType ?? undefined)) {
|
|
4582
|
+
case 'Image':
|
|
4583
|
+
return 'images';
|
|
4584
|
+
case 'Html':
|
|
4585
|
+
return 'html';
|
|
4586
|
+
case 'Json':
|
|
4587
|
+
return 'json';
|
|
4588
|
+
case 'File':
|
|
4589
|
+
default:
|
|
4590
|
+
// Unknown types are generic files; `images/` is only for what the SDK knows is an image.
|
|
4591
|
+
return 'files';
|
|
4592
|
+
}
|
|
4593
|
+
};
|
|
4594
|
+
/**
|
|
4595
|
+
* Subdirectory (relative to the files root, no leading slash) for an ItemStorage property's file:
|
|
4596
|
+
* its model's `storage.path` (`localStorageDir`), else `files`.
|
|
4597
|
+
*/
|
|
4598
|
+
const getItemStorageSubdir = (localStorageDir) => (localStorageDir ?? '').replace(/^\/+/, '').replace(/\/+$/, '') || 'files';
|
|
4599
|
+
/**
|
|
4600
|
+
* File name a local save gives an ItemStorage property: `<seedUid or seedLocalId><extension>`
|
|
4601
|
+
* (see saveItemStorage). Downloaded content is named after its transaction instead
|
|
4602
|
+
* (`<txId><extension>`, see syncDbWithEas), which is what the metadata row's refResolvedValue says.
|
|
4603
|
+
*/
|
|
4604
|
+
const getItemStorageLocalFileName = (seedId, filenameSuffix) => `${seedId}${filenameSuffix ?? ''}`;
|
|
4605
|
+
|
|
2826
4606
|
/**
|
|
2827
4607
|
* Name of a new Image or File seed's file in the shared `images/` or `files/` folder. Uploads keep
|
|
2828
4608
|
* their own name (and so their extension, which publish reads for the content type), prefixed with
|
|
@@ -2946,6 +4726,52 @@ function storageFileName(kind, seedLocalId, content) {
|
|
|
2946
4726
|
return extension ? `${seedLocalId}.${extension}` : seedLocalId;
|
|
2947
4727
|
}
|
|
2948
4728
|
|
|
4729
|
+
const isFile = async (path) => {
|
|
4730
|
+
try {
|
|
4731
|
+
const fs = await BaseFileManager.getFs();
|
|
4732
|
+
return (await fs.promises.stat(path)).isFile();
|
|
4733
|
+
}
|
|
4734
|
+
catch {
|
|
4735
|
+
return false;
|
|
4736
|
+
}
|
|
4737
|
+
};
|
|
4738
|
+
/**
|
|
4739
|
+
* A file in `subdir` (relative to the files root) for a stored reference: the file of that exact
|
|
4740
|
+
* name, else the first whose name contains one of `needles` (a downloaded file is named after its
|
|
4741
|
+
* transaction id, sometimes with an extension). Folders (an image's size variants) don't count.
|
|
4742
|
+
*/
|
|
4743
|
+
async function findFileInDir(subdir, needles) {
|
|
4744
|
+
const wanted = needles.filter((n) => !!n);
|
|
4745
|
+
if (wanted.length === 0)
|
|
4746
|
+
return undefined;
|
|
4747
|
+
for (const needle of wanted) {
|
|
4748
|
+
const exact = BaseFileManager.getFilesPath(subdir, needle);
|
|
4749
|
+
if ((await BaseFileManager.pathExists(exact)) && (await isFile(exact)))
|
|
4750
|
+
return exact;
|
|
4751
|
+
}
|
|
4752
|
+
const dirPath = BaseFileManager.getFilesPath(subdir);
|
|
4753
|
+
if (!(await BaseFileManager.pathExists(dirPath)))
|
|
4754
|
+
return undefined;
|
|
4755
|
+
const fs = await BaseFileManager.getFs();
|
|
4756
|
+
const path = BaseFileManager.getPathModule();
|
|
4757
|
+
const names = await fs.promises.readdir(dirPath);
|
|
4758
|
+
for (const needle of wanted) {
|
|
4759
|
+
for (const name of names) {
|
|
4760
|
+
if (!path.basename(name).includes(needle))
|
|
4761
|
+
continue;
|
|
4762
|
+
const candidate = BaseFileManager.getFilesPath(subdir, name);
|
|
4763
|
+
if (await isFile(candidate))
|
|
4764
|
+
return candidate;
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
return undefined;
|
|
4768
|
+
}
|
|
4769
|
+
|
|
4770
|
+
var findFile = /*#__PURE__*/Object.freeze({
|
|
4771
|
+
__proto__: null,
|
|
4772
|
+
findFileInDir: findFileInDir
|
|
4773
|
+
});
|
|
4774
|
+
|
|
2949
4775
|
/** Which kind of file holds a property's value, from its definition, else the row's `refSeedType`. */
|
|
2950
4776
|
function storedValueKind(def, row) {
|
|
2951
4777
|
if (def?.storageType === 'ItemStorage')
|
|
@@ -3005,7 +4831,7 @@ async function resolveStorageRef(kind, row) {
|
|
|
3005
4831
|
}
|
|
3006
4832
|
if (kind === 'html') {
|
|
3007
4833
|
if (!out.refResolvedValue && out.propertyValue) {
|
|
3008
|
-
const { resolveHtmlStorageSeedLocalId } = await import('./readHtmlBodyForStorageSeed-
|
|
4834
|
+
const { resolveHtmlStorageSeedLocalId } = await import('./readHtmlBodyForStorageSeed-Cva1DPzE.js');
|
|
3009
4835
|
const localId = await resolveHtmlStorageSeedLocalId(out.propertyValue);
|
|
3010
4836
|
if (localId)
|
|
3011
4837
|
out.refResolvedValue = `${localId}.html`;
|
|
@@ -3054,7 +4880,7 @@ async function resolveStorageDisplay(kind, row, options = {}) {
|
|
|
3054
4880
|
}
|
|
3055
4881
|
}
|
|
3056
4882
|
if (!renderValue && options.fetch && ref.propertyValue) {
|
|
3057
|
-
const { readHtmlBodyForStorageSeedPropertyValue } = await import('./readHtmlBodyForStorageSeed-
|
|
4883
|
+
const { readHtmlBodyForStorageSeedPropertyValue } = await import('./readHtmlBodyForStorageSeed-Cva1DPzE.js');
|
|
3058
4884
|
renderValue = await readHtmlBodyForStorageSeedPropertyValue(ref.propertyValue);
|
|
3059
4885
|
}
|
|
3060
4886
|
return { ref, filePath, renderValue };
|
|
@@ -3066,16 +4892,12 @@ async function resolveStorageDisplay(kind, row, options = {}) {
|
|
|
3066
4892
|
: BaseFileManager.pathExists(BaseFileManager.getFilesPath(dir, name)).then((exists) => exists ? BaseFileManager.getFilesPath(dir, name) : undefined);
|
|
3067
4893
|
// ensureImageLocal also makes the smallest size variant when the original is already there
|
|
3068
4894
|
if (kind === 'image' && options.fetch) {
|
|
3069
|
-
const
|
|
3070
|
-
const _ns_1 = _mod_1.bo;
|
|
3071
|
-
const { ensureImageLocal } = _ns_1;
|
|
4895
|
+
const { ensureImageLocal } = await import('./ensureImageLocal-B6suz6FC.js');
|
|
3072
4896
|
await ensureImageLocal({ transactionId: name, fileName: name, widths: [ImageSize.EXTRA_SMALL] });
|
|
3073
4897
|
}
|
|
3074
4898
|
let filePath = await find();
|
|
3075
4899
|
if (!filePath && kind === 'file' && options.fetch) {
|
|
3076
|
-
const
|
|
3077
|
-
const _ns_2 = _mod_2.bm;
|
|
3078
|
-
const { downloadTransactionIdWithDedupe } = _ns_2;
|
|
4900
|
+
const { downloadTransactionIdWithDedupe } = await import('./download-DHWUPLlK.js');
|
|
3079
4901
|
await downloadTransactionIdWithDedupe(name);
|
|
3080
4902
|
filePath = await find();
|
|
3081
4903
|
}
|
|
@@ -3094,7 +4916,7 @@ var resolve = /*#__PURE__*/Object.freeze({
|
|
|
3094
4916
|
storedValueKind: storedValueKind
|
|
3095
4917
|
});
|
|
3096
4918
|
|
|
3097
|
-
const logger$
|
|
4919
|
+
const logger$4 = debug('seedSdk:property:actors:hydrateFromDb');
|
|
3098
4920
|
/**
|
|
3099
4921
|
* Re-reads a loaded property (the `rehydrateFromDb` event): its current row with the pending
|
|
3100
4922
|
* outbox value over it (currentPropertyRow), its recorded conflict, and an Image, File or Html
|
|
@@ -3147,7 +4969,7 @@ const hydrateFromDb = fromCallback(({ sendBack, input: { context } }) => {
|
|
|
3147
4969
|
}
|
|
3148
4970
|
}
|
|
3149
4971
|
catch (e) {
|
|
3150
|
-
logger$
|
|
4972
|
+
logger$4(`[hydrateFromDb] ${kind} file resolution error`, e);
|
|
3151
4973
|
}
|
|
3152
4974
|
}
|
|
3153
4975
|
const propertyValue = propertyRecordSchema && normalizedDataType === 'List' && typeof row.propertyValue === 'string'
|
|
@@ -4070,14 +5892,14 @@ const evictionLog = {
|
|
|
4070
5892
|
},
|
|
4071
5893
|
};
|
|
4072
5894
|
|
|
4073
|
-
const logger$
|
|
5895
|
+
const logger$3 = debug('seedSdk:itemProperty:loadProperty');
|
|
4074
5896
|
/**
|
|
4075
5897
|
* Load a property's value and definition: its current row (with a pending outbox value over it,
|
|
4076
5898
|
* ADR 0019), else only its definition, for a property with no row yet.
|
|
4077
5899
|
*/
|
|
4078
5900
|
async function loadProperty(context) {
|
|
4079
5901
|
const { seedLocalId, seedUid, propertyName, versionLocalId } = context;
|
|
4080
|
-
logger$
|
|
5902
|
+
logger$3(`loadOrCreateProperty called for propertyName: ${propertyName}, seedLocalId: ${seedLocalId}, seedUid: ${seedUid}`);
|
|
4081
5903
|
if (!seedLocalId && !seedUid) {
|
|
4082
5904
|
throw new Error('seedLocalId or seedUid is required');
|
|
4083
5905
|
}
|
|
@@ -4106,9 +5928,7 @@ async function loadProperty(context) {
|
|
|
4106
5928
|
const modelNameForNew = context.modelName;
|
|
4107
5929
|
if (modelNameForNew) {
|
|
4108
5930
|
try {
|
|
4109
|
-
const
|
|
4110
|
-
const _ns_3 = _mod_3.bh;
|
|
4111
|
-
const { resolveItemModelFileId, modelNameForType } = _ns_3;
|
|
5931
|
+
const { resolveItemModelFileId, modelNameForType } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
4112
5932
|
const itemModelFileId = await resolveItemModelFileId({
|
|
4113
5933
|
modelFileId: context.modelFileId,
|
|
4114
5934
|
seedLocalId,
|
|
@@ -4123,10 +5943,10 @@ async function loadProperty(context) {
|
|
|
4123
5943
|
});
|
|
4124
5944
|
}
|
|
4125
5945
|
catch (error) {
|
|
4126
|
-
logger$
|
|
5946
|
+
logger$3(`Metadata not found: no definition for propertyName "${propertyName}": ${error}`);
|
|
4127
5947
|
}
|
|
4128
5948
|
}
|
|
4129
|
-
logger$
|
|
5949
|
+
logger$3(`Metadata not found in database for propertyName: ${propertyName}, seedLocalId: ${seedLocalId}`);
|
|
4130
5950
|
return {
|
|
4131
5951
|
propertyName,
|
|
4132
5952
|
propertyValue: context.propertyValue,
|
|
@@ -4146,9 +5966,7 @@ async function loadProperty(context) {
|
|
|
4146
5966
|
// Load propertyRecordSchema from database to make ItemProperty independent from Model
|
|
4147
5967
|
let propertyRecordSchema = undefined;
|
|
4148
5968
|
// Model names are only unique per schema: pin lookups to the item's own model row.
|
|
4149
|
-
const
|
|
4150
|
-
const _ns_4 = _mod_4.bh;
|
|
4151
|
-
const { resolveItemModelFileId, modelNameForType } = _ns_4;
|
|
5969
|
+
const { resolveItemModelFileId, modelNameForType } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
4152
5970
|
const modelFileId = await resolveItemModelFileId({
|
|
4153
5971
|
modelFileId: context.modelFileId,
|
|
4154
5972
|
seedLocalId: seedLocalId ?? metadataRecord.seedLocalId,
|
|
@@ -4172,7 +5990,7 @@ async function loadProperty(context) {
|
|
|
4172
5990
|
timing.mark('model schema');
|
|
4173
5991
|
}
|
|
4174
5992
|
catch (error) {
|
|
4175
|
-
logger$
|
|
5993
|
+
logger$3(`Error loading the definition of "${propertyName}": ${error}`);
|
|
4176
5994
|
// Continue without propertyRecordSchema
|
|
4177
5995
|
}
|
|
4178
5996
|
// A file's display: a session URL for an Image or File, the text of an Html body, fetched when
|
|
@@ -4204,7 +6022,7 @@ async function loadProperty(context) {
|
|
|
4204
6022
|
}
|
|
4205
6023
|
}
|
|
4206
6024
|
catch (e) {
|
|
4207
|
-
logger$
|
|
6025
|
+
logger$3(`Failed to resolve the ${kind} file for ${propertyName}: ${e}`);
|
|
4208
6026
|
}
|
|
4209
6027
|
}
|
|
4210
6028
|
let propertyValueOut = metadataRecord.propertyValue || undefined;
|
|
@@ -4251,13 +6069,13 @@ async function loadPropertyDefinition(db, { propertyId, modelName, modelFileId,
|
|
|
4251
6069
|
}
|
|
4252
6070
|
if (!modelName)
|
|
4253
6071
|
return undefined;
|
|
4254
|
-
const { loadModelPropertyRecordSchemas } = await import('./modelPropertyRecordSchemas-
|
|
6072
|
+
const { loadModelPropertyRecordSchemas } = await import('./modelPropertyRecordSchemas-CEac3FB8.js');
|
|
4255
6073
|
const definition = (await loadModelPropertyRecordSchemas(modelName, modelFileId, db)).get(propertyName);
|
|
4256
6074
|
if (definition)
|
|
4257
6075
|
return definition;
|
|
4258
|
-
const
|
|
4259
|
-
const
|
|
4260
|
-
const { Model } =
|
|
6076
|
+
const _mod_2 = await import('./publishErrors-DZXDX3B6.js');
|
|
6077
|
+
const _ns_2 = _mod_2.am;
|
|
6078
|
+
const { Model } = _ns_2;
|
|
4261
6079
|
const { modelPropertiesToObject } = await Promise.resolve().then(function () { return model; });
|
|
4262
6080
|
// Try the name first ("post" -> "Post"); then findByModelType for names with spaces ("new_model" -> "New model")
|
|
4263
6081
|
let model$1 = Model.resolve(modelName, { modelFileId });
|
|
@@ -4266,7 +6084,7 @@ async function loadPropertyDefinition(db, { propertyId, modelName, modelFileId,
|
|
|
4266
6084
|
}
|
|
4267
6085
|
const unwritten = model$1?.properties?.length ? modelPropertiesToObject(model$1.properties)[propertyName] : undefined;
|
|
4268
6086
|
if (unwritten)
|
|
4269
|
-
logger$
|
|
6087
|
+
logger$3(`No row yet: in-memory definition of "${propertyName}" from Model ${modelName}`);
|
|
4270
6088
|
return unwritten;
|
|
4271
6089
|
}
|
|
4272
6090
|
|
|
@@ -4365,7 +6183,7 @@ function whenValidationLoaded() {
|
|
|
4365
6183
|
// neutralizes the minified chunk-export read (`.then(n => n._)`) in this form
|
|
4366
6184
|
validationLoading ?? (validationLoading = (async () => {
|
|
4367
6185
|
try {
|
|
4368
|
-
const { SchemaValidationService } = await import('./SchemaValidationService-
|
|
6186
|
+
const { SchemaValidationService } = await import('./SchemaValidationService-Dc-lpFQX.js');
|
|
4369
6187
|
validationService = new SchemaValidationService();
|
|
4370
6188
|
}
|
|
4371
6189
|
finally {
|
|
@@ -4600,17 +6418,13 @@ hints = {}) => {
|
|
|
4600
6418
|
}
|
|
4601
6419
|
try {
|
|
4602
6420
|
if (hints.propertyId != null) {
|
|
4603
|
-
const
|
|
4604
|
-
const _ns_6 = _mod_6.bi;
|
|
4605
|
-
const { getPropertyDefinition } = _ns_6;
|
|
6421
|
+
const { getPropertyDefinition } = await Promise.resolve().then(function () { return propertyDefinition; });
|
|
4606
6422
|
const definition = await getPropertyDefinition(hints.propertyId);
|
|
4607
6423
|
if (definition)
|
|
4608
6424
|
return definition;
|
|
4609
6425
|
}
|
|
4610
|
-
const
|
|
4611
|
-
const
|
|
4612
|
-
const { resolveItemModelFileId } = _ns_7;
|
|
4613
|
-
const { getModelPropertyDefinitions, resolvePropertyKey } = await import('./property-iNW1N3vm.js');
|
|
6426
|
+
const { resolveItemModelFileId } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
6427
|
+
const { getModelPropertyDefinitions, resolvePropertyKey } = await import('./property-4jSqHieb.js');
|
|
4614
6428
|
const modelFileId = (await resolveItemModelFileId(hints)) ?? undefined;
|
|
4615
6429
|
const name = modelName || ((await modelTypeToModelName(modelType)) ?? '');
|
|
4616
6430
|
// An ambiguous bare name reads nothing here; the in-memory model below may still know it
|
|
@@ -4618,9 +6432,9 @@ hints = {}) => {
|
|
|
4618
6432
|
const key = resolvePropertyKey(definitions, propertyName);
|
|
4619
6433
|
if (key)
|
|
4620
6434
|
return definitions[key];
|
|
4621
|
-
const
|
|
4622
|
-
const
|
|
4623
|
-
const { Model } =
|
|
6435
|
+
const _mod_3 = await import('./publishErrors-DZXDX3B6.js');
|
|
6436
|
+
const _ns_3 = _mod_3.am;
|
|
6437
|
+
const { Model } = _ns_3;
|
|
4624
6438
|
const { modelPropertiesToObject } = await Promise.resolve().then(function () { return model; });
|
|
4625
6439
|
let model$1 = modelName ? Model.peekByName(modelName) : undefined;
|
|
4626
6440
|
if (!model$1?.properties?.length && modelType)
|
|
@@ -4639,12 +6453,10 @@ hints = {}) => {
|
|
|
4639
6453
|
const modelTypeToModelName = async (modelType) => {
|
|
4640
6454
|
if (!modelType)
|
|
4641
6455
|
return undefined;
|
|
4642
|
-
const
|
|
4643
|
-
const _ns_9 = _mod_9.bh;
|
|
4644
|
-
const { modelNameForType } = _ns_9;
|
|
6456
|
+
const { modelNameForType } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
4645
6457
|
return modelNameForType(modelType).catch(() => undefined);
|
|
4646
6458
|
};
|
|
4647
|
-
const logger$
|
|
6459
|
+
const logger$2 = debug('seedSdk:property:class');
|
|
4648
6460
|
// Define tracked properties for the Proxy
|
|
4649
6461
|
// These properties will be read from/written to the actor context
|
|
4650
6462
|
const TRACKED_PROPERTIES$1 = [
|
|
@@ -4776,7 +6588,7 @@ class ItemProperty {
|
|
|
4776
6588
|
serviceInput.propertyValue = JSON.parse(propertyValue);
|
|
4777
6589
|
}
|
|
4778
6590
|
catch (e) {
|
|
4779
|
-
logger$
|
|
6591
|
+
logger$2('List property value is not JSON', e);
|
|
4780
6592
|
}
|
|
4781
6593
|
}
|
|
4782
6594
|
}
|
|
@@ -4803,7 +6615,7 @@ class ItemProperty {
|
|
|
4803
6615
|
return;
|
|
4804
6616
|
}
|
|
4805
6617
|
// Use dynamic import to break circular dependency
|
|
4806
|
-
const schemaMod = await import('./index-
|
|
6618
|
+
const schemaMod = await import('./index-CmNqUKQN.js');
|
|
4807
6619
|
const { ModelPropertyDataTypes } = schemaMod;
|
|
4808
6620
|
const { context } = snapshot;
|
|
4809
6621
|
const { propertyRecordSchema } = context;
|
|
@@ -4909,7 +6721,7 @@ class ItemProperty {
|
|
|
4909
6721
|
}
|
|
4910
6722
|
}
|
|
4911
6723
|
catch (e) {
|
|
4912
|
-
logger$
|
|
6724
|
+
logger$2(`[ItemProperty] [${context.seedLocalId}] [${context.propertyName}] [storageType] error`, e);
|
|
4913
6725
|
renderValue = isHtml ? undefined : 'No file found';
|
|
4914
6726
|
}
|
|
4915
6727
|
}
|
|
@@ -4959,7 +6771,7 @@ class ItemProperty {
|
|
|
4959
6771
|
});
|
|
4960
6772
|
this._service.start();
|
|
4961
6773
|
// Assignments check values synchronously (planPropertyWrite)
|
|
4962
|
-
whenValidationLoaded().catch((error) => logger$
|
|
6774
|
+
whenValidationLoaded().catch((error) => logger$2('[ItemProperty] loading validation', error));
|
|
4963
6775
|
onEntityTeardown(this, () => {
|
|
4964
6776
|
this._subscription?.unsubscribe();
|
|
4965
6777
|
revokeHeldObjectUrls(this._service);
|
|
@@ -5016,9 +6828,9 @@ class ItemProperty {
|
|
|
5016
6828
|
// The change feed reports writes by stored name: watch every name this property's rows have
|
|
5017
6829
|
const propertyNames = [...property.names];
|
|
5018
6830
|
if (propertyId != null) {
|
|
5019
|
-
const
|
|
5020
|
-
const
|
|
5021
|
-
const { metadata } =
|
|
6831
|
+
const _mod_4 = await import('./index-2PaIhvOU.js');
|
|
6832
|
+
const _ns_4 = _mod_4.s;
|
|
6833
|
+
const { metadata } = _ns_4;
|
|
5022
6834
|
const { eq: eqOp } = await import('drizzle-orm');
|
|
5023
6835
|
const stored = await db
|
|
5024
6836
|
.selectDistinct({ propertyName: metadata.propertyName })
|
|
@@ -5242,9 +7054,7 @@ class ItemProperty {
|
|
|
5242
7054
|
const ctx = this._service.getSnapshot().context;
|
|
5243
7055
|
let modelFileId;
|
|
5244
7056
|
try {
|
|
5245
|
-
const
|
|
5246
|
-
const _ns_11 = _mod_11.bh;
|
|
5247
|
-
const { resolveItemModelFileId } = _ns_11;
|
|
7057
|
+
const { resolveItemModelFileId } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
5248
7058
|
modelFileId = await resolveItemModelFileId({
|
|
5249
7059
|
modelFileId: ctx.modelFileId,
|
|
5250
7060
|
seedLocalId: ctx.seedLocalId,
|
|
@@ -5561,9 +7371,7 @@ class ItemProperty {
|
|
|
5561
7371
|
const propertiesData = await getItemProperties({ seedLocalId, seedUid });
|
|
5562
7372
|
const instances = [];
|
|
5563
7373
|
const seenNormalized = new Set();
|
|
5564
|
-
const
|
|
5565
|
-
const _ns_12 = _mod_12.bh;
|
|
5566
|
-
const { resolveItemModelFileId } = _ns_12;
|
|
7374
|
+
const { resolveItemModelFileId } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
5567
7375
|
const itemModelFileId = await resolveItemModelFileId({ seedLocalId, seedUid });
|
|
5568
7376
|
// One lookup per model type, not per property
|
|
5569
7377
|
const modelNameByType = new Map();
|
|
@@ -5583,7 +7391,7 @@ class ItemProperty {
|
|
|
5583
7391
|
// (`textId` → `text`, `authorIdentityIds` → `authors`)
|
|
5584
7392
|
const definitionsKey = JSON.stringify([modelName, modelFileId ?? null]);
|
|
5585
7393
|
if (modelName && !definitionsByModel.has(definitionsKey)) {
|
|
5586
|
-
const { getModelPropertyDefinitions } = await import('./property-
|
|
7394
|
+
const { getModelPropertyDefinitions } = await import('./property-4jSqHieb.js');
|
|
5587
7395
|
definitionsByModel.set(definitionsKey,
|
|
5588
7396
|
// No model row: the stored name decides
|
|
5589
7397
|
getModelPropertyDefinitions(modelName, { modelFileId }).then(Object.fromEntries, () => ({})));
|
|
@@ -5757,7 +7565,7 @@ class ItemProperty {
|
|
|
5757
7565
|
const { seedLocalId, propertyName } = this._getSnapshotContext();
|
|
5758
7566
|
if (!seedLocalId || !propertyName)
|
|
5759
7567
|
return;
|
|
5760
|
-
const { appendOutboxOpAndWait, markPropertyConflictsResolved, startItemOutbox, throwItemWriteError } = await import('./index-
|
|
7568
|
+
const { appendOutboxOpAndWait, markPropertyConflictsResolved, startItemOutbox, throwItemWriteError } = await import('./index-BXTyvnKb.js');
|
|
5761
7569
|
if (choice === 'remote') {
|
|
5762
7570
|
startItemOutbox();
|
|
5763
7571
|
await appendOutboxOpAndWait({
|
|
@@ -5821,8 +7629,10 @@ class ItemProperty {
|
|
|
5821
7629
|
* Versions stop showing the value too. Revocation is permanent.
|
|
5822
7630
|
* @throws ClearNotSupportedError for a Boolean or Date property without `retract`: their EAS types
|
|
5823
7631
|
* have no empty value.
|
|
7632
|
+
* @throws EntityUnloadedError when this instance was unloaded; nothing is written.
|
|
5824
7633
|
*/
|
|
5825
7634
|
async clear(options = {}) {
|
|
7635
|
+
this._assertLoaded('clear');
|
|
5826
7636
|
const retract = options.retract === true;
|
|
5827
7637
|
const context = this._getSnapshotContext();
|
|
5828
7638
|
const dataType = normalizeDataType(this._dataType ?? context.propertyRecordSchema?.dataType);
|
|
@@ -5905,9 +7715,7 @@ class ItemProperty {
|
|
|
5905
7715
|
context = this._getSnapshotContext();
|
|
5906
7716
|
if (!context.seedLocalId) {
|
|
5907
7717
|
// An item known only by its seed uid: its local seed, if it has one
|
|
5908
|
-
const
|
|
5909
|
-
const _ns_13 = _mod_13.bg;
|
|
5910
|
-
const { findSeedRow } = _ns_13;
|
|
7718
|
+
const { findSeedRow } = await Promise.resolve().then(function () { return findSeedRow$1; });
|
|
5911
7719
|
const seedLocalId = context.seedUid ? (await findSeedRow({ uid: context.seedUid }))?.localId : undefined;
|
|
5912
7720
|
if (!seedLocalId)
|
|
5913
7721
|
throw new Error(`No local seed for the item to save ${context.propertyName} to`);
|
|
@@ -5945,7 +7753,7 @@ class ItemProperty {
|
|
|
5945
7753
|
/** Waits for an appended op to settle, then shows its row, or the current value if it failed. */
|
|
5946
7754
|
async _followWrite(write, appended, plan) {
|
|
5947
7755
|
try {
|
|
5948
|
-
const { getOutboxOps, outboxOpResult, startItemOutbox, waitForOutboxOp } = await import('./index-
|
|
7756
|
+
const { getOutboxOps, outboxOpResult, startItemOutbox, waitForOutboxOp } = await import('./index-BXTyvnKb.js');
|
|
5949
7757
|
startItemOutbox();
|
|
5950
7758
|
const opId = await appended;
|
|
5951
7759
|
await waitForOutboxOp(opId);
|
|
@@ -6004,7 +7812,7 @@ class ItemProperty {
|
|
|
6004
7812
|
this._service.send({ type: 'updateContext', ...outcome.applied, isSaving: false });
|
|
6005
7813
|
return;
|
|
6006
7814
|
}
|
|
6007
|
-
const { throwItemWriteError } = await import('./index-
|
|
7815
|
+
const { throwItemWriteError } = await import('./index-BXTyvnKb.js');
|
|
6008
7816
|
let error = outcome.error;
|
|
6009
7817
|
try {
|
|
6010
7818
|
throwItemWriteError(outcome.error);
|
|
@@ -6012,7 +7820,7 @@ class ItemProperty {
|
|
|
6012
7820
|
catch (mapped) {
|
|
6013
7821
|
error = mapped;
|
|
6014
7822
|
}
|
|
6015
|
-
await this._showCurrentValue().catch((readError) => logger$
|
|
7823
|
+
await this._showCurrentValue().catch((readError) => logger$2(`[ItemProperty] reading ${this.propertyName} after a failed write`, readError));
|
|
6016
7824
|
if (this._latestWrite !== write || isActorStopped(this._service))
|
|
6017
7825
|
return;
|
|
6018
7826
|
this._service.send({
|
|
@@ -6139,7 +7947,7 @@ class ItemProperty {
|
|
|
6139
7947
|
if (db && seedLocalId) {
|
|
6140
7948
|
// Through the outbox (ADR 0019): after the property's pending edits, which would
|
|
6141
7949
|
// otherwise write its rows back
|
|
6142
|
-
const { appendOutboxOpAndWait, startItemOutbox, throwItemWriteError } = await import('./index-
|
|
7950
|
+
const { appendOutboxOpAndWait, startItemOutbox, throwItemWriteError } = await import('./index-BXTyvnKb.js');
|
|
6143
7951
|
startItemOutbox();
|
|
6144
7952
|
await appendOutboxOpAndWait({
|
|
6145
7953
|
seedLocalId,
|
|
@@ -6149,9 +7957,9 @@ class ItemProperty {
|
|
|
6149
7957
|
}).catch(throwItemWriteError);
|
|
6150
7958
|
}
|
|
6151
7959
|
else if (db) {
|
|
6152
|
-
const
|
|
6153
|
-
const
|
|
6154
|
-
const seedSchemaMod =
|
|
7960
|
+
const _mod_5 = await import('./index-2PaIhvOU.js');
|
|
7961
|
+
const _ns_5 = _mod_5.s;
|
|
7962
|
+
const seedSchemaMod = _ns_5;
|
|
6155
7963
|
const { metadata } = seedSchemaMod;
|
|
6156
7964
|
const drizzleMod = await import('drizzle-orm');
|
|
6157
7965
|
const { and, eq } = drizzleMod;
|
|
@@ -6187,7 +7995,7 @@ var ItemProperty$1 = /*#__PURE__*/Object.freeze({
|
|
|
6187
7995
|
ItemPropertySaveValidationError: ItemPropertySaveValidationError
|
|
6188
7996
|
});
|
|
6189
7997
|
|
|
6190
|
-
const logger$
|
|
7998
|
+
const logger$1 = debug('seedSdk:db:read:getSchemaUidForModel');
|
|
6191
7999
|
/**
|
|
6192
8000
|
* EAS schema UID of a model's seeds: the in-memory cache, then `model_uids` (written from EAS by
|
|
6193
8001
|
* startup and sync), then EAS itself. The DB comes before EAS so creating items and saving
|
|
@@ -6224,7 +8032,7 @@ const getEasSchemaUidForModel = async (modelName) => {
|
|
|
6224
8032
|
});
|
|
6225
8033
|
}
|
|
6226
8034
|
catch (error) {
|
|
6227
|
-
logger$
|
|
8035
|
+
logger$1(`EAS schema lookup for model ${modelName} failed (offline?): ${error}`);
|
|
6228
8036
|
return undefined;
|
|
6229
8037
|
}
|
|
6230
8038
|
if (easSchemaUid) {
|
|
@@ -6270,165 +8078,6 @@ const createSeed = async ({ type, seedUid, modelFileId }, options = {}) => {
|
|
|
6270
8078
|
return newSeedLocalId;
|
|
6271
8079
|
};
|
|
6272
8080
|
|
|
6273
|
-
/**
|
|
6274
|
-
* The publisher a seed's version and property rows take: the seed's own (its `publisher`, else the
|
|
6275
|
-
* attester of its stored attestation). Undefined for a draft that belongs to no wallet yet; those
|
|
6276
|
-
* rows are then claimed together with their seed (claimUnpublishedDrafts).
|
|
6277
|
-
*/
|
|
6278
|
-
async function getSeedPublisher(seedLocalId,
|
|
6279
|
-
/** The database or transaction to read; the app database by default. */
|
|
6280
|
-
db = BaseDb.getAppDb()) {
|
|
6281
|
-
if (!seedLocalId)
|
|
6282
|
-
return undefined;
|
|
6283
|
-
const row = await findSeedRow({ localId: seedLocalId }, db);
|
|
6284
|
-
if (!row)
|
|
6285
|
-
return undefined;
|
|
6286
|
-
return normalizePublisher(resolvePublisherFromSeedRow(row));
|
|
6287
|
-
}
|
|
6288
|
-
|
|
6289
|
-
const logger$1 = debug('seedSdk:helpers:propertySchemaUid');
|
|
6290
|
-
/**
|
|
6291
|
-
* UID of the EAS schema a property publishes under, or undefined when EAS has none, the property's
|
|
6292
|
-
* type has no EAS type, or the lookup fails. Found UIDs and misses are cached
|
|
6293
|
-
* (`getEasSchemaUidForExactDefinition`), so creating an item and then saving its properties asks
|
|
6294
|
-
* EAS once per definition. Callers store the UID on the metadata row; publish resolves schemas
|
|
6295
|
-
* itself, so a missing one costs nothing there.
|
|
6296
|
-
*/
|
|
6297
|
-
async function lookupPropertySchemaUid(propertyName, propertyDef) {
|
|
6298
|
-
const definition = easSchemaDefinitionForProperty(propertyName, propertyDef);
|
|
6299
|
-
if (!definition)
|
|
6300
|
-
return undefined;
|
|
6301
|
-
try {
|
|
6302
|
-
return await getEasSchemaUidForExactDefinition(definition);
|
|
6303
|
-
}
|
|
6304
|
-
catch (error) {
|
|
6305
|
-
logger$1(`EAS schema lookup for "${definition}" failed (offline?): ${error}`);
|
|
6306
|
-
return undefined;
|
|
6307
|
-
}
|
|
6308
|
-
}
|
|
6309
|
-
|
|
6310
|
-
var propertySchemaUid = /*#__PURE__*/Object.freeze({
|
|
6311
|
-
__proto__: null,
|
|
6312
|
-
lookupPropertySchemaUid: lookupPropertySchemaUid
|
|
6313
|
-
});
|
|
6314
|
-
|
|
6315
|
-
/** Error thrown when metadata validation fails (e.g. enum violation). */
|
|
6316
|
-
class MetadataValidationError extends Error {
|
|
6317
|
-
constructor(message, validationErrors) {
|
|
6318
|
-
super(message);
|
|
6319
|
-
this.validationErrors = validationErrors;
|
|
6320
|
-
this.name = 'MetadataValidationError';
|
|
6321
|
-
}
|
|
6322
|
-
}
|
|
6323
|
-
/**
|
|
6324
|
-
* The EAS schema uid a metadata row of this property takes, from the property's data type and EAS
|
|
6325
|
-
* name. Undefined for a data type with no EAS type or when the lookup fails. May call EAS, so run
|
|
6326
|
-
* it before a transaction, not inside one.
|
|
6327
|
-
*/
|
|
6328
|
-
/** The property's EAS schema UID from the shared lookup cache (lookupPropertySchemaUid). */
|
|
6329
|
-
const resolveMetadataSchemaUid = async (propertyName, propertyRecordSchema) => lookupPropertySchemaUid(propertyName, propertyRecordSchema);
|
|
6330
|
-
const createMetadata = async (metadataValues, propertyRecordSchema, options) => {
|
|
6331
|
-
const appDb = options?.db ?? BaseDb.getAppDb();
|
|
6332
|
-
metadataValues.localId = generateId();
|
|
6333
|
-
// A property row belongs to its seed's wallet (none yet for a draft made with no wallet connected).
|
|
6334
|
-
const publisher = await getSeedPublisher(metadataValues.seedLocalId, appDb);
|
|
6335
|
-
if (publisher) {
|
|
6336
|
-
metadataValues.publisher = publisher;
|
|
6337
|
-
}
|
|
6338
|
-
if (!metadataValues.modelType && metadataValues.modelName) {
|
|
6339
|
-
metadataValues.modelType = easName(metadataValues.modelName);
|
|
6340
|
-
}
|
|
6341
|
-
// property_id is the match key (decision D4); the row is written under the property's storage name.
|
|
6342
|
-
const property = await resolveMetadataProperty({
|
|
6343
|
-
propertyId: metadataValues.propertyId,
|
|
6344
|
-
definitionId: propertyRecordSchema?.id,
|
|
6345
|
-
propertyName: metadataValues.propertyName,
|
|
6346
|
-
modelName: metadataValues.modelName ?? metadataValues.modelType,
|
|
6347
|
-
modelFileId: options?.modelFileId,
|
|
6348
|
-
seedLocalId: metadataValues.seedLocalId,
|
|
6349
|
-
seedUid: metadataValues.seedUid,
|
|
6350
|
-
}, appDb, { onAmbiguousModel: 'none' });
|
|
6351
|
-
if (property) {
|
|
6352
|
-
metadataValues.propertyId = property.id;
|
|
6353
|
-
metadataValues.propertyName = property.storageName;
|
|
6354
|
-
}
|
|
6355
|
-
else if (metadataValues.propertyName && propertyRecordSchema) {
|
|
6356
|
-
metadataValues.propertyName = metadataStorageName(metadataValues.propertyName, propertyRecordSchema);
|
|
6357
|
-
}
|
|
6358
|
-
const isItemStorage = propertyRecordSchema && propertyRecordSchema.storageType === 'ItemStorage';
|
|
6359
|
-
// if (
|
|
6360
|
-
// propertyRecordSchema &&
|
|
6361
|
-
// propertyRecordSchema.localStorageDir &&
|
|
6362
|
-
// isItemStorage
|
|
6363
|
-
// ) {
|
|
6364
|
-
// const filename = `${metadataValues.seedUid || metadataValues.seedLocalId}${propertyRecordSchema.filenameSuffix}`
|
|
6365
|
-
// const filePath = path.join(propertyRecordSchema.localStorageDir, filename)
|
|
6366
|
-
// await fs.promises.writeFile(filePath, metadataValues.propertyValue)
|
|
6367
|
-
// metadataValues.propertyValue = filename
|
|
6368
|
-
// metadataValues.refValueType = 'file'
|
|
6369
|
-
// }
|
|
6370
|
-
// Convert propertyValue to string if it's not already (metadata table expects text)
|
|
6371
|
-
if (metadataValues.propertyValue !== undefined && metadataValues.propertyValue !== null) {
|
|
6372
|
-
if (Array.isArray(metadataValues.propertyValue)) {
|
|
6373
|
-
// List values: JSON array is the storage format (String() would give legacy comma-separated ids)
|
|
6374
|
-
metadataValues.propertyValue = JSON.stringify(metadataValues.propertyValue);
|
|
6375
|
-
}
|
|
6376
|
-
else if (typeof metadataValues.propertyValue !== 'string') {
|
|
6377
|
-
metadataValues.propertyValue = String(metadataValues.propertyValue);
|
|
6378
|
-
}
|
|
6379
|
-
}
|
|
6380
|
-
// Validate against property validation rules (enum, pattern, minLength, maxLength) unless skipped
|
|
6381
|
-
if (!options?.skipValidation &&
|
|
6382
|
-
propertyRecordSchema?.validation &&
|
|
6383
|
-
metadataValues.propertyValue != null &&
|
|
6384
|
-
metadataValues.propertyValue !== '') {
|
|
6385
|
-
const { SchemaValidationService } = await import('./SchemaValidationService-CieGrj5c.js');
|
|
6386
|
-
const validationService = new SchemaValidationService();
|
|
6387
|
-
const validationResult = validationService.validatePropertyValue(metadataValues.propertyValue, normalizeDataType(propertyRecordSchema.dataType), propertyRecordSchema.validation, propertyRecordSchema.refValueType
|
|
6388
|
-
? normalizeDataType(String(propertyRecordSchema.refValueType))
|
|
6389
|
-
: undefined);
|
|
6390
|
-
if (!validationResult.isValid && validationResult.errors.length > 0) {
|
|
6391
|
-
const validationErrors = validationResult.errors.map((e) => ({
|
|
6392
|
-
field: e.field,
|
|
6393
|
-
message: e.message,
|
|
6394
|
-
code: e.code,
|
|
6395
|
-
}));
|
|
6396
|
-
const message = validationErrors.map((e) => e.message).join('; ');
|
|
6397
|
-
throw new MetadataValidationError(message, validationErrors);
|
|
6398
|
-
}
|
|
6399
|
-
}
|
|
6400
|
-
// With a given db (a transaction), the caller resolves schemaUid first: the lookup may call EAS.
|
|
6401
|
-
// Without one it isn't required for metadata insertion, so a failed lookup leaves it unset.
|
|
6402
|
-
if (!options?.db && !isItemStorage && !metadataValues.schemaUid && metadataValues.propertyName) {
|
|
6403
|
-
const schemaUid = await resolveMetadataSchemaUid(metadataValues.propertyName, propertyRecordSchema);
|
|
6404
|
-
if (schemaUid)
|
|
6405
|
-
metadataValues.schemaUid = schemaUid;
|
|
6406
|
-
}
|
|
6407
|
-
let inserted;
|
|
6408
|
-
try {
|
|
6409
|
-
inserted = await appDb
|
|
6410
|
-
.insert(metadata)
|
|
6411
|
-
.values({
|
|
6412
|
-
...metadataValues,
|
|
6413
|
-
// Stamped here: a value carried in metadataValues would otherwise win over the column default.
|
|
6414
|
-
createdAt: Date.now(),
|
|
6415
|
-
updatedAt: Date.now(),
|
|
6416
|
-
})
|
|
6417
|
-
.returning();
|
|
6418
|
-
}
|
|
6419
|
-
catch (error) {
|
|
6420
|
-
// Drizzle's message is the full SQL; the driver error that explains it is only on `cause`.
|
|
6421
|
-
const cause = error?.cause;
|
|
6422
|
-
const reason = cause instanceof Error ? cause.message : cause != null ? String(cause) : undefined;
|
|
6423
|
-
throw new Error(`Failed to insert metadata for property ${metadataValues.propertyName}` +
|
|
6424
|
-
(reason ? `: ${reason}` : `: ${error instanceof Error ? error.message : String(error)}`), { cause: error });
|
|
6425
|
-
}
|
|
6426
|
-
if (!inserted || inserted.length === 0) {
|
|
6427
|
-
throw new Error(`Failed to insert metadata record for property ${metadataValues.propertyName}`);
|
|
6428
|
-
}
|
|
6429
|
-
return inserted[0];
|
|
6430
|
-
};
|
|
6431
|
-
|
|
6432
8081
|
/**
|
|
6433
8082
|
* Convert Model.properties array to object format (for backward compatibility)
|
|
6434
8083
|
* This replaces the old model.schema getter
|
|
@@ -6476,9 +8125,9 @@ const createNewItem = async ({ modelName, modelFileId, ...propertyData }) => {
|
|
|
6476
8125
|
}
|
|
6477
8126
|
const seedType = easName(modelName);
|
|
6478
8127
|
// Dynamic import to break circular dependency
|
|
6479
|
-
const
|
|
6480
|
-
const
|
|
6481
|
-
const modelMod =
|
|
8128
|
+
const _mod_6 = await import('./publishErrors-DZXDX3B6.js');
|
|
8129
|
+
const _ns_6 = _mod_6.am;
|
|
8130
|
+
const modelMod = _ns_6;
|
|
6482
8131
|
const { Model } = modelMod;
|
|
6483
8132
|
const label = `createNewItem ${modelName}`;
|
|
6484
8133
|
const model = await timed(`${label} model`, () => Model.resolveAsync(modelName, { modelFileId }));
|
|
@@ -6504,7 +8153,7 @@ const createNewItem = async ({ modelName, modelFileId, ...propertyData }) => {
|
|
|
6504
8153
|
const propertyRecordSchema = propertySchemas?.[propertyName];
|
|
6505
8154
|
if (!propertyRecordSchema || propertyRecordSchema.storageType === 'ItemStorage')
|
|
6506
8155
|
return;
|
|
6507
|
-
schemaUids.set(propertyName, await
|
|
8156
|
+
schemaUids.set(propertyName, await lookupPropertySchemaUid(listRelationEasPropertyName(propertyName, propertyRecordSchema) ?? propertyName, propertyRecordSchema));
|
|
6508
8157
|
})));
|
|
6509
8158
|
const rows = [];
|
|
6510
8159
|
const stagedPaths = [];
|
|
@@ -6534,7 +8183,7 @@ const createNewItem = async ({ modelName, modelFileId, ...propertyData }) => {
|
|
|
6534
8183
|
}
|
|
6535
8184
|
rows.push({ propertyName, propertyValue, propertyRecordSchema, schemaUid: schemaUids.get(propertyName) });
|
|
6536
8185
|
}
|
|
6537
|
-
const { appendOutboxOpAndWait, startItemOutbox } = await import('./index-
|
|
8186
|
+
const { appendOutboxOpAndWait, startItemOutbox } = await import('./index-BXTyvnKb.js');
|
|
6538
8187
|
startItemOutbox();
|
|
6539
8188
|
const fields = await timed(`${label} seed fields`, () => resolveNewSeedFields(seedType));
|
|
6540
8189
|
const applied = await timed(`${label} outbox apply`, () => appendOutboxOpAndWait({
|
|
@@ -6553,9 +8202,9 @@ const createNewItem = async ({ modelName, modelFileId, ...propertyData }) => {
|
|
|
6553
8202
|
}
|
|
6554
8203
|
catch (error) {
|
|
6555
8204
|
// Staged values of a create that never got appended (once appended, the op's discard does it)
|
|
6556
|
-
const
|
|
6557
|
-
const
|
|
6558
|
-
const { deleteFiles } =
|
|
8205
|
+
const _mod_7 = await import('./publishErrors-DZXDX3B6.js');
|
|
8206
|
+
const _ns_7 = _mod_7.ap;
|
|
8207
|
+
const { deleteFiles } = _ns_7;
|
|
6559
8208
|
await deleteFiles(stagedPaths);
|
|
6560
8209
|
throw error;
|
|
6561
8210
|
}
|
|
@@ -6629,16 +8278,16 @@ const createItemPropertyInstances = async (metadataRows, seedLocalId, seedUid, m
|
|
|
6629
8278
|
};
|
|
6630
8279
|
// The model's property definitions from their rows; a model created in this session whose
|
|
6631
8280
|
// rows aren't written yet gives its in-memory ones
|
|
6632
|
-
const { getModelPropertyDefinitions } = await import('./property-
|
|
8281
|
+
const { getModelPropertyDefinitions } = await import('./property-4jSqHieb.js');
|
|
6633
8282
|
let propertySchemas = Object.fromEntries(await getModelPropertyDefinitions(modelName, modelScope).catch((err) => {
|
|
6634
8283
|
logger(`No property definitions for ${modelName}: ${err}`);
|
|
6635
8284
|
return [];
|
|
6636
8285
|
}));
|
|
6637
8286
|
if (Object.keys(propertySchemas).length === 0) {
|
|
6638
8287
|
const { modelPropertiesToObject } = await Promise.resolve().then(function () { return model; });
|
|
6639
|
-
const
|
|
6640
|
-
const
|
|
6641
|
-
const { Model } =
|
|
8288
|
+
const _mod_8 = await import('./publishErrors-DZXDX3B6.js');
|
|
8289
|
+
const _ns_8 = _mod_8.am;
|
|
8290
|
+
const { Model } = _ns_8;
|
|
6642
8291
|
const model$1 = Model.resolve(modelName, modelScope);
|
|
6643
8292
|
if (model$1?.properties?.length)
|
|
6644
8293
|
propertySchemas = modelPropertiesToObject(model$1.properties);
|
|
@@ -6664,7 +8313,7 @@ const createItemPropertyInstances = async (metadataRows, seedLocalId, seedUid, m
|
|
|
6664
8313
|
// Infer propertyRecordSchema from metadata when schema is missing (enables persistence)
|
|
6665
8314
|
let propSchema = propertySchemas[propertyName];
|
|
6666
8315
|
if (!propSchema && isRefTypeFromMeta) {
|
|
6667
|
-
const schemaMod = await import('./index-
|
|
8316
|
+
const schemaMod = await import('./index-CmNqUKQN.js');
|
|
6668
8317
|
const { ModelPropertyDataTypes } = schemaMod;
|
|
6669
8318
|
propSchema = {
|
|
6670
8319
|
dataType: refSeedType === 'html' ? ModelPropertyDataTypes.Html
|
|
@@ -7047,9 +8696,9 @@ const getModel = () => {
|
|
|
7047
8696
|
// the fragile `import(...).then(n => n.X)` chunk-namespace pattern.
|
|
7048
8697
|
if (!modelImportPromise) {
|
|
7049
8698
|
modelImportPromise = (async () => {
|
|
7050
|
-
const
|
|
7051
|
-
const
|
|
7052
|
-
const mod =
|
|
8699
|
+
const _mod_9 = await import('./publishErrors-DZXDX3B6.js');
|
|
8700
|
+
const _ns_9 = _mod_9.am;
|
|
8701
|
+
const mod = _ns_9;
|
|
7053
8702
|
ModelClass = mod.Model;
|
|
7054
8703
|
return mod;
|
|
7055
8704
|
})();
|
|
@@ -7248,16 +8897,16 @@ class Item {
|
|
|
7248
8897
|
};
|
|
7249
8898
|
this.getPublishUploads = async (options) => {
|
|
7250
8899
|
// Use dynamic import to break circular dependency
|
|
7251
|
-
const
|
|
7252
|
-
const
|
|
7253
|
-
const getPublishUploadsMod =
|
|
8900
|
+
const _mod_10 = await import('./publishErrors-DZXDX3B6.js');
|
|
8901
|
+
const _ns_10 = _mod_10.aq;
|
|
8902
|
+
const getPublishUploadsMod = _ns_10;
|
|
7254
8903
|
const { getPublishUploads } = getPublishUploadsMod;
|
|
7255
8904
|
return await getPublishUploads(this, options);
|
|
7256
8905
|
};
|
|
7257
8906
|
this.getPublishPayload = async (uploadedTransactions, options) => {
|
|
7258
|
-
const
|
|
7259
|
-
const
|
|
7260
|
-
const getPublishPayloadMod =
|
|
8907
|
+
const _mod_11 = await import('./getPublishPayload-Dd3HCYeI.js');
|
|
8908
|
+
const _ns_11 = _mod_11.b;
|
|
8909
|
+
const getPublishPayloadMod = _ns_11;
|
|
7261
8910
|
const { getPublishPayload } = getPublishPayloadMod;
|
|
7262
8911
|
return await getPublishPayload(this, uploadedTransactions, options);
|
|
7263
8912
|
};
|
|
@@ -7585,9 +9234,7 @@ class Item {
|
|
|
7585
9234
|
if (!props.modelName && props.type) {
|
|
7586
9235
|
// The model the seed's type names (its easName, at the model's nameMatch level): `blog_post`
|
|
7587
9236
|
// finds "Blog Post". No match leaves the type, which then fails as an unknown model.
|
|
7588
|
-
const
|
|
7589
|
-
const _ns_21 = _mod_21.bh;
|
|
7590
|
-
const { resolveModelRecordByNameOrType } = _ns_21;
|
|
9237
|
+
const { resolveModelRecordByNameOrType } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
7591
9238
|
const model = await resolveModelRecordByNameOrType(props.type, {
|
|
7592
9239
|
modelFileId: props.modelFileId,
|
|
7593
9240
|
schemaName: props.schemaName,
|
|
@@ -7649,18 +9296,16 @@ class Item {
|
|
|
7649
9296
|
const db = BaseDb.getAppDb();
|
|
7650
9297
|
if (db && props.modelName) {
|
|
7651
9298
|
// Model names are only unique per schema: resolve the exact models row from the most specific scope.
|
|
7652
|
-
const
|
|
7653
|
-
const _ns_22 = _mod_22.bh;
|
|
7654
|
-
const { resolveModelRecord } = _ns_22;
|
|
9299
|
+
const { resolveModelRecord } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
7655
9300
|
const modelRecord = await resolveModelRecord(props.modelName, {
|
|
7656
9301
|
modelFileId: props.modelFileId ?? modelInstance?.id,
|
|
7657
9302
|
schemaName: schemaName ?? modelInstance?.schemaName,
|
|
7658
9303
|
});
|
|
7659
9304
|
const modelRecords = modelRecord ? [modelRecord] : [];
|
|
7660
9305
|
if (!modelRecord) {
|
|
7661
|
-
const
|
|
7662
|
-
const
|
|
7663
|
-
const { UnknownModelError } =
|
|
9306
|
+
const _mod_12 = await import('./publishErrors-DZXDX3B6.js');
|
|
9307
|
+
const _ns_12 = _mod_12.ar;
|
|
9308
|
+
const { UnknownModelError } = _ns_12;
|
|
7664
9309
|
throw new UnknownModelError(props.modelName, schemaName ?? modelInstance?.schemaName);
|
|
7665
9310
|
}
|
|
7666
9311
|
if (modelRecord?.schemaFileId) {
|
|
@@ -7669,9 +9314,7 @@ class Item {
|
|
|
7669
9314
|
// Scope the constructor's Model lookup to this model's schema (as getItemData does on reload),
|
|
7670
9315
|
// so a same-named model cached from another schema can't supply the property set.
|
|
7671
9316
|
if (modelRecord && !props.schemaName) {
|
|
7672
|
-
const
|
|
7673
|
-
const _ns_24 = _mod_24.bh;
|
|
7674
|
-
const { getModelSchemaName } = _ns_24;
|
|
9317
|
+
const { getModelSchemaName } = await Promise.resolve().then(function () { return resolveModelRecord$1; });
|
|
7675
9318
|
const modelSchemaName = await getModelSchemaName(modelRecord.id, db);
|
|
7676
9319
|
if (modelSchemaName) {
|
|
7677
9320
|
props.schemaName = modelSchemaName;
|
|
@@ -7774,7 +9417,7 @@ class Item {
|
|
|
7774
9417
|
/* best-effort cache cleanup */
|
|
7775
9418
|
}
|
|
7776
9419
|
try {
|
|
7777
|
-
const { appendOutboxOpAndWait, deleteFiles, startItemOutbox } = await import('./index-
|
|
9420
|
+
const { appendOutboxOpAndWait, deleteFiles, startItemOutbox } = await import('./index-BXTyvnKb.js');
|
|
7778
9421
|
startItemOutbox();
|
|
7779
9422
|
const applied = await appendOutboxOpAndWait({
|
|
7780
9423
|
seedLocalId,
|
|
@@ -7797,9 +9440,9 @@ class Item {
|
|
|
7797
9440
|
for (const [propertyName, value] of pipelineValues) {
|
|
7798
9441
|
const property = await this.waitForPropertyInstance(item, propertyName, timeout);
|
|
7799
9442
|
if (!property) {
|
|
7800
|
-
const
|
|
7801
|
-
const
|
|
7802
|
-
const { ItemCreateError } =
|
|
9443
|
+
const _mod_13 = await import('./publishErrors-DZXDX3B6.js');
|
|
9444
|
+
const _ns_13 = _mod_13.ar;
|
|
9445
|
+
const { ItemCreateError } = _ns_13;
|
|
7803
9446
|
throw new ItemCreateError(`Item.create: property ${item.modelName}.${propertyName} did not load for item ${item.seedLocalId}; value was not saved`, { code: 'property_not_loaded', modelName: item.modelName, property: propertyName, seedLocalId: item.seedLocalId });
|
|
7804
9447
|
}
|
|
7805
9448
|
// setValue, not the setter: the setter skips the save when the value is already in context.
|
|
@@ -7809,16 +9452,16 @@ class Item {
|
|
|
7809
9452
|
}
|
|
7810
9453
|
catch (error) {
|
|
7811
9454
|
if (error instanceof ItemPropertySaveValidationError) {
|
|
7812
|
-
const
|
|
7813
|
-
const
|
|
7814
|
-
const { ItemCreateValidationError } =
|
|
9455
|
+
const _mod_14 = await import('./publishErrors-DZXDX3B6.js');
|
|
9456
|
+
const _ns_14 = _mod_14.ar;
|
|
9457
|
+
const { ItemCreateValidationError } = _ns_14;
|
|
7815
9458
|
const created = new ItemCreateValidationError(item.modelName, error.validationErrors.map((e) => ({ property: propertyName, code: e.code, message: e.message })));
|
|
7816
9459
|
created.cause = error;
|
|
7817
9460
|
throw created;
|
|
7818
9461
|
}
|
|
7819
|
-
const
|
|
7820
|
-
const
|
|
7821
|
-
const { ItemCreateError } =
|
|
9462
|
+
const _mod_15 = await import('./publishErrors-DZXDX3B6.js');
|
|
9463
|
+
const _ns_15 = _mod_15.ar;
|
|
9464
|
+
const { ItemCreateError } = _ns_15;
|
|
7822
9465
|
throw new ItemCreateError(`Item.create: failed to save ${item.modelName}.${propertyName} for item ${item.seedLocalId}: ` +
|
|
7823
9466
|
(error instanceof Error ? error.message : String(error)), { code: 'save_failed', modelName: item.modelName, property: propertyName, seedLocalId: item.seedLocalId, cause: error });
|
|
7824
9467
|
}
|
|
@@ -7994,9 +9637,9 @@ class Item {
|
|
|
7994
9637
|
if (!appDb || Item.cache.size === 0) {
|
|
7995
9638
|
return;
|
|
7996
9639
|
}
|
|
7997
|
-
const
|
|
7998
|
-
const
|
|
7999
|
-
const { metadata } =
|
|
9640
|
+
const _mod_16 = await import('./index-2PaIhvOU.js');
|
|
9641
|
+
const _ns_16 = _mod_16.s;
|
|
9642
|
+
const { metadata } = _ns_16;
|
|
8000
9643
|
const { eq, and } = await import('drizzle-orm');
|
|
8001
9644
|
await Promise.all(Item.cache.instances().map(async (instance) => {
|
|
8002
9645
|
try {
|
|
@@ -8106,11 +9749,11 @@ class Item {
|
|
|
8106
9749
|
}
|
|
8107
9750
|
/** Throws ItemCreateValidationError for unknown keys and values that break their property's rules. */
|
|
8108
9751
|
static async assertValidCreateValues(props, propertyNames, dataTypeByPropertyName, skipKeys) {
|
|
8109
|
-
const
|
|
8110
|
-
const
|
|
8111
|
-
const { ItemCreateValidationError } =
|
|
8112
|
-
const { getModelPropertyDefinitions, resolvePropertyKey } = await import('./property-
|
|
8113
|
-
const { SchemaValidationService } = await import('./SchemaValidationService-
|
|
9752
|
+
const _mod_17 = await import('./publishErrors-DZXDX3B6.js');
|
|
9753
|
+
const _ns_17 = _mod_17.ar;
|
|
9754
|
+
const { ItemCreateValidationError } = _ns_17;
|
|
9755
|
+
const { getModelPropertyDefinitions, resolvePropertyKey } = await import('./property-4jSqHieb.js');
|
|
9756
|
+
const { SchemaValidationService } = await import('./SchemaValidationService-Dc-lpFQX.js');
|
|
8114
9757
|
const validationService = new SchemaValidationService();
|
|
8115
9758
|
const modelName = props.modelName;
|
|
8116
9759
|
const known = new Set(propertyNames);
|
|
@@ -8464,9 +10107,9 @@ class Item {
|
|
|
8464
10107
|
itemActor.send(event);
|
|
8465
10108
|
};
|
|
8466
10109
|
try {
|
|
8467
|
-
const
|
|
8468
|
-
const
|
|
8469
|
-
const seedSchemaMod =
|
|
10110
|
+
const _mod_18 = await import('./index-2PaIhvOU.js');
|
|
10111
|
+
const _ns_18 = _mod_18.s;
|
|
10112
|
+
const seedSchemaMod = _ns_18;
|
|
8470
10113
|
const { seeds, metadata } = seedSchemaMod;
|
|
8471
10114
|
const drizzleMod = await import('drizzle-orm');
|
|
8472
10115
|
const { eq, and } = drizzleMod;
|
|
@@ -8478,9 +10121,7 @@ class Item {
|
|
|
8478
10121
|
return;
|
|
8479
10122
|
}
|
|
8480
10123
|
// Query initial seed and version data
|
|
8481
|
-
const
|
|
8482
|
-
const _ns_31 = _mod_31.bg;
|
|
8483
|
-
const { findSeedRow } = _ns_31;
|
|
10124
|
+
const { findSeedRow } = await Promise.resolve().then(function () { return findSeedRow$1; });
|
|
8484
10125
|
const seed = await findSeedRow({ localId: seedLocalId, uid: seedUid }, db);
|
|
8485
10126
|
if (!seed) {
|
|
8486
10127
|
logger(`[Item._setupLiveQuerySubscription] Seed not found for seedLocalId: ${seedLocalId}`);
|
|
@@ -8779,7 +10420,7 @@ class Item {
|
|
|
8779
10420
|
return;
|
|
8780
10421
|
}
|
|
8781
10422
|
// Through the outbox (ADR 0019): after the item's pending edits, not before them
|
|
8782
|
-
const { appendOutboxOpAndWait, startItemOutbox, throwItemWriteError } = await import('./index-
|
|
10423
|
+
const { appendOutboxOpAndWait, startItemOutbox, throwItemWriteError } = await import('./index-BXTyvnKb.js');
|
|
8783
10424
|
startItemOutbox();
|
|
8784
10425
|
await appendOutboxOpAndWait({
|
|
8785
10426
|
seedLocalId: context.seedLocalId,
|
|
@@ -8806,5 +10447,5 @@ var Item$1 = /*#__PURE__*/Object.freeze({
|
|
|
8806
10447
|
Item: Item
|
|
8807
10448
|
});
|
|
8808
10449
|
|
|
8809
|
-
export {
|
|
8810
|
-
//# sourceMappingURL=Item-
|
|
10450
|
+
export { entityStatus as $, AmbiguousModelError as A, metadataPropertyGroupKeys as B, ClearNotSupportedError as C, compareMetadataRowsLatestFirst as D, EntityUnloadedError as E, readMetadataPropertiesById as F, isPublishedMetadataRow as G, getLatestPublishedVersionRow as H, Item as I, writeInBatches as J, getItemStorageSubdir as K, getAllAddressesFromDb as L, whenDbReady as M, applyContextUpdate as N, OUTBOX_PRUNE_AFTER_MS as O, isInternalUpdate as P, evictionLog as Q, addSchemaToDb as R, StagedValueMissingError as S, holdChild as T, isEntityTornDown as U, sameEntity as V, DEFAULT_READY_TIMEOUT_MS as W, createReactiveProxy as X, runEntityLoad as Y, waitForEntityReady as Z, evictEntities as _, appendOutboxOpAndWait as a, normalizeAddressConfig as a$, inDefinitionsTransaction as a0, releaseEntity as a1, destroyEntity as a2, isClientReady as a3, upsertSchemaRow as a4, broadcastSchemasInvalidate as a5, EntityCache as a6, normalizePublisher as a7, chunkValues as a8, rowsPerInsert as a9, addCachedPropertyRefresher as aA, getStorageTransactionIdForSeedUid as aB, latestVersionOrder as aC, otherVersionRowsWithUid as aD, mergeVersionRowInto as aE, isVersionRevoked as aF, findMetadataPropertyByName as aG, readModelMetadataProperties as aH, parseListPropertyValueFromStorage as aI, findSeedRow as aJ, resolvePublisherFromSeedRow as aK, ClientManagerState as aL, MachineIds as aM, PUBLISH_NOT_CONFIGURED_MESSAGE as aN, createNewItem as aO, getAddressesForItemsFilter as aP, getArweaveUrlForTransaction as aQ, getGetPublisherForNewSeeds as aR, getMetadataLatest as aS, getPublishExecutor as aT, getRevokeExecutor as aU, getUploadExecutor as aV, getVersionData as aW, isClientReadySnapshot as aX, isLocalUnsealedDraft as aY, isSeedRowOwned as aZ, localUnsealedDraftSql as a_, loadNameMatchLevels as aa, getGetAdditionalSyncAddresses as ab, findSeedRowsByUid as ac, normalizeHexAddress as ad, selectInBatches as ae, nameMatchLevels as af, modelPropertiesToObject as ag, listRelationEasPropertyName as ah, easPropertyNameForPublish as ai, metadataStorageName as aj, resolveMetadataPropertyId as ak, getItemStoragePropertiesForModel as al, resolveItemModelFileId as am, matchMetadataProperty as an, metadataRowsOfProperty as ao, METADATA_LATEST_FIRST_ORDER_SQL as ap, getEasSchemaUidForModel as aq, isStorageSeedRef as ar, isActorStopped as as, differsFromOriginals as at, resolveModelRecord as au, definitionOnlyFields as av, persistDefinitionFields as aw, onEntityTeardown as ax, getPropertyDefinition as ay, propertyDefinitionFromRow as az, waitForPendingOutboxOps as b, normalizeAddressList as b0, ownedPublisherSql as b1, ownedSeedsSql as b2, pickLatestMetadataRowPerProperty as b3, placeholderUidSql as b4, propertyMachine as b5, publisherInAddressListSql as b6, publisherIsInOwnedSet as b7, setAdditionalSyncAddresses as b8, setGetPublisherForNewSeeds as b9, getWatchedAddressesFromDb as bA, getOwnedAddressesFromDb as bB, setEntityUnloadGraceMs as bC, stopOutbox as bD, evictAllEntities as bE, registerClientActor as bF, getPropertyDefinitions as bG, metadataPropertyNameForms as bH, currentPropertyRow as bI, resolveModelRecordByNameOrType as bJ, storageSeedTransactionId as bK, storageUploadPath as bL, storedValueKind as bM, resolveStorageDisplay as bN, findSeedRow$1 as bO, resolveModelRecord$1 as bP, propertyDefinition as bQ, definitions as bR, db as bS, currentPropertyRow$1 as bT, propertySchemaUid as bU, findFile as bV, resolve as bW, conflicts as bX, Item$1 as bY, setPublishExecutor as ba, setRevokeExecutor as bb, setUploadExecutor as bc, writeModelToDb as bd, writePropertyToDb as be, currentNameMatchLevels as bf, renameModelInDb as bg, insertModelStubs as bh, warnOnInvalidNameMatch as bi, refreshCachedProperties as bj, resolveMetadataProperty as bk, lookupPropertySchemaUid as bl, getItemStorageLocalFileName as bm, deleteStagedFile as bn, storageFileName as bo, STORAGE_SUBDIR as bp, resolveNewSeedFields as bq, stageStorageContent as br, requireStagedFile as bs, findSetConflict as bt, recordPropertyEdit as bu, startOutbox as bv, createSeed as bw, storageValueRowOf as bx, getLatestVersionRow as by, ClientManagerEvents as bz, OUTBOX_PRUNE_FAILED_AFTER_MS as c, OutboxOpDeferredError as d, OutboxOpFailedError as e, OutboxOpRejectedError as f, getOutboxOps as g, appendOutboxOp as h, drainOutbox as i, getPendingSetValue as j, outboxOpValue as k, ItemProperty as l, markPropertyConflictsResolved as m, ItemPropertySaveValidationError as n, outboxOpResult as o, pruneOutbox as p, OwnershipError as q, registerOutboxApplier as r, assertItemOwned as s, getCorrectId as t, isItemOwned as u, isPublishedSeedRef as v, waitForOutboxOp as w, propertyKeyFromStoredName as x, findFileInDir as y, currentPropertyRows as z };
|
|
10451
|
+
//# sourceMappingURL=Item-BU9wIT7l.js.map
|