@objectstack/objectql 8.0.1 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +177 -5
- package/dist/index.d.ts +177 -5
- package/dist/index.js +892 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +882 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,782 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/seed-loader.ts
|
|
12
|
+
var seed_loader_exports = {};
|
|
13
|
+
__export(seed_loader_exports, {
|
|
14
|
+
SeedLoaderService: () => SeedLoaderService
|
|
15
|
+
});
|
|
16
|
+
import { SeedLoaderConfigSchema } from "@objectstack/spec/data";
|
|
17
|
+
import { resolveSeedRecord } from "@objectstack/formula";
|
|
18
|
+
var DEFAULT_EXTERNAL_ID_FIELD, _SeedLoaderService, SeedLoaderService;
|
|
19
|
+
var init_seed_loader = __esm({
|
|
20
|
+
"src/seed-loader.ts"() {
|
|
21
|
+
"use strict";
|
|
22
|
+
DEFAULT_EXTERNAL_ID_FIELD = "name";
|
|
23
|
+
_SeedLoaderService = class _SeedLoaderService {
|
|
24
|
+
constructor(engine, metadata, logger) {
|
|
25
|
+
this.engine = engine;
|
|
26
|
+
this.metadata = metadata;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
}
|
|
29
|
+
// ==========================================================================
|
|
30
|
+
// Public API
|
|
31
|
+
// ==========================================================================
|
|
32
|
+
async load(request) {
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
const config = request.config;
|
|
35
|
+
const allErrors = [];
|
|
36
|
+
const allResults = [];
|
|
37
|
+
const datasets = this.filterByEnv(request.seeds, config.env);
|
|
38
|
+
if (datasets.length === 0) {
|
|
39
|
+
return this.buildEmptyResult(config, Date.now() - startTime);
|
|
40
|
+
}
|
|
41
|
+
const objectNames = datasets.map((d) => d.object);
|
|
42
|
+
const graph = await this.buildDependencyGraph(objectNames);
|
|
43
|
+
this.logger.info("[SeedLoader] Dependency graph built", {
|
|
44
|
+
objects: objectNames.length,
|
|
45
|
+
insertOrder: graph.insertOrder,
|
|
46
|
+
circularDeps: graph.circularDependencies.length
|
|
47
|
+
});
|
|
48
|
+
const orderedDatasets = this.orderDatasets(datasets, graph.insertOrder);
|
|
49
|
+
const refMap = this.buildReferenceMap(graph);
|
|
50
|
+
const insertedRecords = /* @__PURE__ */ new Map();
|
|
51
|
+
const deferredUpdates = [];
|
|
52
|
+
for (const dataset of orderedDatasets) {
|
|
53
|
+
const result = await this.loadDataset(
|
|
54
|
+
dataset,
|
|
55
|
+
config,
|
|
56
|
+
refMap,
|
|
57
|
+
insertedRecords,
|
|
58
|
+
deferredUpdates,
|
|
59
|
+
allErrors
|
|
60
|
+
);
|
|
61
|
+
allResults.push(result);
|
|
62
|
+
if (config.haltOnError && result.errored > 0) {
|
|
63
|
+
this.logger.warn("[SeedLoader] Halting on first error", { object: dataset.object });
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (config.multiPass && deferredUpdates.length > 0 && !config.dryRun) {
|
|
68
|
+
this.logger.info("[SeedLoader] Pass 2: resolving deferred references", {
|
|
69
|
+
count: deferredUpdates.length
|
|
70
|
+
});
|
|
71
|
+
await this.resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, config.organizationId);
|
|
72
|
+
}
|
|
73
|
+
const durationMs = Date.now() - startTime;
|
|
74
|
+
return this.buildResult(config, graph, allResults, allErrors, durationMs);
|
|
75
|
+
}
|
|
76
|
+
async buildDependencyGraph(objectNames) {
|
|
77
|
+
const nodes = [];
|
|
78
|
+
const objectSet = new Set(objectNames);
|
|
79
|
+
for (const objectName of objectNames) {
|
|
80
|
+
const objDef = await this.metadata.getObject(objectName);
|
|
81
|
+
const dependsOn = [];
|
|
82
|
+
const references = [];
|
|
83
|
+
if (objDef && objDef.fields) {
|
|
84
|
+
const fields = objDef.fields;
|
|
85
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
86
|
+
if ((fieldDef.type === "lookup" || fieldDef.type === "master_detail") && fieldDef.reference) {
|
|
87
|
+
const targetObject = fieldDef.reference;
|
|
88
|
+
if (objectSet.has(targetObject) && !dependsOn.includes(targetObject)) {
|
|
89
|
+
dependsOn.push(targetObject);
|
|
90
|
+
}
|
|
91
|
+
references.push({
|
|
92
|
+
field: fieldName,
|
|
93
|
+
targetObject,
|
|
94
|
+
targetField: DEFAULT_EXTERNAL_ID_FIELD,
|
|
95
|
+
fieldType: fieldDef.type
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
nodes.push({ object: objectName, dependsOn, references });
|
|
101
|
+
}
|
|
102
|
+
const { insertOrder, circularDependencies } = this.topologicalSort(nodes);
|
|
103
|
+
return { nodes, insertOrder, circularDependencies };
|
|
104
|
+
}
|
|
105
|
+
async validate(datasets, config) {
|
|
106
|
+
const parsedConfig = SeedLoaderConfigSchema.parse({ ...config, dryRun: true });
|
|
107
|
+
return this.load({ seeds: datasets, config: parsedConfig });
|
|
108
|
+
}
|
|
109
|
+
// ==========================================================================
|
|
110
|
+
// Internal: Seed Loading
|
|
111
|
+
// ==========================================================================
|
|
112
|
+
async loadDataset(dataset, config, refMap, insertedRecords, deferredUpdates, allErrors) {
|
|
113
|
+
const objectName = dataset.object;
|
|
114
|
+
const mode = dataset.mode || config.defaultMode;
|
|
115
|
+
const externalId = dataset.externalId || "name";
|
|
116
|
+
let inserted = 0;
|
|
117
|
+
let updated = 0;
|
|
118
|
+
let skipped = 0;
|
|
119
|
+
let errored = 0;
|
|
120
|
+
let referencesResolved = 0;
|
|
121
|
+
let referencesDeferred = 0;
|
|
122
|
+
const errors = [];
|
|
123
|
+
if (!insertedRecords.has(objectName)) {
|
|
124
|
+
insertedRecords.set(objectName, /* @__PURE__ */ new Map());
|
|
125
|
+
}
|
|
126
|
+
let existingRecords;
|
|
127
|
+
if ((mode === "upsert" || mode === "update" || mode === "ignore") && !config.dryRun) {
|
|
128
|
+
existingRecords = await this.loadExistingRecords(
|
|
129
|
+
objectName,
|
|
130
|
+
externalId,
|
|
131
|
+
config.organizationId
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const objectRefs = refMap.get(objectName) || [];
|
|
135
|
+
const seedNow = /* @__PURE__ */ new Date();
|
|
136
|
+
const seedIdentity = config.identity;
|
|
137
|
+
const baseEvalCtx = {
|
|
138
|
+
now: seedNow,
|
|
139
|
+
user: seedIdentity?.user,
|
|
140
|
+
// Fall back to the per-tenant organizationId so `os.org.id` resolves
|
|
141
|
+
// during per-org replay even without an explicit identity.org.
|
|
142
|
+
org: seedIdentity?.org ?? (config.organizationId ? { id: config.organizationId } : void 0),
|
|
143
|
+
env: config.env
|
|
144
|
+
};
|
|
145
|
+
for (let i = 0; i < dataset.records.length; i++) {
|
|
146
|
+
const seedResult = resolveSeedRecord(
|
|
147
|
+
dataset.records[i],
|
|
148
|
+
baseEvalCtx
|
|
149
|
+
);
|
|
150
|
+
if (!seedResult.ok) {
|
|
151
|
+
errored++;
|
|
152
|
+
const error = {
|
|
153
|
+
sourceObject: objectName,
|
|
154
|
+
field: "(expression)",
|
|
155
|
+
targetObject: objectName,
|
|
156
|
+
targetField: "(expression)",
|
|
157
|
+
attemptedValue: dataset.records[i],
|
|
158
|
+
recordIndex: i,
|
|
159
|
+
message: `Cannot resolve dynamic seed values for ${objectName} record #${i}: ${seedResult.error.message}. Records using cel\`os.user.id\` / cel\`os.org.id\` require a seed identity \u2014 ensure a system/admin user exists before seeding (see SeedLoaderConfig.identity).`
|
|
160
|
+
};
|
|
161
|
+
errors.push(error);
|
|
162
|
+
allErrors.push(error);
|
|
163
|
+
this.logger.warn(`[SeedLoader] ${error.message}`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const record = { ...seedResult.value };
|
|
167
|
+
if (config.organizationId && record["organization_id"] == null) {
|
|
168
|
+
record["organization_id"] = config.organizationId;
|
|
169
|
+
}
|
|
170
|
+
for (const ref of objectRefs) {
|
|
171
|
+
const fieldValue = record[ref.field];
|
|
172
|
+
if (fieldValue === void 0 || fieldValue === null) continue;
|
|
173
|
+
if (typeof fieldValue === "object") {
|
|
174
|
+
const wrapped = fieldValue.externalId;
|
|
175
|
+
const hint = wrapped !== void 0 ? ` Pass the natural key directly: ${ref.field}: ${JSON.stringify(wrapped)}.` : ` Pass the target's ${ref.targetField} value as a plain string.`;
|
|
176
|
+
const error = {
|
|
177
|
+
sourceObject: objectName,
|
|
178
|
+
field: ref.field,
|
|
179
|
+
targetObject: ref.targetObject,
|
|
180
|
+
targetField: ref.targetField,
|
|
181
|
+
attemptedValue: fieldValue,
|
|
182
|
+
recordIndex: i,
|
|
183
|
+
message: `Invalid reference for ${objectName}.${ref.field}: expected a ${ref.targetObject}.${ref.targetField} natural-key string but got an object.${hint}`
|
|
184
|
+
};
|
|
185
|
+
errors.push(error);
|
|
186
|
+
allErrors.push(error);
|
|
187
|
+
this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
|
|
188
|
+
record[ref.field] = null;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (typeof fieldValue !== "string" || this.looksLikeInternalId(fieldValue)) continue;
|
|
192
|
+
const targetMap = insertedRecords.get(ref.targetObject);
|
|
193
|
+
const resolvedId = targetMap?.get(String(fieldValue));
|
|
194
|
+
if (resolvedId) {
|
|
195
|
+
record[ref.field] = resolvedId;
|
|
196
|
+
referencesResolved++;
|
|
197
|
+
} else if (!config.dryRun) {
|
|
198
|
+
const dbId = await this.resolveFromDatabase(ref.targetObject, ref.targetField, fieldValue, config.organizationId);
|
|
199
|
+
if (dbId) {
|
|
200
|
+
record[ref.field] = dbId;
|
|
201
|
+
referencesResolved++;
|
|
202
|
+
} else if (config.multiPass) {
|
|
203
|
+
record[ref.field] = null;
|
|
204
|
+
deferredUpdates.push({
|
|
205
|
+
objectName,
|
|
206
|
+
recordExternalId: String(record[externalId] ?? ""),
|
|
207
|
+
field: ref.field,
|
|
208
|
+
targetObject: ref.targetObject,
|
|
209
|
+
targetField: ref.targetField,
|
|
210
|
+
attemptedValue: fieldValue,
|
|
211
|
+
recordIndex: i
|
|
212
|
+
});
|
|
213
|
+
referencesDeferred++;
|
|
214
|
+
} else {
|
|
215
|
+
const error = {
|
|
216
|
+
sourceObject: objectName,
|
|
217
|
+
field: ref.field,
|
|
218
|
+
targetObject: ref.targetObject,
|
|
219
|
+
targetField: ref.targetField,
|
|
220
|
+
attemptedValue: fieldValue,
|
|
221
|
+
recordIndex: i,
|
|
222
|
+
message: `Cannot resolve reference: ${objectName}.${ref.field} = '${fieldValue}' \u2192 ${ref.targetObject}.${ref.targetField} not found`
|
|
223
|
+
};
|
|
224
|
+
errors.push(error);
|
|
225
|
+
allErrors.push(error);
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
const targetMap2 = insertedRecords.get(ref.targetObject);
|
|
229
|
+
if (!targetMap2?.has(String(fieldValue))) {
|
|
230
|
+
const error = {
|
|
231
|
+
sourceObject: objectName,
|
|
232
|
+
field: ref.field,
|
|
233
|
+
targetObject: ref.targetObject,
|
|
234
|
+
targetField: ref.targetField,
|
|
235
|
+
attemptedValue: fieldValue,
|
|
236
|
+
recordIndex: i,
|
|
237
|
+
message: `[dry-run] Reference may not resolve: ${objectName}.${ref.field} = '${fieldValue}' \u2192 ${ref.targetObject}.${ref.targetField}`
|
|
238
|
+
};
|
|
239
|
+
errors.push(error);
|
|
240
|
+
allErrors.push(error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!config.dryRun) {
|
|
245
|
+
try {
|
|
246
|
+
const result = await this.writeRecord(
|
|
247
|
+
objectName,
|
|
248
|
+
record,
|
|
249
|
+
mode,
|
|
250
|
+
externalId,
|
|
251
|
+
existingRecords
|
|
252
|
+
);
|
|
253
|
+
if (result.action === "inserted") inserted++;
|
|
254
|
+
else if (result.action === "updated") updated++;
|
|
255
|
+
else if (result.action === "skipped") skipped++;
|
|
256
|
+
const externalIdValue = String(record[externalId] ?? "");
|
|
257
|
+
const internalId = result.id;
|
|
258
|
+
if (externalIdValue && internalId) {
|
|
259
|
+
insertedRecords.get(objectName).set(externalIdValue, String(internalId));
|
|
260
|
+
}
|
|
261
|
+
} catch (err) {
|
|
262
|
+
errored++;
|
|
263
|
+
const error = {
|
|
264
|
+
sourceObject: objectName,
|
|
265
|
+
field: "(write)",
|
|
266
|
+
targetObject: objectName,
|
|
267
|
+
targetField: externalId,
|
|
268
|
+
attemptedValue: record[externalId] ?? null,
|
|
269
|
+
recordIndex: i,
|
|
270
|
+
message: `Failed to write ${objectName} record #${i} (${externalId}=${String(record[externalId] ?? "")}): ${err.message}`
|
|
271
|
+
};
|
|
272
|
+
errors.push(error);
|
|
273
|
+
allErrors.push(error);
|
|
274
|
+
this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
const externalIdValue = String(record[externalId] ?? "");
|
|
278
|
+
if (externalIdValue) {
|
|
279
|
+
insertedRecords.get(objectName).set(externalIdValue, `dry-run-id-${i}`);
|
|
280
|
+
}
|
|
281
|
+
inserted++;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
object: objectName,
|
|
286
|
+
mode,
|
|
287
|
+
inserted,
|
|
288
|
+
updated,
|
|
289
|
+
skipped,
|
|
290
|
+
errored,
|
|
291
|
+
total: dataset.records.length,
|
|
292
|
+
referencesResolved,
|
|
293
|
+
referencesDeferred,
|
|
294
|
+
errors
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
// ==========================================================================
|
|
298
|
+
// Internal: Reference Resolution
|
|
299
|
+
// ==========================================================================
|
|
300
|
+
async resolveFromDatabase(targetObject, targetField, value, organizationId) {
|
|
301
|
+
try {
|
|
302
|
+
const where = { [targetField]: value };
|
|
303
|
+
if (organizationId) where.organization_id = organizationId;
|
|
304
|
+
const records = await this.engine.find(targetObject, {
|
|
305
|
+
where,
|
|
306
|
+
fields: ["id"],
|
|
307
|
+
limit: 1,
|
|
308
|
+
context: { isSystem: true }
|
|
309
|
+
});
|
|
310
|
+
if (records && records.length > 0) {
|
|
311
|
+
return String(records[0].id || records[0]._id);
|
|
312
|
+
}
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
async resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, organizationId) {
|
|
318
|
+
for (const deferred of deferredUpdates) {
|
|
319
|
+
const targetMap = insertedRecords.get(deferred.targetObject);
|
|
320
|
+
let resolvedId = targetMap?.get(String(deferred.attemptedValue));
|
|
321
|
+
if (!resolvedId) {
|
|
322
|
+
resolvedId = await this.resolveFromDatabase(
|
|
323
|
+
deferred.targetObject,
|
|
324
|
+
deferred.targetField,
|
|
325
|
+
deferred.attemptedValue,
|
|
326
|
+
organizationId
|
|
327
|
+
) ?? void 0;
|
|
328
|
+
}
|
|
329
|
+
if (resolvedId) {
|
|
330
|
+
const objectRecordMap = insertedRecords.get(deferred.objectName);
|
|
331
|
+
const recordId = objectRecordMap?.get(deferred.recordExternalId);
|
|
332
|
+
if (recordId) {
|
|
333
|
+
try {
|
|
334
|
+
await this.engine.update(deferred.objectName, {
|
|
335
|
+
id: recordId,
|
|
336
|
+
[deferred.field]: resolvedId
|
|
337
|
+
}, { context: { isSystem: true } });
|
|
338
|
+
const resultEntry = allResults.find((r) => r.object === deferred.objectName);
|
|
339
|
+
if (resultEntry) {
|
|
340
|
+
resultEntry.referencesResolved++;
|
|
341
|
+
resultEntry.referencesDeferred--;
|
|
342
|
+
}
|
|
343
|
+
} catch (err) {
|
|
344
|
+
this.logger.warn("[SeedLoader] Failed to resolve deferred reference", {
|
|
345
|
+
object: deferred.objectName,
|
|
346
|
+
field: deferred.field,
|
|
347
|
+
error: err.message
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
const error = {
|
|
353
|
+
sourceObject: deferred.objectName,
|
|
354
|
+
field: deferred.field,
|
|
355
|
+
targetObject: deferred.targetObject,
|
|
356
|
+
targetField: deferred.targetField,
|
|
357
|
+
attemptedValue: deferred.attemptedValue,
|
|
358
|
+
recordIndex: deferred.recordIndex,
|
|
359
|
+
message: `Deferred reference unresolved after pass 2: ${deferred.objectName}.${deferred.field} = '${deferred.attemptedValue}' \u2192 ${deferred.targetObject}.${deferred.targetField} not found`
|
|
360
|
+
};
|
|
361
|
+
const resultEntry = allResults.find((r) => r.object === deferred.objectName);
|
|
362
|
+
if (resultEntry) {
|
|
363
|
+
resultEntry.errors.push(error);
|
|
364
|
+
}
|
|
365
|
+
allErrors.push(error);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async writeRecord(objectName, record, mode, externalId, existingRecords) {
|
|
370
|
+
const externalIdValue = record[externalId];
|
|
371
|
+
const existing = existingRecords?.get(String(externalIdValue ?? ""));
|
|
372
|
+
const opts = _SeedLoaderService.SEED_OPTIONS;
|
|
373
|
+
switch (mode) {
|
|
374
|
+
case "insert": {
|
|
375
|
+
const result = await this.engine.insert(objectName, record, opts);
|
|
376
|
+
return { action: "inserted", id: this.extractId(result) };
|
|
377
|
+
}
|
|
378
|
+
case "update": {
|
|
379
|
+
if (!existing) {
|
|
380
|
+
return { action: "skipped" };
|
|
381
|
+
}
|
|
382
|
+
const id = this.extractId(existing);
|
|
383
|
+
await this.engine.update(objectName, { ...record, id }, opts);
|
|
384
|
+
return { action: "updated", id };
|
|
385
|
+
}
|
|
386
|
+
case "upsert": {
|
|
387
|
+
if (existing) {
|
|
388
|
+
const id = this.extractId(existing);
|
|
389
|
+
await this.engine.update(objectName, { ...record, id }, opts);
|
|
390
|
+
return { action: "updated", id };
|
|
391
|
+
} else {
|
|
392
|
+
const result = await this.engine.insert(objectName, record, opts);
|
|
393
|
+
return { action: "inserted", id: this.extractId(result) };
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
case "ignore": {
|
|
397
|
+
if (existing) {
|
|
398
|
+
return { action: "skipped", id: this.extractId(existing) };
|
|
399
|
+
}
|
|
400
|
+
const result = await this.engine.insert(objectName, record, opts);
|
|
401
|
+
return { action: "inserted", id: this.extractId(result) };
|
|
402
|
+
}
|
|
403
|
+
case "replace": {
|
|
404
|
+
const result = await this.engine.insert(objectName, record, opts);
|
|
405
|
+
return { action: "inserted", id: this.extractId(result) };
|
|
406
|
+
}
|
|
407
|
+
default: {
|
|
408
|
+
const result = await this.engine.insert(objectName, record, opts);
|
|
409
|
+
return { action: "inserted", id: this.extractId(result) };
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// ==========================================================================
|
|
414
|
+
// Internal: Dependency Graph
|
|
415
|
+
// ==========================================================================
|
|
416
|
+
/**
|
|
417
|
+
* Kahn's algorithm for topological sort with cycle detection.
|
|
418
|
+
*/
|
|
419
|
+
topologicalSort(nodes) {
|
|
420
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
421
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
422
|
+
const objectSet = new Set(nodes.map((n) => n.object));
|
|
423
|
+
for (const node of nodes) {
|
|
424
|
+
inDegree.set(node.object, 0);
|
|
425
|
+
adjacency.set(node.object, []);
|
|
426
|
+
}
|
|
427
|
+
for (const node of nodes) {
|
|
428
|
+
for (const dep of node.dependsOn) {
|
|
429
|
+
if (objectSet.has(dep) && dep !== node.object) {
|
|
430
|
+
adjacency.get(dep).push(node.object);
|
|
431
|
+
inDegree.set(node.object, (inDegree.get(node.object) || 0) + 1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const queue = [];
|
|
436
|
+
for (const [obj, degree] of inDegree) {
|
|
437
|
+
if (degree === 0) queue.push(obj);
|
|
438
|
+
}
|
|
439
|
+
const insertOrder = [];
|
|
440
|
+
while (queue.length > 0) {
|
|
441
|
+
const current = queue.shift();
|
|
442
|
+
insertOrder.push(current);
|
|
443
|
+
for (const neighbor of adjacency.get(current) || []) {
|
|
444
|
+
const newDegree = (inDegree.get(neighbor) || 0) - 1;
|
|
445
|
+
inDegree.set(neighbor, newDegree);
|
|
446
|
+
if (newDegree === 0) {
|
|
447
|
+
queue.push(neighbor);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const circularDependencies = [];
|
|
452
|
+
const remaining = nodes.filter((n) => !insertOrder.includes(n.object));
|
|
453
|
+
if (remaining.length > 0) {
|
|
454
|
+
const cycles = this.findCycles(remaining);
|
|
455
|
+
circularDependencies.push(...cycles);
|
|
456
|
+
for (const node of remaining) {
|
|
457
|
+
if (!insertOrder.includes(node.object)) {
|
|
458
|
+
insertOrder.push(node.object);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return { insertOrder, circularDependencies };
|
|
463
|
+
}
|
|
464
|
+
findCycles(nodes) {
|
|
465
|
+
const cycles = [];
|
|
466
|
+
const nodeMap = new Map(nodes.map((n) => [n.object, n]));
|
|
467
|
+
const visited = /* @__PURE__ */ new Set();
|
|
468
|
+
const inStack = /* @__PURE__ */ new Set();
|
|
469
|
+
const dfs = (current, path) => {
|
|
470
|
+
if (inStack.has(current)) {
|
|
471
|
+
const cycleStart = path.indexOf(current);
|
|
472
|
+
if (cycleStart !== -1) {
|
|
473
|
+
cycles.push([...path.slice(cycleStart), current]);
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (visited.has(current)) return;
|
|
478
|
+
visited.add(current);
|
|
479
|
+
inStack.add(current);
|
|
480
|
+
path.push(current);
|
|
481
|
+
const node = nodeMap.get(current);
|
|
482
|
+
if (node) {
|
|
483
|
+
for (const dep of node.dependsOn) {
|
|
484
|
+
if (nodeMap.has(dep)) {
|
|
485
|
+
dfs(dep, [...path]);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
inStack.delete(current);
|
|
490
|
+
};
|
|
491
|
+
for (const node of nodes) {
|
|
492
|
+
if (!visited.has(node.object)) {
|
|
493
|
+
dfs(node.object, []);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return cycles;
|
|
497
|
+
}
|
|
498
|
+
// ==========================================================================
|
|
499
|
+
// Internal: Helpers
|
|
500
|
+
// ==========================================================================
|
|
501
|
+
filterByEnv(datasets, env) {
|
|
502
|
+
if (!env) return datasets;
|
|
503
|
+
return datasets.filter((d) => d.env.includes(env));
|
|
504
|
+
}
|
|
505
|
+
orderDatasets(datasets, insertOrder) {
|
|
506
|
+
const orderMap = new Map(insertOrder.map((name, i) => [name, i]));
|
|
507
|
+
return [...datasets].sort((a, b) => {
|
|
508
|
+
const orderA = orderMap.get(a.object) ?? Number.MAX_SAFE_INTEGER;
|
|
509
|
+
const orderB = orderMap.get(b.object) ?? Number.MAX_SAFE_INTEGER;
|
|
510
|
+
return orderA - orderB;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
buildReferenceMap(graph) {
|
|
514
|
+
const map = /* @__PURE__ */ new Map();
|
|
515
|
+
for (const node of graph.nodes) {
|
|
516
|
+
if (node.references.length > 0) {
|
|
517
|
+
map.set(node.object, node.references);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return map;
|
|
521
|
+
}
|
|
522
|
+
async loadExistingRecords(objectName, externalId, organizationId) {
|
|
523
|
+
const map = /* @__PURE__ */ new Map();
|
|
524
|
+
try {
|
|
525
|
+
const findArgs = {
|
|
526
|
+
fields: ["id", externalId],
|
|
527
|
+
context: { isSystem: true }
|
|
528
|
+
};
|
|
529
|
+
if (organizationId) findArgs.where = { organization_id: organizationId };
|
|
530
|
+
const records = await this.engine.find(objectName, findArgs);
|
|
531
|
+
for (const record of records || []) {
|
|
532
|
+
const key = String(record[externalId] ?? "");
|
|
533
|
+
if (key) {
|
|
534
|
+
map.set(key, record);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
} catch {
|
|
538
|
+
}
|
|
539
|
+
return map;
|
|
540
|
+
}
|
|
541
|
+
looksLikeInternalId(value) {
|
|
542
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
if (/^[0-9a-f]{24}$/i.test(value)) {
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
extractId(record) {
|
|
551
|
+
if (!record) return void 0;
|
|
552
|
+
return String(record.id || record._id || "");
|
|
553
|
+
}
|
|
554
|
+
buildEmptyResult(config, durationMs) {
|
|
555
|
+
return {
|
|
556
|
+
success: true,
|
|
557
|
+
dryRun: config.dryRun,
|
|
558
|
+
dependencyGraph: { nodes: [], insertOrder: [], circularDependencies: [] },
|
|
559
|
+
results: [],
|
|
560
|
+
errors: [],
|
|
561
|
+
summary: {
|
|
562
|
+
objectsProcessed: 0,
|
|
563
|
+
totalRecords: 0,
|
|
564
|
+
totalInserted: 0,
|
|
565
|
+
totalUpdated: 0,
|
|
566
|
+
totalSkipped: 0,
|
|
567
|
+
totalErrored: 0,
|
|
568
|
+
totalReferencesResolved: 0,
|
|
569
|
+
totalReferencesDeferred: 0,
|
|
570
|
+
circularDependencyCount: 0,
|
|
571
|
+
durationMs
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
buildResult(config, graph, results, errors, durationMs) {
|
|
576
|
+
const summary = {
|
|
577
|
+
objectsProcessed: results.length,
|
|
578
|
+
totalRecords: results.reduce((sum, r) => sum + r.total, 0),
|
|
579
|
+
totalInserted: results.reduce((sum, r) => sum + r.inserted, 0),
|
|
580
|
+
totalUpdated: results.reduce((sum, r) => sum + r.updated, 0),
|
|
581
|
+
totalSkipped: results.reduce((sum, r) => sum + r.skipped, 0),
|
|
582
|
+
totalErrored: results.reduce((sum, r) => sum + r.errored, 0),
|
|
583
|
+
totalReferencesResolved: results.reduce((sum, r) => sum + r.referencesResolved, 0),
|
|
584
|
+
totalReferencesDeferred: results.reduce((sum, r) => sum + r.referencesDeferred, 0),
|
|
585
|
+
circularDependencyCount: graph.circularDependencies.length,
|
|
586
|
+
durationMs
|
|
587
|
+
};
|
|
588
|
+
const hasErrors = errors.length > 0 || summary.totalErrored > 0;
|
|
589
|
+
return {
|
|
590
|
+
success: !hasErrors,
|
|
591
|
+
dryRun: config.dryRun,
|
|
592
|
+
dependencyGraph: graph,
|
|
593
|
+
results,
|
|
594
|
+
errors,
|
|
595
|
+
summary
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
// ==========================================================================
|
|
600
|
+
// Internal: Write Operations
|
|
601
|
+
// ==========================================================================
|
|
602
|
+
/**
|
|
603
|
+
* Seed writes always run as a privileged system context. This bypasses
|
|
604
|
+
* RBAC checks (so seeds can target system tables like `sys_*`) and
|
|
605
|
+
* disables the SecurityPlugin's auto-injection of `organization_id` /
|
|
606
|
+
* `owner_id` — seeds either declare those fields explicitly per
|
|
607
|
+
* record, or are intentionally cross-tenant / global.
|
|
608
|
+
*/
|
|
609
|
+
_SeedLoaderService.SEED_OPTIONS = { context: { isSystem: true } };
|
|
610
|
+
SeedLoaderService = _SeedLoaderService;
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// src/build-probes.ts
|
|
615
|
+
var build_probes_exports = {};
|
|
616
|
+
__export(build_probes_exports, {
|
|
617
|
+
runBuildProbes: () => runBuildProbes
|
|
618
|
+
});
|
|
619
|
+
function asRec(v) {
|
|
620
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
621
|
+
}
|
|
622
|
+
function asArr(v) {
|
|
623
|
+
return Array.isArray(v) ? v : [];
|
|
624
|
+
}
|
|
625
|
+
function resultRows(result) {
|
|
626
|
+
if (Array.isArray(result)) return result;
|
|
627
|
+
const r = asRec(result);
|
|
628
|
+
if (!r) return [];
|
|
629
|
+
if (Array.isArray(r.rows)) return r.rows;
|
|
630
|
+
if (Array.isArray(r.data)) return r.data;
|
|
631
|
+
return [];
|
|
632
|
+
}
|
|
633
|
+
async function hasRows(engine, objectName, organizationId) {
|
|
634
|
+
const rows = await engine.find(objectName, {
|
|
635
|
+
fields: ["id"],
|
|
636
|
+
limit: 1,
|
|
637
|
+
...organizationId ? { where: { organization_id: organizationId } } : {},
|
|
638
|
+
context: { isSystem: true }
|
|
639
|
+
});
|
|
640
|
+
return Array.isArray(rows) && rows.length > 0;
|
|
641
|
+
}
|
|
642
|
+
async function runBuildProbes(opts) {
|
|
643
|
+
const issues = [];
|
|
644
|
+
const checked = { seeds: 0, views: 0, widgets: 0 };
|
|
645
|
+
const { engine, getItem, published, analytics, organizationId } = opts;
|
|
646
|
+
const itemCache = /* @__PURE__ */ new Map();
|
|
647
|
+
const readItem = async (type, name) => {
|
|
648
|
+
const key = `${type} ${name}`;
|
|
649
|
+
if (itemCache.has(key)) return itemCache.get(key);
|
|
650
|
+
let item;
|
|
651
|
+
try {
|
|
652
|
+
item = await getItem(type, name);
|
|
653
|
+
} catch {
|
|
654
|
+
item = void 0;
|
|
655
|
+
}
|
|
656
|
+
itemCache.set(key, item);
|
|
657
|
+
return item;
|
|
658
|
+
};
|
|
659
|
+
for (const p of published.filter((x) => x.type === "seed")) {
|
|
660
|
+
const body = asRec(await readItem("seed", p.name));
|
|
661
|
+
const objectName = typeof body?.object === "string" ? body.object : void 0;
|
|
662
|
+
if (!objectName) continue;
|
|
663
|
+
checked.seeds += 1;
|
|
664
|
+
try {
|
|
665
|
+
if (!await hasRows(engine, objectName, organizationId)) {
|
|
666
|
+
issues.push({
|
|
667
|
+
layer: "runtime",
|
|
668
|
+
severity: "error",
|
|
669
|
+
artifact: { type: "seed", name: p.name },
|
|
670
|
+
ref: { type: "object", name: objectName },
|
|
671
|
+
code: "seed_not_applied",
|
|
672
|
+
message: `Seed "${p.name}" was published but object "${objectName}" has no rows \u2014 the sample data never materialized.`,
|
|
673
|
+
fix: `Check the publish response's seedApplied for the load error, fix the seed rows (field names/types), and republish the seed.`
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
} catch (e) {
|
|
677
|
+
issues.push({
|
|
678
|
+
layer: "runtime",
|
|
679
|
+
severity: "error",
|
|
680
|
+
artifact: { type: "seed", name: p.name },
|
|
681
|
+
ref: { type: "object", name: objectName },
|
|
682
|
+
code: "seed_not_applied",
|
|
683
|
+
message: `Seed "${p.name}" probe could not read object "${objectName}": ${String(e?.message ?? e)}`
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
for (const p of published.filter((x) => x.type === "view")) {
|
|
688
|
+
const body = asRec(await readItem("view", p.name));
|
|
689
|
+
const config = asRec(body?.config);
|
|
690
|
+
const dataObj = asRec(config?.data)?.object;
|
|
691
|
+
const objectName = typeof body?.object === "string" ? body.object : typeof dataObj === "string" ? dataObj : void 0;
|
|
692
|
+
if (!objectName) continue;
|
|
693
|
+
checked.views += 1;
|
|
694
|
+
try {
|
|
695
|
+
await engine.find(objectName, {
|
|
696
|
+
fields: ["id"],
|
|
697
|
+
limit: 1,
|
|
698
|
+
...organizationId ? { where: { organization_id: organizationId } } : {},
|
|
699
|
+
context: { isSystem: true }
|
|
700
|
+
});
|
|
701
|
+
} catch (e) {
|
|
702
|
+
issues.push({
|
|
703
|
+
layer: "runtime",
|
|
704
|
+
severity: "error",
|
|
705
|
+
artifact: { type: "view", name: p.name },
|
|
706
|
+
ref: { type: "object", name: objectName },
|
|
707
|
+
code: "view_read_failed",
|
|
708
|
+
message: `View "${p.name}" cannot read object "${objectName}": ${String(e?.message ?? e)} \u2014 it will render as an error for every user.`,
|
|
709
|
+
fix: `Verify object "${objectName}" published successfully (its table must exist) and that the view's binding is correct.`
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const dashboards = published.filter((x) => x.type === "dashboard");
|
|
714
|
+
let widgetsToProbe = 0;
|
|
715
|
+
for (const p of dashboards) {
|
|
716
|
+
const body = asRec(await readItem("dashboard", p.name));
|
|
717
|
+
const widgets = asArr(body?.widgets).map(asRec).filter((w) => !!w);
|
|
718
|
+
const datasetBound = widgets.filter((w) => typeof w.dataset === "string" && w.dataset);
|
|
719
|
+
widgetsToProbe += datasetBound.length;
|
|
720
|
+
if (!analytics || typeof analytics.queryDataset !== "function") continue;
|
|
721
|
+
for (const w of datasetBound) {
|
|
722
|
+
const widgetId = String(w.id ?? w.title ?? "?");
|
|
723
|
+
const dsName = w.dataset;
|
|
724
|
+
const dataset = asRec(await readItem("dataset", dsName));
|
|
725
|
+
if (!dataset) continue;
|
|
726
|
+
checked.widgets += 1;
|
|
727
|
+
const measures = asArr(w.values).filter((v) => typeof v === "string" && v.length > 0);
|
|
728
|
+
const firstMeasure = asRec(asArr(dataset.measures)[0])?.name;
|
|
729
|
+
const selection = {
|
|
730
|
+
measures: measures.length ? measures : typeof firstMeasure === "string" ? [firstMeasure] : [],
|
|
731
|
+
dimensions: [],
|
|
732
|
+
limit: 1
|
|
733
|
+
};
|
|
734
|
+
if (selection.measures.length === 0) continue;
|
|
735
|
+
const objectName = typeof dataset.object === "string" ? dataset.object : void 0;
|
|
736
|
+
try {
|
|
737
|
+
const result = await analytics.queryDataset(dataset, selection, void 0);
|
|
738
|
+
const rows = resultRows(result);
|
|
739
|
+
if (rows.length === 0 && objectName && await hasRows(engine, objectName, organizationId)) {
|
|
740
|
+
issues.push({
|
|
741
|
+
layer: "runtime",
|
|
742
|
+
severity: "error",
|
|
743
|
+
artifact: { type: "dashboard", name: p.name },
|
|
744
|
+
ref: { type: "dataset", name: dsName, member: widgetId },
|
|
745
|
+
code: "empty_query",
|
|
746
|
+
message: `Dashboard "${p.name}" widget "${widgetId}" returns NO data from dataset "${dsName}" although object "${objectName}" has rows \u2014 the widget will render empty for every user.`,
|
|
747
|
+
fix: `Run the dataset query directly to see the compiled strategy/SQL; check the dataset's measure/dimension field bindings against object "${objectName}".`
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
} catch (e) {
|
|
751
|
+
issues.push({
|
|
752
|
+
layer: "runtime",
|
|
753
|
+
severity: "error",
|
|
754
|
+
artifact: { type: "dashboard", name: p.name },
|
|
755
|
+
ref: { type: "dataset", name: dsName, member: widgetId },
|
|
756
|
+
code: "widget_query_failed",
|
|
757
|
+
message: `Dashboard "${p.name}" widget "${widgetId}" query against dataset "${dsName}" failed: ${String(e?.message ?? e)}`,
|
|
758
|
+
fix: `Fix the dataset definition (or the widget's values/dimensions) so the query compiles, then republish.`
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (widgetsToProbe > 0 && (!analytics || typeof analytics.queryDataset !== "function")) {
|
|
764
|
+
issues.push({
|
|
765
|
+
layer: "runtime",
|
|
766
|
+
severity: "warning",
|
|
767
|
+
artifact: { type: "dashboard", name: dashboards.map((d) => d.name).join(", ") },
|
|
768
|
+
code: "probes_unavailable",
|
|
769
|
+
message: `${widgetsToProbe} dashboard widget(s) could not be probed: no analytics service is mounted on this kernel.`
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
return { issues, checked };
|
|
773
|
+
}
|
|
774
|
+
var init_build_probes = __esm({
|
|
775
|
+
"src/build-probes.ts"() {
|
|
776
|
+
"use strict";
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
|
|
1
780
|
// src/registry.ts
|
|
2
781
|
import { ObjectSchema } from "@objectstack/spec/data";
|
|
3
782
|
import { readEnvWithDeprecation } from "@objectstack/types";
|
|
@@ -4215,12 +4994,16 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
4215
4994
|
item: result.item.body
|
|
4216
4995
|
});
|
|
4217
4996
|
await this.ensureObjectStorage(request.type, request.name);
|
|
4218
|
-
|
|
4997
|
+
const response = {
|
|
4219
4998
|
success: true,
|
|
4220
4999
|
version: result.version,
|
|
4221
5000
|
seq: result.seq,
|
|
4222
5001
|
message: `Published draft \u2014 type=${request.type}, name=${request.name} [seq=${result.seq}]`
|
|
4223
5002
|
};
|
|
5003
|
+
if (singularType === "seed" && !request._skipSeedApply) {
|
|
5004
|
+
response.seedApplied = await this.applySeedBodies([result.item.body], orgId);
|
|
5005
|
+
}
|
|
5006
|
+
return response;
|
|
4224
5007
|
} catch (err) {
|
|
4225
5008
|
if (err instanceof ConflictError2) {
|
|
4226
5009
|
const conflict = new Error(
|
|
@@ -4235,6 +5018,58 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
4235
5018
|
throw err;
|
|
4236
5019
|
}
|
|
4237
5020
|
}
|
|
5021
|
+
/**
|
|
5022
|
+
* Materialize published `seed` bodies into data rows via the SeedLoaderService
|
|
5023
|
+
* (externalId-keyed upsert, multi-pass for cross-seed references). Passing ALL
|
|
5024
|
+
* of a publish's seed bodies in ONE call lets a child seed reference a parent
|
|
5025
|
+
* seed's rows regardless of publish order. Best-effort: any failure is
|
|
5026
|
+
* returned, never thrown — publishing metadata must not be blocked by a data
|
|
5027
|
+
* problem, but the caller surfaces `seedApplied` so the failure is LOUD.
|
|
5028
|
+
*/
|
|
5029
|
+
async applySeedBodies(bodies, organizationId) {
|
|
5030
|
+
try {
|
|
5031
|
+
const seeds = bodies.filter(
|
|
5032
|
+
(b) => b && typeof b.object === "string" && Array.isArray(b.records)
|
|
5033
|
+
);
|
|
5034
|
+
if (seeds.length === 0) {
|
|
5035
|
+
return { success: false, inserted: 0, updated: 0, error: "seed apply: no readable seed bodies" };
|
|
5036
|
+
}
|
|
5037
|
+
const { SeedLoaderService: SeedLoaderService2 } = await Promise.resolve().then(() => (init_seed_loader(), seed_loader_exports));
|
|
5038
|
+
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
5039
|
+
const metadataAdapter = {
|
|
5040
|
+
getObject: async (name) => {
|
|
5041
|
+
const wrapper = await this.getMetaItem({
|
|
5042
|
+
type: "object",
|
|
5043
|
+
name,
|
|
5044
|
+
...organizationId ? { organizationId } : {}
|
|
5045
|
+
});
|
|
5046
|
+
return wrapper?.item ?? wrapper ?? null;
|
|
5047
|
+
}
|
|
5048
|
+
};
|
|
5049
|
+
const loader = new SeedLoaderService2(
|
|
5050
|
+
this.engine,
|
|
5051
|
+
metadataAdapter,
|
|
5052
|
+
console
|
|
5053
|
+
);
|
|
5054
|
+
const request = SeedLoaderRequestSchema.parse({
|
|
5055
|
+
seeds,
|
|
5056
|
+
config: {
|
|
5057
|
+
defaultMode: "upsert",
|
|
5058
|
+
multiPass: true,
|
|
5059
|
+
...organizationId ? { organizationId } : {}
|
|
5060
|
+
}
|
|
5061
|
+
});
|
|
5062
|
+
const r = await loader.load(request);
|
|
5063
|
+
return {
|
|
5064
|
+
success: r.success,
|
|
5065
|
+
inserted: r.summary.totalInserted,
|
|
5066
|
+
updated: r.summary.totalUpdated,
|
|
5067
|
+
...r.errors?.length ? { errors: r.errors } : {}
|
|
5068
|
+
};
|
|
5069
|
+
} catch (e) {
|
|
5070
|
+
return { success: false, inserted: 0, updated: 0, error: e?.message ?? "seed apply failed" };
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
4238
5073
|
/**
|
|
4239
5074
|
* List pending DRAFT metadata (ADR-0033) for the org, optionally narrowed
|
|
4240
5075
|
* by `packageId` and/or `type`. The list reads of `getMetaItems` only see
|
|
@@ -4268,14 +5103,25 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
4268
5103
|
const drafts = await repo.listDrafts({ packageId: request.packageId });
|
|
4269
5104
|
const published = [];
|
|
4270
5105
|
const failed = [];
|
|
4271
|
-
|
|
5106
|
+
const ordered = [
|
|
5107
|
+
...drafts.filter((d) => d.type !== "seed"),
|
|
5108
|
+
...drafts.filter((d) => d.type === "seed")
|
|
5109
|
+
];
|
|
5110
|
+
const seedBodies = [];
|
|
5111
|
+
for (const d of ordered) {
|
|
4272
5112
|
try {
|
|
5113
|
+
if (d.type === "seed") {
|
|
5114
|
+
const ref = { type: d.type, name: d.name, org: orgId ?? "env" };
|
|
5115
|
+
const draft = await repo.get(ref, { state: "draft" });
|
|
5116
|
+
if (draft?.body) seedBodies.push(draft.body);
|
|
5117
|
+
}
|
|
4273
5118
|
const r = await this.publishMetaItem({
|
|
4274
5119
|
type: d.type,
|
|
4275
5120
|
name: d.name,
|
|
4276
5121
|
...request.organizationId ? { organizationId: request.organizationId } : {},
|
|
4277
5122
|
...request.actor ? { actor: request.actor } : {},
|
|
4278
|
-
message: `publish app package '${request.packageId}'
|
|
5123
|
+
message: `publish app package '${request.packageId}'`,
|
|
5124
|
+
_skipSeedApply: true
|
|
4279
5125
|
});
|
|
4280
5126
|
published.push({ type: d.type, name: d.name, version: r.version });
|
|
4281
5127
|
} catch (e) {
|
|
@@ -4287,12 +5133,38 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
4287
5133
|
});
|
|
4288
5134
|
}
|
|
4289
5135
|
}
|
|
5136
|
+
const seedApplied = seedBodies.length > 0 ? await this.applySeedBodies(seedBodies, orgId) : void 0;
|
|
5137
|
+
let probes;
|
|
5138
|
+
if (published.length > 0) {
|
|
5139
|
+
try {
|
|
5140
|
+
const { runBuildProbes: runBuildProbes2 } = await Promise.resolve().then(() => (init_build_probes(), build_probes_exports));
|
|
5141
|
+
const analytics = this.getServicesRegistry?.().get("analytics");
|
|
5142
|
+
probes = await runBuildProbes2({
|
|
5143
|
+
engine: this.engine,
|
|
5144
|
+
getItem: async (type, name) => {
|
|
5145
|
+
const wrapper = await this.getMetaItem({
|
|
5146
|
+
type,
|
|
5147
|
+
name,
|
|
5148
|
+
...orgId ? { organizationId: orgId } : {}
|
|
5149
|
+
});
|
|
5150
|
+
return wrapper?.item ?? wrapper ?? void 0;
|
|
5151
|
+
},
|
|
5152
|
+
published,
|
|
5153
|
+
...analytics && typeof analytics.queryDataset === "function" ? { analytics } : {},
|
|
5154
|
+
organizationId: orgId
|
|
5155
|
+
});
|
|
5156
|
+
} catch {
|
|
5157
|
+
probes = void 0;
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
4290
5160
|
return {
|
|
4291
5161
|
success: failed.length === 0 && published.length > 0,
|
|
4292
5162
|
publishedCount: published.length,
|
|
4293
5163
|
failedCount: failed.length,
|
|
4294
5164
|
published,
|
|
4295
|
-
failed
|
|
5165
|
+
failed,
|
|
5166
|
+
...seedApplied ? { seedApplied } : {},
|
|
5167
|
+
...probes ? { probes } : {}
|
|
4296
5168
|
};
|
|
4297
5169
|
}
|
|
4298
5170
|
/**
|
|
@@ -9156,6 +10028,10 @@ function convertIntrospectedSchemaToObjects(introspectedSchema, options) {
|
|
|
9156
10028
|
}
|
|
9157
10029
|
return objects;
|
|
9158
10030
|
}
|
|
10031
|
+
|
|
10032
|
+
// src/index.ts
|
|
10033
|
+
init_seed_loader();
|
|
10034
|
+
init_build_probes();
|
|
9159
10035
|
export {
|
|
9160
10036
|
DEFAULT_EXTENDER_PRIORITY,
|
|
9161
10037
|
DEFAULT_OWNER_PRIORITY,
|
|
@@ -9170,6 +10046,7 @@ export {
|
|
|
9170
10046
|
SECRET_REF_PREFIX,
|
|
9171
10047
|
SchemaRegistry,
|
|
9172
10048
|
ScopedContext,
|
|
10049
|
+
SeedLoaderService,
|
|
9173
10050
|
SysMetadataRepository,
|
|
9174
10051
|
ValidationError,
|
|
9175
10052
|
applyInMemoryAggregation,
|
|
@@ -9188,6 +10065,7 @@ export {
|
|
|
9188
10065
|
noopHookMetricsRecorder,
|
|
9189
10066
|
parseFQN,
|
|
9190
10067
|
parseSecretRef,
|
|
10068
|
+
runBuildProbes,
|
|
9191
10069
|
toTitleCase,
|
|
9192
10070
|
validateRecord,
|
|
9193
10071
|
wrapDeclarativeHook
|