|
1 | 1 | // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. |
2 | 2 |
|
3 | 3 | import { Plugin, PluginContext } from '@objectstack/core'; |
| 4 | +import { SeedLoaderService } from './seed-loader.js'; |
| 5 | +import type { IMetadataService } from '@objectstack/spec/contracts'; |
4 | 6 |
|
5 | 7 | /** |
6 | 8 | * AppPlugin |
@@ -127,24 +129,60 @@ export class AppPlugin implements Plugin { |
127 | 129 |
|
128 | 130 | if (seedDatasets.length > 0) { |
129 | 131 | 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}`); |
| 132 | + |
| 133 | + // Normalize dataset object names to FQN |
| 134 | + const normalizedDatasets = seedDatasets |
| 135 | + .filter((d: any) => d.object && Array.isArray(d.records)) |
| 136 | + .map((d: any) => ({ |
| 137 | + ...d, |
| 138 | + object: toFQN(d.object), |
| 139 | + })); |
| 140 | + |
| 141 | + // Use SeedLoaderService for metadata-driven loading with reference resolution |
| 142 | + try { |
| 143 | + const metadata = ctx.getService('metadata') as IMetadataService | undefined; |
| 144 | + if (metadata) { |
| 145 | + const seedLoader = new SeedLoaderService(ql, metadata, ctx.logger); |
| 146 | + const { SeedLoaderRequestSchema } = await import('@objectstack/spec/data'); |
| 147 | + const request = SeedLoaderRequestSchema.parse({ |
| 148 | + datasets: normalizedDatasets, |
| 149 | + config: { defaultMode: 'upsert', multiPass: true }, |
| 150 | + }); |
| 151 | + const result = await seedLoader.load(request); |
| 152 | + ctx.logger.info('[Seeder] Seed loading complete', { |
| 153 | + inserted: result.summary.totalInserted, |
| 154 | + updated: result.summary.totalUpdated, |
| 155 | + errors: result.errors.length, |
| 156 | + }); |
| 157 | + } else { |
| 158 | + // Fallback: basic insert when metadata service is not available |
| 159 | + ctx.logger.debug('[Seeder] No metadata service; using basic insert fallback'); |
| 160 | + for (const dataset of normalizedDatasets) { |
| 161 | + ctx.logger.info(`[Seeder] Seeding ${dataset.records.length} records for ${dataset.object}`); |
| 162 | + for (const record of dataset.records) { |
| 163 | + try { |
| 164 | + await ql.insert(dataset.object, record); |
| 165 | + } catch (err: any) { |
| 166 | + ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error: err.message }); |
| 167 | + } |
| 168 | + } |
| 169 | + } |
| 170 | + ctx.logger.info('[Seeder] Data seeding complete.'); |
| 171 | + } |
| 172 | + } catch (err: any) { |
| 173 | + // If SeedLoaderService fails (e.g., metadata not available), fall back to basic insert |
| 174 | + ctx.logger.warn('[Seeder] SeedLoaderService failed, falling back to basic insert', { error: err.message }); |
| 175 | + for (const dataset of normalizedDatasets) { |
134 | 176 | 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 | | - } |
| 177 | + try { |
| 178 | + await ql.insert(dataset.object, record); |
| 179 | + } catch (insertErr: any) { |
| 180 | + ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error: insertErr.message }); |
| 181 | + } |
144 | 182 | } |
145 | 183 | } |
| 184 | + ctx.logger.info('[Seeder] Data seeding complete (fallback).'); |
146 | 185 | } |
147 | | - ctx.logger.info('[Seeder] Data seeding complete.'); |
148 | 186 | } |
149 | 187 | } |
150 | 188 | } |
0 commit comments