@objectstack/runtime 8.0.0 → 9.0.0

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.cjs CHANGED
@@ -165,603 +165,13 @@ var init_driver_plugin = __esm({
165
165
  // src/seed-loader.ts
166
166
  var seed_loader_exports = {};
167
167
  __export(seed_loader_exports, {
168
- SeedLoaderService: () => SeedLoaderService
168
+ SeedLoaderService: () => import_objectql.SeedLoaderService
169
169
  });
170
- var import_data, import_formula, DEFAULT_EXTERNAL_ID_FIELD, _SeedLoaderService, SeedLoaderService;
170
+ var import_objectql;
171
171
  var init_seed_loader = __esm({
172
172
  "src/seed-loader.ts"() {
173
173
  "use strict";
174
- import_data = require("@objectstack/spec/data");
175
- import_formula = require("@objectstack/formula");
176
- DEFAULT_EXTERNAL_ID_FIELD = "name";
177
- _SeedLoaderService = class _SeedLoaderService {
178
- constructor(engine, metadata, logger) {
179
- this.engine = engine;
180
- this.metadata = metadata;
181
- this.logger = logger;
182
- }
183
- // ==========================================================================
184
- // Public API
185
- // ==========================================================================
186
- async load(request) {
187
- const startTime = Date.now();
188
- const config = request.config;
189
- const allErrors = [];
190
- const allResults = [];
191
- const datasets = this.filterByEnv(request.seeds, config.env);
192
- if (datasets.length === 0) {
193
- return this.buildEmptyResult(config, Date.now() - startTime);
194
- }
195
- const objectNames = datasets.map((d) => d.object);
196
- const graph = await this.buildDependencyGraph(objectNames);
197
- this.logger.info("[SeedLoader] Dependency graph built", {
198
- objects: objectNames.length,
199
- insertOrder: graph.insertOrder,
200
- circularDeps: graph.circularDependencies.length
201
- });
202
- const orderedDatasets = this.orderDatasets(datasets, graph.insertOrder);
203
- const refMap = this.buildReferenceMap(graph);
204
- const insertedRecords = /* @__PURE__ */ new Map();
205
- const deferredUpdates = [];
206
- for (const dataset of orderedDatasets) {
207
- const result = await this.loadDataset(
208
- dataset,
209
- config,
210
- refMap,
211
- insertedRecords,
212
- deferredUpdates,
213
- allErrors
214
- );
215
- allResults.push(result);
216
- if (config.haltOnError && result.errored > 0) {
217
- this.logger.warn("[SeedLoader] Halting on first error", { object: dataset.object });
218
- break;
219
- }
220
- }
221
- if (config.multiPass && deferredUpdates.length > 0 && !config.dryRun) {
222
- this.logger.info("[SeedLoader] Pass 2: resolving deferred references", {
223
- count: deferredUpdates.length
224
- });
225
- await this.resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, config.organizationId);
226
- }
227
- const durationMs = Date.now() - startTime;
228
- return this.buildResult(config, graph, allResults, allErrors, durationMs);
229
- }
230
- async buildDependencyGraph(objectNames) {
231
- const nodes = [];
232
- const objectSet = new Set(objectNames);
233
- for (const objectName of objectNames) {
234
- const objDef = await this.metadata.getObject(objectName);
235
- const dependsOn = [];
236
- const references = [];
237
- if (objDef && objDef.fields) {
238
- const fields = objDef.fields;
239
- for (const [fieldName, fieldDef] of Object.entries(fields)) {
240
- if ((fieldDef.type === "lookup" || fieldDef.type === "master_detail") && fieldDef.reference) {
241
- const targetObject = fieldDef.reference;
242
- if (objectSet.has(targetObject) && !dependsOn.includes(targetObject)) {
243
- dependsOn.push(targetObject);
244
- }
245
- references.push({
246
- field: fieldName,
247
- targetObject,
248
- targetField: DEFAULT_EXTERNAL_ID_FIELD,
249
- fieldType: fieldDef.type
250
- });
251
- }
252
- }
253
- }
254
- nodes.push({ object: objectName, dependsOn, references });
255
- }
256
- const { insertOrder, circularDependencies } = this.topologicalSort(nodes);
257
- return { nodes, insertOrder, circularDependencies };
258
- }
259
- async validate(datasets, config) {
260
- const parsedConfig = import_data.SeedLoaderConfigSchema.parse({ ...config, dryRun: true });
261
- return this.load({ seeds: datasets, config: parsedConfig });
262
- }
263
- // ==========================================================================
264
- // Internal: Seed Loading
265
- // ==========================================================================
266
- async loadDataset(dataset, config, refMap, insertedRecords, deferredUpdates, allErrors) {
267
- const objectName = dataset.object;
268
- const mode = dataset.mode || config.defaultMode;
269
- const externalId = dataset.externalId || "name";
270
- let inserted = 0;
271
- let updated = 0;
272
- let skipped = 0;
273
- let errored = 0;
274
- let referencesResolved = 0;
275
- let referencesDeferred = 0;
276
- const errors = [];
277
- if (!insertedRecords.has(objectName)) {
278
- insertedRecords.set(objectName, /* @__PURE__ */ new Map());
279
- }
280
- let existingRecords;
281
- if ((mode === "upsert" || mode === "update" || mode === "ignore") && !config.dryRun) {
282
- existingRecords = await this.loadExistingRecords(
283
- objectName,
284
- externalId,
285
- config.organizationId
286
- );
287
- }
288
- const objectRefs = refMap.get(objectName) || [];
289
- const seedNow = /* @__PURE__ */ new Date();
290
- const seedIdentity = config.identity;
291
- const baseEvalCtx = {
292
- now: seedNow,
293
- user: seedIdentity?.user,
294
- // Fall back to the per-tenant organizationId so `os.org.id` resolves
295
- // during per-org replay even without an explicit identity.org.
296
- org: seedIdentity?.org ?? (config.organizationId ? { id: config.organizationId } : void 0),
297
- env: config.env
298
- };
299
- for (let i = 0; i < dataset.records.length; i++) {
300
- const seedResult = (0, import_formula.resolveSeedRecord)(
301
- dataset.records[i],
302
- baseEvalCtx
303
- );
304
- if (!seedResult.ok) {
305
- errored++;
306
- const error = {
307
- sourceObject: objectName,
308
- field: "(expression)",
309
- targetObject: objectName,
310
- targetField: "(expression)",
311
- attemptedValue: dataset.records[i],
312
- recordIndex: i,
313
- 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).`
314
- };
315
- errors.push(error);
316
- allErrors.push(error);
317
- this.logger.warn(`[SeedLoader] ${error.message}`);
318
- continue;
319
- }
320
- const record = { ...seedResult.value };
321
- if (config.organizationId && record["organization_id"] == null) {
322
- record["organization_id"] = config.organizationId;
323
- }
324
- for (const ref of objectRefs) {
325
- const fieldValue = record[ref.field];
326
- if (fieldValue === void 0 || fieldValue === null) continue;
327
- if (typeof fieldValue === "object") {
328
- const wrapped = fieldValue.externalId;
329
- 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.`;
330
- const error = {
331
- sourceObject: objectName,
332
- field: ref.field,
333
- targetObject: ref.targetObject,
334
- targetField: ref.targetField,
335
- attemptedValue: fieldValue,
336
- recordIndex: i,
337
- message: `Invalid reference for ${objectName}.${ref.field}: expected a ${ref.targetObject}.${ref.targetField} natural-key string but got an object.${hint}`
338
- };
339
- errors.push(error);
340
- allErrors.push(error);
341
- this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
342
- record[ref.field] = null;
343
- continue;
344
- }
345
- if (typeof fieldValue !== "string" || this.looksLikeInternalId(fieldValue)) continue;
346
- const targetMap = insertedRecords.get(ref.targetObject);
347
- const resolvedId = targetMap?.get(String(fieldValue));
348
- if (resolvedId) {
349
- record[ref.field] = resolvedId;
350
- referencesResolved++;
351
- } else if (!config.dryRun) {
352
- const dbId = await this.resolveFromDatabase(ref.targetObject, ref.targetField, fieldValue, config.organizationId);
353
- if (dbId) {
354
- record[ref.field] = dbId;
355
- referencesResolved++;
356
- } else if (config.multiPass) {
357
- record[ref.field] = null;
358
- deferredUpdates.push({
359
- objectName,
360
- recordExternalId: String(record[externalId] ?? ""),
361
- field: ref.field,
362
- targetObject: ref.targetObject,
363
- targetField: ref.targetField,
364
- attemptedValue: fieldValue,
365
- recordIndex: i
366
- });
367
- referencesDeferred++;
368
- } else {
369
- const error = {
370
- sourceObject: objectName,
371
- field: ref.field,
372
- targetObject: ref.targetObject,
373
- targetField: ref.targetField,
374
- attemptedValue: fieldValue,
375
- recordIndex: i,
376
- message: `Cannot resolve reference: ${objectName}.${ref.field} = '${fieldValue}' \u2192 ${ref.targetObject}.${ref.targetField} not found`
377
- };
378
- errors.push(error);
379
- allErrors.push(error);
380
- }
381
- } else {
382
- const targetMap2 = insertedRecords.get(ref.targetObject);
383
- if (!targetMap2?.has(String(fieldValue))) {
384
- const error = {
385
- sourceObject: objectName,
386
- field: ref.field,
387
- targetObject: ref.targetObject,
388
- targetField: ref.targetField,
389
- attemptedValue: fieldValue,
390
- recordIndex: i,
391
- message: `[dry-run] Reference may not resolve: ${objectName}.${ref.field} = '${fieldValue}' \u2192 ${ref.targetObject}.${ref.targetField}`
392
- };
393
- errors.push(error);
394
- allErrors.push(error);
395
- }
396
- }
397
- }
398
- if (!config.dryRun) {
399
- try {
400
- const result = await this.writeRecord(
401
- objectName,
402
- record,
403
- mode,
404
- externalId,
405
- existingRecords
406
- );
407
- if (result.action === "inserted") inserted++;
408
- else if (result.action === "updated") updated++;
409
- else if (result.action === "skipped") skipped++;
410
- const externalIdValue = String(record[externalId] ?? "");
411
- const internalId = result.id;
412
- if (externalIdValue && internalId) {
413
- insertedRecords.get(objectName).set(externalIdValue, String(internalId));
414
- }
415
- } catch (err) {
416
- errored++;
417
- const error = {
418
- sourceObject: objectName,
419
- field: "(write)",
420
- targetObject: objectName,
421
- targetField: externalId,
422
- attemptedValue: record[externalId] ?? null,
423
- recordIndex: i,
424
- message: `Failed to write ${objectName} record #${i} (${externalId}=${String(record[externalId] ?? "")}): ${err.message}`
425
- };
426
- errors.push(error);
427
- allErrors.push(error);
428
- this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
429
- }
430
- } else {
431
- const externalIdValue = String(record[externalId] ?? "");
432
- if (externalIdValue) {
433
- insertedRecords.get(objectName).set(externalIdValue, `dry-run-id-${i}`);
434
- }
435
- inserted++;
436
- }
437
- }
438
- return {
439
- object: objectName,
440
- mode,
441
- inserted,
442
- updated,
443
- skipped,
444
- errored,
445
- total: dataset.records.length,
446
- referencesResolved,
447
- referencesDeferred,
448
- errors
449
- };
450
- }
451
- // ==========================================================================
452
- // Internal: Reference Resolution
453
- // ==========================================================================
454
- async resolveFromDatabase(targetObject, targetField, value, organizationId) {
455
- try {
456
- const where = { [targetField]: value };
457
- if (organizationId) where.organization_id = organizationId;
458
- const records = await this.engine.find(targetObject, {
459
- where,
460
- fields: ["id"],
461
- limit: 1,
462
- context: { isSystem: true }
463
- });
464
- if (records && records.length > 0) {
465
- return String(records[0].id || records[0]._id);
466
- }
467
- } catch {
468
- }
469
- return null;
470
- }
471
- async resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, organizationId) {
472
- for (const deferred of deferredUpdates) {
473
- const targetMap = insertedRecords.get(deferred.targetObject);
474
- let resolvedId = targetMap?.get(String(deferred.attemptedValue));
475
- if (!resolvedId) {
476
- resolvedId = await this.resolveFromDatabase(
477
- deferred.targetObject,
478
- deferred.targetField,
479
- deferred.attemptedValue,
480
- organizationId
481
- ) ?? void 0;
482
- }
483
- if (resolvedId) {
484
- const objectRecordMap = insertedRecords.get(deferred.objectName);
485
- const recordId = objectRecordMap?.get(deferred.recordExternalId);
486
- if (recordId) {
487
- try {
488
- await this.engine.update(deferred.objectName, {
489
- id: recordId,
490
- [deferred.field]: resolvedId
491
- }, { context: { isSystem: true } });
492
- const resultEntry = allResults.find((r) => r.object === deferred.objectName);
493
- if (resultEntry) {
494
- resultEntry.referencesResolved++;
495
- resultEntry.referencesDeferred--;
496
- }
497
- } catch (err) {
498
- this.logger.warn("[SeedLoader] Failed to resolve deferred reference", {
499
- object: deferred.objectName,
500
- field: deferred.field,
501
- error: err.message
502
- });
503
- }
504
- }
505
- } else {
506
- const error = {
507
- sourceObject: deferred.objectName,
508
- field: deferred.field,
509
- targetObject: deferred.targetObject,
510
- targetField: deferred.targetField,
511
- attemptedValue: deferred.attemptedValue,
512
- recordIndex: deferred.recordIndex,
513
- message: `Deferred reference unresolved after pass 2: ${deferred.objectName}.${deferred.field} = '${deferred.attemptedValue}' \u2192 ${deferred.targetObject}.${deferred.targetField} not found`
514
- };
515
- const resultEntry = allResults.find((r) => r.object === deferred.objectName);
516
- if (resultEntry) {
517
- resultEntry.errors.push(error);
518
- }
519
- allErrors.push(error);
520
- }
521
- }
522
- }
523
- async writeRecord(objectName, record, mode, externalId, existingRecords) {
524
- const externalIdValue = record[externalId];
525
- const existing = existingRecords?.get(String(externalIdValue ?? ""));
526
- const opts = _SeedLoaderService.SEED_OPTIONS;
527
- switch (mode) {
528
- case "insert": {
529
- const result = await this.engine.insert(objectName, record, opts);
530
- return { action: "inserted", id: this.extractId(result) };
531
- }
532
- case "update": {
533
- if (!existing) {
534
- return { action: "skipped" };
535
- }
536
- const id = this.extractId(existing);
537
- await this.engine.update(objectName, { ...record, id }, opts);
538
- return { action: "updated", id };
539
- }
540
- case "upsert": {
541
- if (existing) {
542
- const id = this.extractId(existing);
543
- await this.engine.update(objectName, { ...record, id }, opts);
544
- return { action: "updated", id };
545
- } else {
546
- const result = await this.engine.insert(objectName, record, opts);
547
- return { action: "inserted", id: this.extractId(result) };
548
- }
549
- }
550
- case "ignore": {
551
- if (existing) {
552
- return { action: "skipped", id: this.extractId(existing) };
553
- }
554
- const result = await this.engine.insert(objectName, record, opts);
555
- return { action: "inserted", id: this.extractId(result) };
556
- }
557
- case "replace": {
558
- const result = await this.engine.insert(objectName, record, opts);
559
- return { action: "inserted", id: this.extractId(result) };
560
- }
561
- default: {
562
- const result = await this.engine.insert(objectName, record, opts);
563
- return { action: "inserted", id: this.extractId(result) };
564
- }
565
- }
566
- }
567
- // ==========================================================================
568
- // Internal: Dependency Graph
569
- // ==========================================================================
570
- /**
571
- * Kahn's algorithm for topological sort with cycle detection.
572
- */
573
- topologicalSort(nodes) {
574
- const inDegree = /* @__PURE__ */ new Map();
575
- const adjacency = /* @__PURE__ */ new Map();
576
- const objectSet = new Set(nodes.map((n) => n.object));
577
- for (const node of nodes) {
578
- inDegree.set(node.object, 0);
579
- adjacency.set(node.object, []);
580
- }
581
- for (const node of nodes) {
582
- for (const dep of node.dependsOn) {
583
- if (objectSet.has(dep) && dep !== node.object) {
584
- adjacency.get(dep).push(node.object);
585
- inDegree.set(node.object, (inDegree.get(node.object) || 0) + 1);
586
- }
587
- }
588
- }
589
- const queue = [];
590
- for (const [obj, degree] of inDegree) {
591
- if (degree === 0) queue.push(obj);
592
- }
593
- const insertOrder = [];
594
- while (queue.length > 0) {
595
- const current = queue.shift();
596
- insertOrder.push(current);
597
- for (const neighbor of adjacency.get(current) || []) {
598
- const newDegree = (inDegree.get(neighbor) || 0) - 1;
599
- inDegree.set(neighbor, newDegree);
600
- if (newDegree === 0) {
601
- queue.push(neighbor);
602
- }
603
- }
604
- }
605
- const circularDependencies = [];
606
- const remaining = nodes.filter((n) => !insertOrder.includes(n.object));
607
- if (remaining.length > 0) {
608
- const cycles = this.findCycles(remaining);
609
- circularDependencies.push(...cycles);
610
- for (const node of remaining) {
611
- if (!insertOrder.includes(node.object)) {
612
- insertOrder.push(node.object);
613
- }
614
- }
615
- }
616
- return { insertOrder, circularDependencies };
617
- }
618
- findCycles(nodes) {
619
- const cycles = [];
620
- const nodeMap = new Map(nodes.map((n) => [n.object, n]));
621
- const visited = /* @__PURE__ */ new Set();
622
- const inStack = /* @__PURE__ */ new Set();
623
- const dfs = (current, path) => {
624
- if (inStack.has(current)) {
625
- const cycleStart = path.indexOf(current);
626
- if (cycleStart !== -1) {
627
- cycles.push([...path.slice(cycleStart), current]);
628
- }
629
- return;
630
- }
631
- if (visited.has(current)) return;
632
- visited.add(current);
633
- inStack.add(current);
634
- path.push(current);
635
- const node = nodeMap.get(current);
636
- if (node) {
637
- for (const dep of node.dependsOn) {
638
- if (nodeMap.has(dep)) {
639
- dfs(dep, [...path]);
640
- }
641
- }
642
- }
643
- inStack.delete(current);
644
- };
645
- for (const node of nodes) {
646
- if (!visited.has(node.object)) {
647
- dfs(node.object, []);
648
- }
649
- }
650
- return cycles;
651
- }
652
- // ==========================================================================
653
- // Internal: Helpers
654
- // ==========================================================================
655
- filterByEnv(datasets, env) {
656
- if (!env) return datasets;
657
- return datasets.filter((d) => d.env.includes(env));
658
- }
659
- orderDatasets(datasets, insertOrder) {
660
- const orderMap = new Map(insertOrder.map((name, i) => [name, i]));
661
- return [...datasets].sort((a, b) => {
662
- const orderA = orderMap.get(a.object) ?? Number.MAX_SAFE_INTEGER;
663
- const orderB = orderMap.get(b.object) ?? Number.MAX_SAFE_INTEGER;
664
- return orderA - orderB;
665
- });
666
- }
667
- buildReferenceMap(graph) {
668
- const map = /* @__PURE__ */ new Map();
669
- for (const node of graph.nodes) {
670
- if (node.references.length > 0) {
671
- map.set(node.object, node.references);
672
- }
673
- }
674
- return map;
675
- }
676
- async loadExistingRecords(objectName, externalId, organizationId) {
677
- const map = /* @__PURE__ */ new Map();
678
- try {
679
- const findArgs = {
680
- fields: ["id", externalId],
681
- context: { isSystem: true }
682
- };
683
- if (organizationId) findArgs.where = { organization_id: organizationId };
684
- const records = await this.engine.find(objectName, findArgs);
685
- for (const record of records || []) {
686
- const key = String(record[externalId] ?? "");
687
- if (key) {
688
- map.set(key, record);
689
- }
690
- }
691
- } catch {
692
- }
693
- return map;
694
- }
695
- looksLikeInternalId(value) {
696
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
697
- return true;
698
- }
699
- if (/^[0-9a-f]{24}$/i.test(value)) {
700
- return true;
701
- }
702
- return false;
703
- }
704
- extractId(record) {
705
- if (!record) return void 0;
706
- return String(record.id || record._id || "");
707
- }
708
- buildEmptyResult(config, durationMs) {
709
- return {
710
- success: true,
711
- dryRun: config.dryRun,
712
- dependencyGraph: { nodes: [], insertOrder: [], circularDependencies: [] },
713
- results: [],
714
- errors: [],
715
- summary: {
716
- objectsProcessed: 0,
717
- totalRecords: 0,
718
- totalInserted: 0,
719
- totalUpdated: 0,
720
- totalSkipped: 0,
721
- totalErrored: 0,
722
- totalReferencesResolved: 0,
723
- totalReferencesDeferred: 0,
724
- circularDependencyCount: 0,
725
- durationMs
726
- }
727
- };
728
- }
729
- buildResult(config, graph, results, errors, durationMs) {
730
- const summary = {
731
- objectsProcessed: results.length,
732
- totalRecords: results.reduce((sum, r) => sum + r.total, 0),
733
- totalInserted: results.reduce((sum, r) => sum + r.inserted, 0),
734
- totalUpdated: results.reduce((sum, r) => sum + r.updated, 0),
735
- totalSkipped: results.reduce((sum, r) => sum + r.skipped, 0),
736
- totalErrored: results.reduce((sum, r) => sum + r.errored, 0),
737
- totalReferencesResolved: results.reduce((sum, r) => sum + r.referencesResolved, 0),
738
- totalReferencesDeferred: results.reduce((sum, r) => sum + r.referencesDeferred, 0),
739
- circularDependencyCount: graph.circularDependencies.length,
740
- durationMs
741
- };
742
- const hasErrors = errors.length > 0 || summary.totalErrored > 0;
743
- return {
744
- success: !hasErrors,
745
- dryRun: config.dryRun,
746
- dependencyGraph: graph,
747
- results,
748
- errors,
749
- summary
750
- };
751
- }
752
- };
753
- // ==========================================================================
754
- // Internal: Write Operations
755
- // ==========================================================================
756
- /**
757
- * Seed writes always run as a privileged system context. This bypasses
758
- * RBAC checks (so seeds can target system tables like `sys_*`) and
759
- * disables the SecurityPlugin's auto-injection of `organization_id` /
760
- * `owner_id` — seeds either declare those fields explicitly per
761
- * record, or are intentionally cross-tenant / global.
762
- */
763
- _SeedLoaderService.SEED_OPTIONS = { context: { isSystem: true } };
764
- SeedLoaderService = _SeedLoaderService;
174
+ import_objectql = require("@objectstack/objectql");
765
175
  }
766
176
  });
767
177
 
@@ -1126,7 +536,7 @@ function hookBodyRunnerFactory(runner, opts) {
1126
536
  return (hook) => {
1127
537
  const raw = hook.body;
1128
538
  if (!raw) return void 0;
1129
- const parsed = import_data2.HookBodySchema.safeParse(raw);
539
+ const parsed = import_data.HookBodySchema.safeParse(raw);
1130
540
  if (!parsed.success) {
1131
541
  opts.logger?.warn?.("[BodyRunner] invalid hook.body shape", {
1132
542
  appId: opts.appId,
@@ -1163,7 +573,7 @@ function actionBodyRunnerFactory(runner, opts) {
1163
573
  return (action) => {
1164
574
  const raw = action.body;
1165
575
  if (!raw) return void 0;
1166
- const parsed = import_data2.HookBodySchema.safeParse(raw);
576
+ const parsed = import_data.HookBodySchema.safeParse(raw);
1167
577
  if (!parsed.success) {
1168
578
  opts.logger?.warn?.("[BodyRunner] invalid action.body shape", {
1169
579
  appId: opts.appId,
@@ -1294,11 +704,11 @@ function unwrapProxyToPlain(v) {
1294
704
  return void 0;
1295
705
  }
1296
706
  }
1297
- var import_data2;
707
+ var import_data;
1298
708
  var init_body_runner = __esm({
1299
709
  "src/sandbox/body-runner.ts"() {
1300
710
  "use strict";
1301
- import_data2 = require("@objectstack/spec/data");
711
+ import_data = require("@objectstack/spec/data");
1302
712
  }
1303
713
  });
1304
714
 
@@ -1677,7 +1087,7 @@ var init_app_plugin = __esm({
1677
1087
  if (!Array.isArray(datasetsNow) || datasetsNow.length === 0) {
1678
1088
  return { inserted: 0, updated: 0, errors: [] };
1679
1089
  }
1680
- const seedLoader = new SeedLoaderService(ql, md, loggerRef);
1090
+ const seedLoader = new import_objectql.SeedLoaderService(ql, md, loggerRef);
1681
1091
  const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
1682
1092
  const request = SeedLoaderRequestSchema.parse({
1683
1093
  seeds: datasetsNow,
@@ -1713,7 +1123,7 @@ var init_app_plugin = __esm({
1713
1123
  try {
1714
1124
  const metadata = ctx.getService("metadata");
1715
1125
  if (metadata) {
1716
- const seedLoader = new SeedLoaderService(ql, metadata, ctx.logger);
1126
+ const seedLoader = new import_objectql.SeedLoaderService(ql, metadata, ctx.logger);
1717
1127
  const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
1718
1128
  const request = SeedLoaderRequestSchema.parse({
1719
1129
  seeds: normalizedDatasets,
@@ -2221,7 +1631,7 @@ __export(index_exports, {
2221
1631
  RuntimeConfigPlugin: () => RuntimeConfigPlugin,
2222
1632
  SYSTEM_ENVIRONMENT_ID: () => SYSTEM_ENVIRONMENT_ID,
2223
1633
  SandboxError: () => SandboxError,
2224
- SeedLoaderService: () => SeedLoaderService,
1634
+ SeedLoaderService: () => import_objectql.SeedLoaderService,
2225
1635
  UnimplementedScriptRunner: () => UnimplementedScriptRunner,
2226
1636
  _resetEnvDeprecationWarnings: () => import_types4._resetEnvDeprecationWarnings,
2227
1637
  actionBodyRunnerFactory: () => actionBodyRunnerFactory,
@@ -4048,17 +3458,19 @@ var _HttpDispatcher = class _HttpDispatcher {
4048
3458
  ...organizationId ? { organizationId } : {},
4049
3459
  ...body?.actor ? { actor: body.actor } : {}
4050
3460
  });
4051
- try {
4052
- const seedNames = (result?.published ?? []).filter((p) => p?.type === "seed").map((p) => p.name);
4053
- if (seedNames.length > 0) {
4054
- result.seedApplied = await this.applyPublishedSeeds(
4055
- seedNames,
4056
- organizationId,
4057
- _context
4058
- );
3461
+ if (result?.seedApplied === void 0) {
3462
+ try {
3463
+ const seedNames = (result?.published ?? []).filter((p) => p?.type === "seed").map((p) => p.name);
3464
+ if (seedNames.length > 0) {
3465
+ result.seedApplied = await this.applyPublishedSeeds(
3466
+ seedNames,
3467
+ organizationId,
3468
+ _context
3469
+ );
3470
+ }
3471
+ } catch (e) {
3472
+ result.seedApplied = { success: false, error: e?.message ?? "seed apply failed" };
4059
3473
  }
4060
- } catch (e) {
4061
- result.seedApplied = { success: false, error: e?.message ?? "seed apply failed" };
4062
3474
  }
4063
3475
  return { handled: true, response: this.success(result) };
4064
3476
  } catch (e) {