@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.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +30,775 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/seed-loader.ts
34
+ var seed_loader_exports = {};
35
+ __export(seed_loader_exports, {
36
+ SeedLoaderService: () => SeedLoaderService
37
+ });
38
+ var import_data2, import_formula, DEFAULT_EXTERNAL_ID_FIELD, _SeedLoaderService, SeedLoaderService;
39
+ var init_seed_loader = __esm({
40
+ "src/seed-loader.ts"() {
41
+ "use strict";
42
+ import_data2 = require("@objectstack/spec/data");
43
+ import_formula = require("@objectstack/formula");
44
+ DEFAULT_EXTERNAL_ID_FIELD = "name";
45
+ _SeedLoaderService = class _SeedLoaderService {
46
+ constructor(engine, metadata, logger) {
47
+ this.engine = engine;
48
+ this.metadata = metadata;
49
+ this.logger = logger;
50
+ }
51
+ // ==========================================================================
52
+ // Public API
53
+ // ==========================================================================
54
+ async load(request) {
55
+ const startTime = Date.now();
56
+ const config = request.config;
57
+ const allErrors = [];
58
+ const allResults = [];
59
+ const datasets = this.filterByEnv(request.seeds, config.env);
60
+ if (datasets.length === 0) {
61
+ return this.buildEmptyResult(config, Date.now() - startTime);
62
+ }
63
+ const objectNames = datasets.map((d) => d.object);
64
+ const graph = await this.buildDependencyGraph(objectNames);
65
+ this.logger.info("[SeedLoader] Dependency graph built", {
66
+ objects: objectNames.length,
67
+ insertOrder: graph.insertOrder,
68
+ circularDeps: graph.circularDependencies.length
69
+ });
70
+ const orderedDatasets = this.orderDatasets(datasets, graph.insertOrder);
71
+ const refMap = this.buildReferenceMap(graph);
72
+ const insertedRecords = /* @__PURE__ */ new Map();
73
+ const deferredUpdates = [];
74
+ for (const dataset of orderedDatasets) {
75
+ const result = await this.loadDataset(
76
+ dataset,
77
+ config,
78
+ refMap,
79
+ insertedRecords,
80
+ deferredUpdates,
81
+ allErrors
82
+ );
83
+ allResults.push(result);
84
+ if (config.haltOnError && result.errored > 0) {
85
+ this.logger.warn("[SeedLoader] Halting on first error", { object: dataset.object });
86
+ break;
87
+ }
88
+ }
89
+ if (config.multiPass && deferredUpdates.length > 0 && !config.dryRun) {
90
+ this.logger.info("[SeedLoader] Pass 2: resolving deferred references", {
91
+ count: deferredUpdates.length
92
+ });
93
+ await this.resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, config.organizationId);
94
+ }
95
+ const durationMs = Date.now() - startTime;
96
+ return this.buildResult(config, graph, allResults, allErrors, durationMs);
97
+ }
98
+ async buildDependencyGraph(objectNames) {
99
+ const nodes = [];
100
+ const objectSet = new Set(objectNames);
101
+ for (const objectName of objectNames) {
102
+ const objDef = await this.metadata.getObject(objectName);
103
+ const dependsOn = [];
104
+ const references = [];
105
+ if (objDef && objDef.fields) {
106
+ const fields = objDef.fields;
107
+ for (const [fieldName, fieldDef] of Object.entries(fields)) {
108
+ if ((fieldDef.type === "lookup" || fieldDef.type === "master_detail") && fieldDef.reference) {
109
+ const targetObject = fieldDef.reference;
110
+ if (objectSet.has(targetObject) && !dependsOn.includes(targetObject)) {
111
+ dependsOn.push(targetObject);
112
+ }
113
+ references.push({
114
+ field: fieldName,
115
+ targetObject,
116
+ targetField: DEFAULT_EXTERNAL_ID_FIELD,
117
+ fieldType: fieldDef.type
118
+ });
119
+ }
120
+ }
121
+ }
122
+ nodes.push({ object: objectName, dependsOn, references });
123
+ }
124
+ const { insertOrder, circularDependencies } = this.topologicalSort(nodes);
125
+ return { nodes, insertOrder, circularDependencies };
126
+ }
127
+ async validate(datasets, config) {
128
+ const parsedConfig = import_data2.SeedLoaderConfigSchema.parse({ ...config, dryRun: true });
129
+ return this.load({ seeds: datasets, config: parsedConfig });
130
+ }
131
+ // ==========================================================================
132
+ // Internal: Seed Loading
133
+ // ==========================================================================
134
+ async loadDataset(dataset, config, refMap, insertedRecords, deferredUpdates, allErrors) {
135
+ const objectName = dataset.object;
136
+ const mode = dataset.mode || config.defaultMode;
137
+ const externalId = dataset.externalId || "name";
138
+ let inserted = 0;
139
+ let updated = 0;
140
+ let skipped = 0;
141
+ let errored = 0;
142
+ let referencesResolved = 0;
143
+ let referencesDeferred = 0;
144
+ const errors = [];
145
+ if (!insertedRecords.has(objectName)) {
146
+ insertedRecords.set(objectName, /* @__PURE__ */ new Map());
147
+ }
148
+ let existingRecords;
149
+ if ((mode === "upsert" || mode === "update" || mode === "ignore") && !config.dryRun) {
150
+ existingRecords = await this.loadExistingRecords(
151
+ objectName,
152
+ externalId,
153
+ config.organizationId
154
+ );
155
+ }
156
+ const objectRefs = refMap.get(objectName) || [];
157
+ const seedNow = /* @__PURE__ */ new Date();
158
+ const seedIdentity = config.identity;
159
+ const baseEvalCtx = {
160
+ now: seedNow,
161
+ user: seedIdentity?.user,
162
+ // Fall back to the per-tenant organizationId so `os.org.id` resolves
163
+ // during per-org replay even without an explicit identity.org.
164
+ org: seedIdentity?.org ?? (config.organizationId ? { id: config.organizationId } : void 0),
165
+ env: config.env
166
+ };
167
+ for (let i = 0; i < dataset.records.length; i++) {
168
+ const seedResult = (0, import_formula.resolveSeedRecord)(
169
+ dataset.records[i],
170
+ baseEvalCtx
171
+ );
172
+ if (!seedResult.ok) {
173
+ errored++;
174
+ const error = {
175
+ sourceObject: objectName,
176
+ field: "(expression)",
177
+ targetObject: objectName,
178
+ targetField: "(expression)",
179
+ attemptedValue: dataset.records[i],
180
+ recordIndex: i,
181
+ 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).`
182
+ };
183
+ errors.push(error);
184
+ allErrors.push(error);
185
+ this.logger.warn(`[SeedLoader] ${error.message}`);
186
+ continue;
187
+ }
188
+ const record = { ...seedResult.value };
189
+ if (config.organizationId && record["organization_id"] == null) {
190
+ record["organization_id"] = config.organizationId;
191
+ }
192
+ for (const ref of objectRefs) {
193
+ const fieldValue = record[ref.field];
194
+ if (fieldValue === void 0 || fieldValue === null) continue;
195
+ if (typeof fieldValue === "object") {
196
+ const wrapped = fieldValue.externalId;
197
+ 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.`;
198
+ const error = {
199
+ sourceObject: objectName,
200
+ field: ref.field,
201
+ targetObject: ref.targetObject,
202
+ targetField: ref.targetField,
203
+ attemptedValue: fieldValue,
204
+ recordIndex: i,
205
+ message: `Invalid reference for ${objectName}.${ref.field}: expected a ${ref.targetObject}.${ref.targetField} natural-key string but got an object.${hint}`
206
+ };
207
+ errors.push(error);
208
+ allErrors.push(error);
209
+ this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
210
+ record[ref.field] = null;
211
+ continue;
212
+ }
213
+ if (typeof fieldValue !== "string" || this.looksLikeInternalId(fieldValue)) continue;
214
+ const targetMap = insertedRecords.get(ref.targetObject);
215
+ const resolvedId = targetMap?.get(String(fieldValue));
216
+ if (resolvedId) {
217
+ record[ref.field] = resolvedId;
218
+ referencesResolved++;
219
+ } else if (!config.dryRun) {
220
+ const dbId = await this.resolveFromDatabase(ref.targetObject, ref.targetField, fieldValue, config.organizationId);
221
+ if (dbId) {
222
+ record[ref.field] = dbId;
223
+ referencesResolved++;
224
+ } else if (config.multiPass) {
225
+ record[ref.field] = null;
226
+ deferredUpdates.push({
227
+ objectName,
228
+ recordExternalId: String(record[externalId] ?? ""),
229
+ field: ref.field,
230
+ targetObject: ref.targetObject,
231
+ targetField: ref.targetField,
232
+ attemptedValue: fieldValue,
233
+ recordIndex: i
234
+ });
235
+ referencesDeferred++;
236
+ } else {
237
+ const error = {
238
+ sourceObject: objectName,
239
+ field: ref.field,
240
+ targetObject: ref.targetObject,
241
+ targetField: ref.targetField,
242
+ attemptedValue: fieldValue,
243
+ recordIndex: i,
244
+ message: `Cannot resolve reference: ${objectName}.${ref.field} = '${fieldValue}' \u2192 ${ref.targetObject}.${ref.targetField} not found`
245
+ };
246
+ errors.push(error);
247
+ allErrors.push(error);
248
+ }
249
+ } else {
250
+ const targetMap2 = insertedRecords.get(ref.targetObject);
251
+ if (!targetMap2?.has(String(fieldValue))) {
252
+ const error = {
253
+ sourceObject: objectName,
254
+ field: ref.field,
255
+ targetObject: ref.targetObject,
256
+ targetField: ref.targetField,
257
+ attemptedValue: fieldValue,
258
+ recordIndex: i,
259
+ message: `[dry-run] Reference may not resolve: ${objectName}.${ref.field} = '${fieldValue}' \u2192 ${ref.targetObject}.${ref.targetField}`
260
+ };
261
+ errors.push(error);
262
+ allErrors.push(error);
263
+ }
264
+ }
265
+ }
266
+ if (!config.dryRun) {
267
+ try {
268
+ const result = await this.writeRecord(
269
+ objectName,
270
+ record,
271
+ mode,
272
+ externalId,
273
+ existingRecords
274
+ );
275
+ if (result.action === "inserted") inserted++;
276
+ else if (result.action === "updated") updated++;
277
+ else if (result.action === "skipped") skipped++;
278
+ const externalIdValue = String(record[externalId] ?? "");
279
+ const internalId = result.id;
280
+ if (externalIdValue && internalId) {
281
+ insertedRecords.get(objectName).set(externalIdValue, String(internalId));
282
+ }
283
+ } catch (err) {
284
+ errored++;
285
+ const error = {
286
+ sourceObject: objectName,
287
+ field: "(write)",
288
+ targetObject: objectName,
289
+ targetField: externalId,
290
+ attemptedValue: record[externalId] ?? null,
291
+ recordIndex: i,
292
+ message: `Failed to write ${objectName} record #${i} (${externalId}=${String(record[externalId] ?? "")}): ${err.message}`
293
+ };
294
+ errors.push(error);
295
+ allErrors.push(error);
296
+ this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
297
+ }
298
+ } else {
299
+ const externalIdValue = String(record[externalId] ?? "");
300
+ if (externalIdValue) {
301
+ insertedRecords.get(objectName).set(externalIdValue, `dry-run-id-${i}`);
302
+ }
303
+ inserted++;
304
+ }
305
+ }
306
+ return {
307
+ object: objectName,
308
+ mode,
309
+ inserted,
310
+ updated,
311
+ skipped,
312
+ errored,
313
+ total: dataset.records.length,
314
+ referencesResolved,
315
+ referencesDeferred,
316
+ errors
317
+ };
318
+ }
319
+ // ==========================================================================
320
+ // Internal: Reference Resolution
321
+ // ==========================================================================
322
+ async resolveFromDatabase(targetObject, targetField, value, organizationId) {
323
+ try {
324
+ const where = { [targetField]: value };
325
+ if (organizationId) where.organization_id = organizationId;
326
+ const records = await this.engine.find(targetObject, {
327
+ where,
328
+ fields: ["id"],
329
+ limit: 1,
330
+ context: { isSystem: true }
331
+ });
332
+ if (records && records.length > 0) {
333
+ return String(records[0].id || records[0]._id);
334
+ }
335
+ } catch {
336
+ }
337
+ return null;
338
+ }
339
+ async resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, organizationId) {
340
+ for (const deferred of deferredUpdates) {
341
+ const targetMap = insertedRecords.get(deferred.targetObject);
342
+ let resolvedId = targetMap?.get(String(deferred.attemptedValue));
343
+ if (!resolvedId) {
344
+ resolvedId = await this.resolveFromDatabase(
345
+ deferred.targetObject,
346
+ deferred.targetField,
347
+ deferred.attemptedValue,
348
+ organizationId
349
+ ) ?? void 0;
350
+ }
351
+ if (resolvedId) {
352
+ const objectRecordMap = insertedRecords.get(deferred.objectName);
353
+ const recordId = objectRecordMap?.get(deferred.recordExternalId);
354
+ if (recordId) {
355
+ try {
356
+ await this.engine.update(deferred.objectName, {
357
+ id: recordId,
358
+ [deferred.field]: resolvedId
359
+ }, { context: { isSystem: true } });
360
+ const resultEntry = allResults.find((r) => r.object === deferred.objectName);
361
+ if (resultEntry) {
362
+ resultEntry.referencesResolved++;
363
+ resultEntry.referencesDeferred--;
364
+ }
365
+ } catch (err) {
366
+ this.logger.warn("[SeedLoader] Failed to resolve deferred reference", {
367
+ object: deferred.objectName,
368
+ field: deferred.field,
369
+ error: err.message
370
+ });
371
+ }
372
+ }
373
+ } else {
374
+ const error = {
375
+ sourceObject: deferred.objectName,
376
+ field: deferred.field,
377
+ targetObject: deferred.targetObject,
378
+ targetField: deferred.targetField,
379
+ attemptedValue: deferred.attemptedValue,
380
+ recordIndex: deferred.recordIndex,
381
+ message: `Deferred reference unresolved after pass 2: ${deferred.objectName}.${deferred.field} = '${deferred.attemptedValue}' \u2192 ${deferred.targetObject}.${deferred.targetField} not found`
382
+ };
383
+ const resultEntry = allResults.find((r) => r.object === deferred.objectName);
384
+ if (resultEntry) {
385
+ resultEntry.errors.push(error);
386
+ }
387
+ allErrors.push(error);
388
+ }
389
+ }
390
+ }
391
+ async writeRecord(objectName, record, mode, externalId, existingRecords) {
392
+ const externalIdValue = record[externalId];
393
+ const existing = existingRecords?.get(String(externalIdValue ?? ""));
394
+ const opts = _SeedLoaderService.SEED_OPTIONS;
395
+ switch (mode) {
396
+ case "insert": {
397
+ const result = await this.engine.insert(objectName, record, opts);
398
+ return { action: "inserted", id: this.extractId(result) };
399
+ }
400
+ case "update": {
401
+ if (!existing) {
402
+ return { action: "skipped" };
403
+ }
404
+ const id = this.extractId(existing);
405
+ await this.engine.update(objectName, { ...record, id }, opts);
406
+ return { action: "updated", id };
407
+ }
408
+ case "upsert": {
409
+ if (existing) {
410
+ const id = this.extractId(existing);
411
+ await this.engine.update(objectName, { ...record, id }, opts);
412
+ return { action: "updated", id };
413
+ } else {
414
+ const result = await this.engine.insert(objectName, record, opts);
415
+ return { action: "inserted", id: this.extractId(result) };
416
+ }
417
+ }
418
+ case "ignore": {
419
+ if (existing) {
420
+ return { action: "skipped", id: this.extractId(existing) };
421
+ }
422
+ const result = await this.engine.insert(objectName, record, opts);
423
+ return { action: "inserted", id: this.extractId(result) };
424
+ }
425
+ case "replace": {
426
+ const result = await this.engine.insert(objectName, record, opts);
427
+ return { action: "inserted", id: this.extractId(result) };
428
+ }
429
+ default: {
430
+ const result = await this.engine.insert(objectName, record, opts);
431
+ return { action: "inserted", id: this.extractId(result) };
432
+ }
433
+ }
434
+ }
435
+ // ==========================================================================
436
+ // Internal: Dependency Graph
437
+ // ==========================================================================
438
+ /**
439
+ * Kahn's algorithm for topological sort with cycle detection.
440
+ */
441
+ topologicalSort(nodes) {
442
+ const inDegree = /* @__PURE__ */ new Map();
443
+ const adjacency = /* @__PURE__ */ new Map();
444
+ const objectSet = new Set(nodes.map((n) => n.object));
445
+ for (const node of nodes) {
446
+ inDegree.set(node.object, 0);
447
+ adjacency.set(node.object, []);
448
+ }
449
+ for (const node of nodes) {
450
+ for (const dep of node.dependsOn) {
451
+ if (objectSet.has(dep) && dep !== node.object) {
452
+ adjacency.get(dep).push(node.object);
453
+ inDegree.set(node.object, (inDegree.get(node.object) || 0) + 1);
454
+ }
455
+ }
456
+ }
457
+ const queue = [];
458
+ for (const [obj, degree] of inDegree) {
459
+ if (degree === 0) queue.push(obj);
460
+ }
461
+ const insertOrder = [];
462
+ while (queue.length > 0) {
463
+ const current = queue.shift();
464
+ insertOrder.push(current);
465
+ for (const neighbor of adjacency.get(current) || []) {
466
+ const newDegree = (inDegree.get(neighbor) || 0) - 1;
467
+ inDegree.set(neighbor, newDegree);
468
+ if (newDegree === 0) {
469
+ queue.push(neighbor);
470
+ }
471
+ }
472
+ }
473
+ const circularDependencies = [];
474
+ const remaining = nodes.filter((n) => !insertOrder.includes(n.object));
475
+ if (remaining.length > 0) {
476
+ const cycles = this.findCycles(remaining);
477
+ circularDependencies.push(...cycles);
478
+ for (const node of remaining) {
479
+ if (!insertOrder.includes(node.object)) {
480
+ insertOrder.push(node.object);
481
+ }
482
+ }
483
+ }
484
+ return { insertOrder, circularDependencies };
485
+ }
486
+ findCycles(nodes) {
487
+ const cycles = [];
488
+ const nodeMap = new Map(nodes.map((n) => [n.object, n]));
489
+ const visited = /* @__PURE__ */ new Set();
490
+ const inStack = /* @__PURE__ */ new Set();
491
+ const dfs = (current, path) => {
492
+ if (inStack.has(current)) {
493
+ const cycleStart = path.indexOf(current);
494
+ if (cycleStart !== -1) {
495
+ cycles.push([...path.slice(cycleStart), current]);
496
+ }
497
+ return;
498
+ }
499
+ if (visited.has(current)) return;
500
+ visited.add(current);
501
+ inStack.add(current);
502
+ path.push(current);
503
+ const node = nodeMap.get(current);
504
+ if (node) {
505
+ for (const dep of node.dependsOn) {
506
+ if (nodeMap.has(dep)) {
507
+ dfs(dep, [...path]);
508
+ }
509
+ }
510
+ }
511
+ inStack.delete(current);
512
+ };
513
+ for (const node of nodes) {
514
+ if (!visited.has(node.object)) {
515
+ dfs(node.object, []);
516
+ }
517
+ }
518
+ return cycles;
519
+ }
520
+ // ==========================================================================
521
+ // Internal: Helpers
522
+ // ==========================================================================
523
+ filterByEnv(datasets, env) {
524
+ if (!env) return datasets;
525
+ return datasets.filter((d) => d.env.includes(env));
526
+ }
527
+ orderDatasets(datasets, insertOrder) {
528
+ const orderMap = new Map(insertOrder.map((name, i) => [name, i]));
529
+ return [...datasets].sort((a, b) => {
530
+ const orderA = orderMap.get(a.object) ?? Number.MAX_SAFE_INTEGER;
531
+ const orderB = orderMap.get(b.object) ?? Number.MAX_SAFE_INTEGER;
532
+ return orderA - orderB;
533
+ });
534
+ }
535
+ buildReferenceMap(graph) {
536
+ const map = /* @__PURE__ */ new Map();
537
+ for (const node of graph.nodes) {
538
+ if (node.references.length > 0) {
539
+ map.set(node.object, node.references);
540
+ }
541
+ }
542
+ return map;
543
+ }
544
+ async loadExistingRecords(objectName, externalId, organizationId) {
545
+ const map = /* @__PURE__ */ new Map();
546
+ try {
547
+ const findArgs = {
548
+ fields: ["id", externalId],
549
+ context: { isSystem: true }
550
+ };
551
+ if (organizationId) findArgs.where = { organization_id: organizationId };
552
+ const records = await this.engine.find(objectName, findArgs);
553
+ for (const record of records || []) {
554
+ const key = String(record[externalId] ?? "");
555
+ if (key) {
556
+ map.set(key, record);
557
+ }
558
+ }
559
+ } catch {
560
+ }
561
+ return map;
562
+ }
563
+ looksLikeInternalId(value) {
564
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
565
+ return true;
566
+ }
567
+ if (/^[0-9a-f]{24}$/i.test(value)) {
568
+ return true;
569
+ }
570
+ return false;
571
+ }
572
+ extractId(record) {
573
+ if (!record) return void 0;
574
+ return String(record.id || record._id || "");
575
+ }
576
+ buildEmptyResult(config, durationMs) {
577
+ return {
578
+ success: true,
579
+ dryRun: config.dryRun,
580
+ dependencyGraph: { nodes: [], insertOrder: [], circularDependencies: [] },
581
+ results: [],
582
+ errors: [],
583
+ summary: {
584
+ objectsProcessed: 0,
585
+ totalRecords: 0,
586
+ totalInserted: 0,
587
+ totalUpdated: 0,
588
+ totalSkipped: 0,
589
+ totalErrored: 0,
590
+ totalReferencesResolved: 0,
591
+ totalReferencesDeferred: 0,
592
+ circularDependencyCount: 0,
593
+ durationMs
594
+ }
595
+ };
596
+ }
597
+ buildResult(config, graph, results, errors, durationMs) {
598
+ const summary = {
599
+ objectsProcessed: results.length,
600
+ totalRecords: results.reduce((sum, r) => sum + r.total, 0),
601
+ totalInserted: results.reduce((sum, r) => sum + r.inserted, 0),
602
+ totalUpdated: results.reduce((sum, r) => sum + r.updated, 0),
603
+ totalSkipped: results.reduce((sum, r) => sum + r.skipped, 0),
604
+ totalErrored: results.reduce((sum, r) => sum + r.errored, 0),
605
+ totalReferencesResolved: results.reduce((sum, r) => sum + r.referencesResolved, 0),
606
+ totalReferencesDeferred: results.reduce((sum, r) => sum + r.referencesDeferred, 0),
607
+ circularDependencyCount: graph.circularDependencies.length,
608
+ durationMs
609
+ };
610
+ const hasErrors = errors.length > 0 || summary.totalErrored > 0;
611
+ return {
612
+ success: !hasErrors,
613
+ dryRun: config.dryRun,
614
+ dependencyGraph: graph,
615
+ results,
616
+ errors,
617
+ summary
618
+ };
619
+ }
620
+ };
621
+ // ==========================================================================
622
+ // Internal: Write Operations
623
+ // ==========================================================================
624
+ /**
625
+ * Seed writes always run as a privileged system context. This bypasses
626
+ * RBAC checks (so seeds can target system tables like `sys_*`) and
627
+ * disables the SecurityPlugin's auto-injection of `organization_id` /
628
+ * `owner_id` — seeds either declare those fields explicitly per
629
+ * record, or are intentionally cross-tenant / global.
630
+ */
631
+ _SeedLoaderService.SEED_OPTIONS = { context: { isSystem: true } };
632
+ SeedLoaderService = _SeedLoaderService;
633
+ }
634
+ });
635
+
636
+ // src/build-probes.ts
637
+ var build_probes_exports = {};
638
+ __export(build_probes_exports, {
639
+ runBuildProbes: () => runBuildProbes
640
+ });
641
+ function asRec(v) {
642
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
643
+ }
644
+ function asArr(v) {
645
+ return Array.isArray(v) ? v : [];
646
+ }
647
+ function resultRows(result) {
648
+ if (Array.isArray(result)) return result;
649
+ const r = asRec(result);
650
+ if (!r) return [];
651
+ if (Array.isArray(r.rows)) return r.rows;
652
+ if (Array.isArray(r.data)) return r.data;
653
+ return [];
654
+ }
655
+ async function hasRows(engine, objectName, organizationId) {
656
+ const rows = await engine.find(objectName, {
657
+ fields: ["id"],
658
+ limit: 1,
659
+ ...organizationId ? { where: { organization_id: organizationId } } : {},
660
+ context: { isSystem: true }
661
+ });
662
+ return Array.isArray(rows) && rows.length > 0;
663
+ }
664
+ async function runBuildProbes(opts) {
665
+ const issues = [];
666
+ const checked = { seeds: 0, views: 0, widgets: 0 };
667
+ const { engine, getItem, published, analytics, organizationId } = opts;
668
+ const itemCache = /* @__PURE__ */ new Map();
669
+ const readItem = async (type, name) => {
670
+ const key = `${type} ${name}`;
671
+ if (itemCache.has(key)) return itemCache.get(key);
672
+ let item;
673
+ try {
674
+ item = await getItem(type, name);
675
+ } catch {
676
+ item = void 0;
677
+ }
678
+ itemCache.set(key, item);
679
+ return item;
680
+ };
681
+ for (const p of published.filter((x) => x.type === "seed")) {
682
+ const body = asRec(await readItem("seed", p.name));
683
+ const objectName = typeof body?.object === "string" ? body.object : void 0;
684
+ if (!objectName) continue;
685
+ checked.seeds += 1;
686
+ try {
687
+ if (!await hasRows(engine, objectName, organizationId)) {
688
+ issues.push({
689
+ layer: "runtime",
690
+ severity: "error",
691
+ artifact: { type: "seed", name: p.name },
692
+ ref: { type: "object", name: objectName },
693
+ code: "seed_not_applied",
694
+ message: `Seed "${p.name}" was published but object "${objectName}" has no rows \u2014 the sample data never materialized.`,
695
+ fix: `Check the publish response's seedApplied for the load error, fix the seed rows (field names/types), and republish the seed.`
696
+ });
697
+ }
698
+ } catch (e) {
699
+ issues.push({
700
+ layer: "runtime",
701
+ severity: "error",
702
+ artifact: { type: "seed", name: p.name },
703
+ ref: { type: "object", name: objectName },
704
+ code: "seed_not_applied",
705
+ message: `Seed "${p.name}" probe could not read object "${objectName}": ${String(e?.message ?? e)}`
706
+ });
707
+ }
708
+ }
709
+ for (const p of published.filter((x) => x.type === "view")) {
710
+ const body = asRec(await readItem("view", p.name));
711
+ const config = asRec(body?.config);
712
+ const dataObj = asRec(config?.data)?.object;
713
+ const objectName = typeof body?.object === "string" ? body.object : typeof dataObj === "string" ? dataObj : void 0;
714
+ if (!objectName) continue;
715
+ checked.views += 1;
716
+ try {
717
+ await engine.find(objectName, {
718
+ fields: ["id"],
719
+ limit: 1,
720
+ ...organizationId ? { where: { organization_id: organizationId } } : {},
721
+ context: { isSystem: true }
722
+ });
723
+ } catch (e) {
724
+ issues.push({
725
+ layer: "runtime",
726
+ severity: "error",
727
+ artifact: { type: "view", name: p.name },
728
+ ref: { type: "object", name: objectName },
729
+ code: "view_read_failed",
730
+ message: `View "${p.name}" cannot read object "${objectName}": ${String(e?.message ?? e)} \u2014 it will render as an error for every user.`,
731
+ fix: `Verify object "${objectName}" published successfully (its table must exist) and that the view's binding is correct.`
732
+ });
733
+ }
734
+ }
735
+ const dashboards = published.filter((x) => x.type === "dashboard");
736
+ let widgetsToProbe = 0;
737
+ for (const p of dashboards) {
738
+ const body = asRec(await readItem("dashboard", p.name));
739
+ const widgets = asArr(body?.widgets).map(asRec).filter((w) => !!w);
740
+ const datasetBound = widgets.filter((w) => typeof w.dataset === "string" && w.dataset);
741
+ widgetsToProbe += datasetBound.length;
742
+ if (!analytics || typeof analytics.queryDataset !== "function") continue;
743
+ for (const w of datasetBound) {
744
+ const widgetId = String(w.id ?? w.title ?? "?");
745
+ const dsName = w.dataset;
746
+ const dataset = asRec(await readItem("dataset", dsName));
747
+ if (!dataset) continue;
748
+ checked.widgets += 1;
749
+ const measures = asArr(w.values).filter((v) => typeof v === "string" && v.length > 0);
750
+ const firstMeasure = asRec(asArr(dataset.measures)[0])?.name;
751
+ const selection = {
752
+ measures: measures.length ? measures : typeof firstMeasure === "string" ? [firstMeasure] : [],
753
+ dimensions: [],
754
+ limit: 1
755
+ };
756
+ if (selection.measures.length === 0) continue;
757
+ const objectName = typeof dataset.object === "string" ? dataset.object : void 0;
758
+ try {
759
+ const result = await analytics.queryDataset(dataset, selection, void 0);
760
+ const rows = resultRows(result);
761
+ if (rows.length === 0 && objectName && await hasRows(engine, objectName, organizationId)) {
762
+ issues.push({
763
+ layer: "runtime",
764
+ severity: "error",
765
+ artifact: { type: "dashboard", name: p.name },
766
+ ref: { type: "dataset", name: dsName, member: widgetId },
767
+ code: "empty_query",
768
+ 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.`,
769
+ fix: `Run the dataset query directly to see the compiled strategy/SQL; check the dataset's measure/dimension field bindings against object "${objectName}".`
770
+ });
771
+ }
772
+ } catch (e) {
773
+ issues.push({
774
+ layer: "runtime",
775
+ severity: "error",
776
+ artifact: { type: "dashboard", name: p.name },
777
+ ref: { type: "dataset", name: dsName, member: widgetId },
778
+ code: "widget_query_failed",
779
+ message: `Dashboard "${p.name}" widget "${widgetId}" query against dataset "${dsName}" failed: ${String(e?.message ?? e)}`,
780
+ fix: `Fix the dataset definition (or the widget's values/dimensions) so the query compiles, then republish.`
781
+ });
782
+ }
783
+ }
784
+ }
785
+ if (widgetsToProbe > 0 && (!analytics || typeof analytics.queryDataset !== "function")) {
786
+ issues.push({
787
+ layer: "runtime",
788
+ severity: "warning",
789
+ artifact: { type: "dashboard", name: dashboards.map((d) => d.name).join(", ") },
790
+ code: "probes_unavailable",
791
+ message: `${widgetsToProbe} dashboard widget(s) could not be probed: no analytics service is mounted on this kernel.`
792
+ });
793
+ }
794
+ return { issues, checked };
795
+ }
796
+ var init_build_probes = __esm({
797
+ "src/build-probes.ts"() {
798
+ "use strict";
799
+ }
800
+ });
801
+
30
802
  // src/index.ts
31
803
  var index_exports = {};
32
804
  __export(index_exports, {
@@ -43,6 +815,7 @@ __export(index_exports, {
43
815
  SECRET_REF_PREFIX: () => SECRET_REF_PREFIX,
44
816
  SchemaRegistry: () => SchemaRegistry,
45
817
  ScopedContext: () => ScopedContext,
818
+ SeedLoaderService: () => SeedLoaderService,
46
819
  SysMetadataRepository: () => SysMetadataRepository,
47
820
  ValidationError: () => ValidationError,
48
821
  applyInMemoryAggregation: () => applyInMemoryAggregation,
@@ -61,6 +834,7 @@ __export(index_exports, {
61
834
  noopHookMetricsRecorder: () => noopHookMetricsRecorder,
62
835
  parseFQN: () => parseFQN,
63
836
  parseSecretRef: () => parseSecretRef,
837
+ runBuildProbes: () => runBuildProbes,
64
838
  toTitleCase: () => toTitleCase,
65
839
  validateRecord: () => validateRecord,
66
840
  wrapDeclarativeHook: () => wrapDeclarativeHook
@@ -1568,7 +2342,7 @@ var SysMetadataRepository = class {
1568
2342
 
1569
2343
  // src/protocol.ts
1570
2344
  var import_metadata_core2 = require("@objectstack/metadata-core");
1571
- var import_data2 = require("@objectstack/spec/data");
2345
+ var import_data3 = require("@objectstack/spec/data");
1572
2346
  var import_shared4 = require("@objectstack/spec/shared");
1573
2347
  var import_ui2 = require("@objectstack/spec/ui");
1574
2348
  var import_system = require("@objectstack/spec/system");
@@ -2937,8 +3711,8 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
2937
3711
  } catch {
2938
3712
  }
2939
3713
  }
2940
- if ((0, import_data2.isFilterAST)(parsedFilter)) {
2941
- parsedFilter = (0, import_data2.parseFilterAST)(parsedFilter);
3714
+ if ((0, import_data3.isFilterAST)(parsedFilter)) {
3715
+ parsedFilter = (0, import_data3.parseFilterAST)(parsedFilter);
2942
3716
  }
2943
3717
  options.where = parsedFilter;
2944
3718
  }
@@ -4279,12 +5053,16 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4279
5053
  item: result.item.body
4280
5054
  });
4281
5055
  await this.ensureObjectStorage(request.type, request.name);
4282
- return {
5056
+ const response = {
4283
5057
  success: true,
4284
5058
  version: result.version,
4285
5059
  seq: result.seq,
4286
5060
  message: `Published draft \u2014 type=${request.type}, name=${request.name} [seq=${result.seq}]`
4287
5061
  };
5062
+ if (singularType === "seed" && !request._skipSeedApply) {
5063
+ response.seedApplied = await this.applySeedBodies([result.item.body], orgId);
5064
+ }
5065
+ return response;
4288
5066
  } catch (err) {
4289
5067
  if (err instanceof import_metadata_core2.ConflictError) {
4290
5068
  const conflict = new Error(
@@ -4299,6 +5077,58 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4299
5077
  throw err;
4300
5078
  }
4301
5079
  }
5080
+ /**
5081
+ * Materialize published `seed` bodies into data rows via the SeedLoaderService
5082
+ * (externalId-keyed upsert, multi-pass for cross-seed references). Passing ALL
5083
+ * of a publish's seed bodies in ONE call lets a child seed reference a parent
5084
+ * seed's rows regardless of publish order. Best-effort: any failure is
5085
+ * returned, never thrown — publishing metadata must not be blocked by a data
5086
+ * problem, but the caller surfaces `seedApplied` so the failure is LOUD.
5087
+ */
5088
+ async applySeedBodies(bodies, organizationId) {
5089
+ try {
5090
+ const seeds = bodies.filter(
5091
+ (b) => b && typeof b.object === "string" && Array.isArray(b.records)
5092
+ );
5093
+ if (seeds.length === 0) {
5094
+ return { success: false, inserted: 0, updated: 0, error: "seed apply: no readable seed bodies" };
5095
+ }
5096
+ const { SeedLoaderService: SeedLoaderService2 } = await Promise.resolve().then(() => (init_seed_loader(), seed_loader_exports));
5097
+ const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
5098
+ const metadataAdapter = {
5099
+ getObject: async (name) => {
5100
+ const wrapper = await this.getMetaItem({
5101
+ type: "object",
5102
+ name,
5103
+ ...organizationId ? { organizationId } : {}
5104
+ });
5105
+ return wrapper?.item ?? wrapper ?? null;
5106
+ }
5107
+ };
5108
+ const loader = new SeedLoaderService2(
5109
+ this.engine,
5110
+ metadataAdapter,
5111
+ console
5112
+ );
5113
+ const request = SeedLoaderRequestSchema.parse({
5114
+ seeds,
5115
+ config: {
5116
+ defaultMode: "upsert",
5117
+ multiPass: true,
5118
+ ...organizationId ? { organizationId } : {}
5119
+ }
5120
+ });
5121
+ const r = await loader.load(request);
5122
+ return {
5123
+ success: r.success,
5124
+ inserted: r.summary.totalInserted,
5125
+ updated: r.summary.totalUpdated,
5126
+ ...r.errors?.length ? { errors: r.errors } : {}
5127
+ };
5128
+ } catch (e) {
5129
+ return { success: false, inserted: 0, updated: 0, error: e?.message ?? "seed apply failed" };
5130
+ }
5131
+ }
4302
5132
  /**
4303
5133
  * List pending DRAFT metadata (ADR-0033) for the org, optionally narrowed
4304
5134
  * by `packageId` and/or `type`. The list reads of `getMetaItems` only see
@@ -4332,14 +5162,25 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4332
5162
  const drafts = await repo.listDrafts({ packageId: request.packageId });
4333
5163
  const published = [];
4334
5164
  const failed = [];
4335
- for (const d of drafts) {
5165
+ const ordered = [
5166
+ ...drafts.filter((d) => d.type !== "seed"),
5167
+ ...drafts.filter((d) => d.type === "seed")
5168
+ ];
5169
+ const seedBodies = [];
5170
+ for (const d of ordered) {
4336
5171
  try {
5172
+ if (d.type === "seed") {
5173
+ const ref = { type: d.type, name: d.name, org: orgId ?? "env" };
5174
+ const draft = await repo.get(ref, { state: "draft" });
5175
+ if (draft?.body) seedBodies.push(draft.body);
5176
+ }
4337
5177
  const r = await this.publishMetaItem({
4338
5178
  type: d.type,
4339
5179
  name: d.name,
4340
5180
  ...request.organizationId ? { organizationId: request.organizationId } : {},
4341
5181
  ...request.actor ? { actor: request.actor } : {},
4342
- message: `publish app package '${request.packageId}'`
5182
+ message: `publish app package '${request.packageId}'`,
5183
+ _skipSeedApply: true
4343
5184
  });
4344
5185
  published.push({ type: d.type, name: d.name, version: r.version });
4345
5186
  } catch (e) {
@@ -4351,12 +5192,38 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4351
5192
  });
4352
5193
  }
4353
5194
  }
5195
+ const seedApplied = seedBodies.length > 0 ? await this.applySeedBodies(seedBodies, orgId) : void 0;
5196
+ let probes;
5197
+ if (published.length > 0) {
5198
+ try {
5199
+ const { runBuildProbes: runBuildProbes2 } = await Promise.resolve().then(() => (init_build_probes(), build_probes_exports));
5200
+ const analytics = this.getServicesRegistry?.().get("analytics");
5201
+ probes = await runBuildProbes2({
5202
+ engine: this.engine,
5203
+ getItem: async (type, name) => {
5204
+ const wrapper = await this.getMetaItem({
5205
+ type,
5206
+ name,
5207
+ ...orgId ? { organizationId: orgId } : {}
5208
+ });
5209
+ return wrapper?.item ?? wrapper ?? void 0;
5210
+ },
5211
+ published,
5212
+ ...analytics && typeof analytics.queryDataset === "function" ? { analytics } : {},
5213
+ organizationId: orgId
5214
+ });
5215
+ } catch {
5216
+ probes = void 0;
5217
+ }
5218
+ }
4354
5219
  return {
4355
5220
  success: failed.length === 0 && published.length > 0,
4356
5221
  publishedCount: published.length,
4357
5222
  failedCount: failed.length,
4358
5223
  published,
4359
- failed
5224
+ failed,
5225
+ ...seedApplied ? { seedApplied } : {},
5226
+ ...probes ? { probes } : {}
4360
5227
  };
4361
5228
  }
4362
5229
  /**
@@ -5132,11 +5999,11 @@ function collectSecretFields(schema) {
5132
5999
 
5133
6000
  // src/engine.ts
5134
6001
  var import_shared5 = require("@objectstack/spec/shared");
5135
- var import_formula3 = require("@objectstack/formula");
6002
+ var import_formula4 = require("@objectstack/formula");
5136
6003
  var import_spec = require("@objectstack/spec");
5137
6004
 
5138
6005
  // src/hook-wrappers.ts
5139
- var import_formula = require("@objectstack/formula");
6006
+ var import_formula2 = require("@objectstack/formula");
5140
6007
 
5141
6008
  // src/hook-metrics.ts
5142
6009
  var noopHookMetricsRecorder = {
@@ -5213,10 +6080,10 @@ function wrapDeclarativeHook(meta, handler, opts = {}) {
5213
6080
  if (meta.condition) {
5214
6081
  const expr = typeof meta.condition === "string" ? { dialect: "cel", source: meta.condition } : meta.condition;
5215
6082
  if (expr.source && expr.source.trim()) {
5216
- const check = import_formula.ExpressionEngine.compile(expr);
6083
+ const check = import_formula2.ExpressionEngine.compile(expr);
5217
6084
  if (check.ok) {
5218
6085
  conditionFn = (record) => {
5219
- const r = import_formula.ExpressionEngine.evaluate(expr, { record: record ?? {} });
6086
+ const r = import_formula2.ExpressionEngine.evaluate(expr, { record: record ?? {} });
5220
6087
  if (!r.ok) {
5221
6088
  logger.warn("[hook] condition evaluation failed; treating as false", {
5222
6089
  hook: meta.name,
@@ -5688,7 +6555,7 @@ function validateRecord(objectSchema, data, mode) {
5688
6555
  }
5689
6556
 
5690
6557
  // src/validation/rule-validator.ts
5691
- var import_formula2 = require("@objectstack/formula");
6558
+ var import_formula3 = require("@objectstack/formula");
5692
6559
  var import_ajv = __toESM(require("ajv"));
5693
6560
  var ajv = new import_ajv.default({ allErrors: true, strict: false });
5694
6561
  var jsonSchemaCache = /* @__PURE__ */ new WeakMap();
@@ -5704,7 +6571,7 @@ function stripReadonlyWhenFields(objectSchema, data, previous, logger) {
5704
6571
  let result = data;
5705
6572
  for (const [name, def] of Object.entries(fields)) {
5706
6573
  if (!def?.readonlyWhen || !(name in data)) continue;
5707
- const res = import_formula2.ExpressionEngine.evaluate(toExpression(def.readonlyWhen), {
6574
+ const res = import_formula3.ExpressionEngine.evaluate(toExpression(def.readonlyWhen), {
5708
6575
  record: merged,
5709
6576
  previous: previous ?? void 0
5710
6577
  });
@@ -5759,7 +6626,7 @@ function evaluateValidationRules(objectSchema, data, mode, opts = {}) {
5759
6626
  for (const [name, def] of Object.entries(fields)) {
5760
6627
  const pred = def?.requiredWhen ?? def?.conditionalRequired;
5761
6628
  if (!pred) continue;
5762
- const res = import_formula2.ExpressionEngine.evaluate(toExpression(pred), { record: merged, previous });
6629
+ const res = import_formula3.ExpressionEngine.evaluate(toExpression(pred), { record: merged, previous });
5763
6630
  if (!res.ok) {
5764
6631
  opts.logger?.warn?.(`requiredWhen for '${name}' failed to evaluate \u2014 skipped`);
5765
6632
  continue;
@@ -5830,7 +6697,7 @@ function checkStateMachine(rule, mode, data, previous) {
5830
6697
  }
5831
6698
  function checkPredicate(rule, record, previous, logger) {
5832
6699
  const expr = toExpression(rule.condition);
5833
- const result = import_formula2.ExpressionEngine.evaluate(expr, {
6700
+ const result = import_formula3.ExpressionEngine.evaluate(expr, {
5834
6701
  record,
5835
6702
  previous: previous ?? void 0
5836
6703
  });
@@ -5928,7 +6795,7 @@ function checkJsonSchema(rule, data, logger) {
5928
6795
  return null;
5929
6796
  }
5930
6797
  function checkConditional(rule, ctx) {
5931
- const result = import_formula2.ExpressionEngine.evaluate(toExpression(rule.when), {
6798
+ const result = import_formula3.ExpressionEngine.evaluate(toExpression(rule.when), {
5932
6799
  record: ctx.merged,
5933
6800
  previous: ctx.previous ?? void 0
5934
6801
  });
@@ -6109,7 +6976,7 @@ function planFormulaProjection(schema, requestedFields) {
6109
6976
  if (def?.type === "formula" && def.expression) {
6110
6977
  const expr = typeof def.expression === "string" ? { dialect: "cel", source: def.expression } : def.expression;
6111
6978
  plan.push({ name: f, expression: expr });
6112
- import_formula3.ExpressionEngine.compile(expr);
6979
+ import_formula4.ExpressionEngine.compile(expr);
6113
6980
  } else if (Array.isArray(requestedFields) && requestedFields.length > 0) {
6114
6981
  projected.add(f);
6115
6982
  }
@@ -6131,7 +6998,7 @@ function applyFormulaPlan(plan, records) {
6131
6998
  for (const rec of records) {
6132
6999
  if (rec == null) continue;
6133
7000
  for (const fp of plan) {
6134
- const r = import_formula3.ExpressionEngine.evaluate(fp.expression, { record: rec });
7001
+ const r = import_formula4.ExpressionEngine.evaluate(fp.expression, { record: rec });
6135
7002
  rec[fp.name] = r.ok ? r.value : null;
6136
7003
  }
6137
7004
  }
@@ -6556,7 +7423,7 @@ var _ObjectQL = class _ObjectQL {
6556
7423
  if (f.defaultValue == null) continue;
6557
7424
  const dv = f.defaultValue;
6558
7425
  if (typeof dv === "object" && dv !== null && dv.dialect && typeof dv.source === "string") {
6559
- const result = import_formula3.ExpressionEngine.evaluate(dv, {
7426
+ const result = import_formula4.ExpressionEngine.evaluate(dv, {
6560
7427
  now,
6561
7428
  user: execCtx?.userId ? { id: String(execCtx.userId), role: execCtx?.roles?.[0] } : void 0,
6562
7429
  org: execCtx?.tenantId ? { id: String(execCtx.tenantId) } : void 0,
@@ -9215,6 +10082,10 @@ function convertIntrospectedSchemaToObjects(introspectedSchema, options) {
9215
10082
  }
9216
10083
  return objects;
9217
10084
  }
10085
+
10086
+ // src/index.ts
10087
+ init_seed_loader();
10088
+ init_build_probes();
9218
10089
  // Annotate the CommonJS export names for ESM import in node:
9219
10090
  0 && (module.exports = {
9220
10091
  DEFAULT_EXTENDER_PRIORITY,
@@ -9230,6 +10101,7 @@ function convertIntrospectedSchemaToObjects(introspectedSchema, options) {
9230
10101
  SECRET_REF_PREFIX,
9231
10102
  SchemaRegistry,
9232
10103
  ScopedContext,
10104
+ SeedLoaderService,
9233
10105
  SysMetadataRepository,
9234
10106
  ValidationError,
9235
10107
  applyInMemoryAggregation,
@@ -9248,6 +10120,7 @@ function convertIntrospectedSchemaToObjects(introspectedSchema, options) {
9248
10120
  noopHookMetricsRecorder,
9249
10121
  parseFQN,
9250
10122
  parseSecretRef,
10123
+ runBuildProbes,
9251
10124
  toTitleCase,
9252
10125
  validateRecord,
9253
10126
  wrapDeclarativeHook