Skip to content

Commit 4d4fe3c

Browse files
Copilothotlong
andcommitted
feat(runtime): implement SeedLoaderService with reference resolution, dependency ordering, and multi-pass loading
- ISeedLoaderService contract in packages/spec/src/contracts/ - SeedLoaderService runtime in packages/runtime/src/seed-loader.ts - Dependency graph builder from object metadata (lookup/master_detail) - Topological sort (Kahn's algorithm) for insert ordering - Reference resolution via externalId with DB fallback - Multi-pass loading for circular dependencies - Dry-run validation mode - Upsert/insert/update/ignore/replace mode support - UUID/ObjectId detection to skip resolution - Actionable error reporting - Updated app-plugin.ts to use SeedLoaderService with fallback - 36 runtime tests + 3 contract tests, all passing - Full suite: 121 runtime tests passing Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent d6efa2a commit 4d4fe3c

7 files changed

Lines changed: 2076 additions & 14 deletions

File tree

packages/runtime/src/app-plugin.ts

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { Plugin, PluginContext } from '@objectstack/core';
4+
import { SeedLoaderService } from './seed-loader.js';
45

56
/**
67
* AppPlugin
@@ -127,24 +128,60 @@ export class AppPlugin implements Plugin {
127128

128129
if (seedDatasets.length > 0) {
129130
ctx.logger.info(`[AppPlugin] Found ${seedDatasets.length} seed datasets for ${appId}`);
130-
for (const dataset of seedDatasets) {
131-
if (dataset.object && Array.isArray(dataset.records)) {
132-
const objectFQN = toFQN(dataset.object);
133-
ctx.logger.info(`[Seeder] Seeding ${dataset.records.length} records for ${objectFQN}`);
131+
132+
// Normalize dataset object names to FQN
133+
const normalizedDatasets = seedDatasets
134+
.filter((d: any) => d.object && Array.isArray(d.records))
135+
.map((d: any) => ({
136+
...d,
137+
object: toFQN(d.object),
138+
}));
139+
140+
// Use SeedLoaderService for metadata-driven loading with reference resolution
141+
try {
142+
const metadata = ctx.getService('metadata') as any;
143+
if (metadata) {
144+
const seedLoader = new SeedLoaderService(ql, metadata, ctx.logger);
145+
const { SeedLoaderRequestSchema } = await import('@objectstack/spec/data');
146+
const request = SeedLoaderRequestSchema.parse({
147+
datasets: normalizedDatasets,
148+
config: { defaultMode: 'upsert', multiPass: true },
149+
});
150+
const result = await seedLoader.load(request);
151+
ctx.logger.info('[Seeder] Seed loading complete', {
152+
inserted: result.summary.totalInserted,
153+
updated: result.summary.totalUpdated,
154+
errors: result.errors.length,
155+
});
156+
} else {
157+
// Fallback: basic insert when metadata service is not available
158+
ctx.logger.debug('[Seeder] No metadata service; using basic insert fallback');
159+
for (const dataset of normalizedDatasets) {
160+
ctx.logger.info(`[Seeder] Seeding ${dataset.records.length} records for ${dataset.object}`);
161+
for (const record of dataset.records) {
162+
try {
163+
await ql.insert(dataset.object, record);
164+
} catch (err: any) {
165+
ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error: err.message });
166+
}
167+
}
168+
}
169+
ctx.logger.info('[Seeder] Data seeding complete.');
170+
}
171+
} catch (err: any) {
172+
// If SeedLoaderService fails (e.g., metadata not available), fall back to basic insert
173+
ctx.logger.warn('[Seeder] SeedLoaderService failed, falling back to basic insert', { error: err.message });
174+
for (const dataset of normalizedDatasets) {
134175
for (const record of dataset.records) {
135-
try {
136-
// Use ObjectQL engine to insert data
137-
// This ensures driver resolution and hook execution
138-
// Use 'insert' which corresponds to 'create' in driver
139-
await ql.insert(objectFQN, record);
140-
} catch (err: any) {
141-
// Ignore duplicate errors if needed, or log/warn
142-
ctx.logger.warn(`[Seeder] Failed to insert ${objectFQN} record:`, { error: err.message });
143-
}
176+
try {
177+
await ql.insert(dataset.object, record);
178+
} catch (insertErr: any) {
179+
ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error: insertErr.message });
180+
}
144181
}
145182
}
183+
ctx.logger.info('[Seeder] Data seeding complete (fallback).');
146184
}
147-
ctx.logger.info('[Seeder] Data seeding complete.');
148185
}
149186
}
150187
}

packages/runtime/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type { RuntimeConfig } from './runtime.js';
1010
// Export Plugins
1111
export { DriverPlugin } from './driver-plugin.js';
1212
export { AppPlugin } from './app-plugin.js';
13+
export { SeedLoaderService } from './seed-loader.js';
1314
export { createDispatcherPlugin } from './dispatcher-plugin.js';
1415
export type { DispatcherPluginConfig } from './dispatcher-plugin.js';
1516

0 commit comments

Comments
 (0)