@objectstack/runtime 3.2.1 → 3.2.3
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +19 -0
- package/dist/index.cjs +568 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -2
- package/dist/index.d.ts +48 -2
- package/dist/index.js +557 -12
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/app-plugin.ts +52 -14
- package/src/http-dispatcher.test.ts +3 -3
- package/src/http-dispatcher.ts +4 -4
- package/src/index.ts +1 -0
- package/src/seed-loader.test.ts +1123 -0
- package/src/seed-loader.ts +713 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/runtime",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "ObjectStack Core Runtime & Query Engine",
|
|
6
6
|
"type": "module",
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"zod": "^4.3.6",
|
|
18
|
-
"@objectstack/core": "3.2.
|
|
19
|
-
"@objectstack/
|
|
20
|
-
"@objectstack/
|
|
21
|
-
"@objectstack/
|
|
18
|
+
"@objectstack/core": "3.2.3",
|
|
19
|
+
"@objectstack/rest": "3.2.3",
|
|
20
|
+
"@objectstack/spec": "3.2.3",
|
|
21
|
+
"@objectstack/types": "3.2.3"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"typescript": "^5.0.0",
|
package/src/app-plugin.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
}
|
|
@@ -216,7 +216,7 @@ describe('HttpDispatcher', () => {
|
|
|
216
216
|
it('should resolve analytics service from Promise (async factory)', async () => {
|
|
217
217
|
const mockAnalytics = {
|
|
218
218
|
query: vi.fn().mockResolvedValue({ rows: [{ id: 1 }], total: 1 }),
|
|
219
|
-
|
|
219
|
+
getMeta: vi.fn().mockResolvedValue({ tables: ['t1'] }),
|
|
220
220
|
generateSql: vi.fn().mockResolvedValue({ sql: 'SELECT 1' }),
|
|
221
221
|
};
|
|
222
222
|
// Inject as Promise (simulates async factory registration)
|
|
@@ -245,7 +245,7 @@ describe('HttpDispatcher', () => {
|
|
|
245
245
|
|
|
246
246
|
it('should handle GET /analytics/meta with async service', async () => {
|
|
247
247
|
const mockAnalytics = {
|
|
248
|
-
|
|
248
|
+
getMeta: vi.fn().mockResolvedValue({ tables: ['users', 'orders'] }),
|
|
249
249
|
};
|
|
250
250
|
(kernel as any).getService = vi.fn().mockResolvedValue(mockAnalytics);
|
|
251
251
|
|
|
@@ -600,7 +600,7 @@ describe('HttpDispatcher', () => {
|
|
|
600
600
|
|
|
601
601
|
describe('handleData', () => {
|
|
602
602
|
it('should pass expand and select to broker for GET /data/:object/:id', async () => {
|
|
603
|
-
mockBroker.call.mockResolvedValue({ object: 'order_item', id: 'oi_1', record: {
|
|
603
|
+
mockBroker.call.mockResolvedValue({ object: 'order_item', id: 'oi_1', record: { id: 'oi_1' } });
|
|
604
604
|
|
|
605
605
|
const result = await dispatcher.handleData(
|
|
606
606
|
'/order_item/oi_1', 'GET', {},
|
package/src/http-dispatcher.ts
CHANGED
|
@@ -526,7 +526,7 @@ export class HttpDispatcher {
|
|
|
526
526
|
* Handles Analytics requests
|
|
527
527
|
* path: sub-path after /analytics/
|
|
528
528
|
*/
|
|
529
|
-
async handleAnalytics(path: string, method: string, body: any,
|
|
529
|
+
async handleAnalytics(path: string, method: string, body: any, _context: HttpProtocolContext): Promise<HttpDispatcherResult> {
|
|
530
530
|
const analyticsService = await this.getService(CoreServiceName.enum.analytics);
|
|
531
531
|
if (!analyticsService) return { handled: false }; // 404 handled by caller if unhandled
|
|
532
532
|
|
|
@@ -535,20 +535,20 @@ export class HttpDispatcher {
|
|
|
535
535
|
|
|
536
536
|
// POST /analytics/query
|
|
537
537
|
if (subPath === 'query' && m === 'POST') {
|
|
538
|
-
const result = await analyticsService.query(body
|
|
538
|
+
const result = await analyticsService.query(body);
|
|
539
539
|
return { handled: true, response: this.success(result) };
|
|
540
540
|
}
|
|
541
541
|
|
|
542
542
|
// GET /analytics/meta
|
|
543
543
|
if (subPath === 'meta' && m === 'GET') {
|
|
544
|
-
const result = await analyticsService.
|
|
544
|
+
const result = await analyticsService.getMeta();
|
|
545
545
|
return { handled: true, response: this.success(result) };
|
|
546
546
|
}
|
|
547
547
|
|
|
548
548
|
// POST /analytics/sql (Dry-run or debug)
|
|
549
549
|
if (subPath === 'sql' && m === 'POST') {
|
|
550
550
|
// Assuming service has generateSql method
|
|
551
|
-
const result = await analyticsService.generateSql(body
|
|
551
|
+
const result = await analyticsService.generateSql(body);
|
|
552
552
|
return { handled: true, response: this.success(result) };
|
|
553
553
|
}
|
|
554
554
|
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type { RuntimeConfig } from './runtime.js';
|
|
|
10
10
|
// Export Plugins
|
|
11
11
|
export { DriverPlugin } from './driver-plugin.js';
|
|
12
12
|
export { AppPlugin } from './app-plugin.js';
|
|
13
|
+
export { SeedLoaderService } from './seed-loader.js';
|
|
13
14
|
export { createDispatcherPlugin } from './dispatcher-plugin.js';
|
|
14
15
|
export type { DispatcherPluginConfig } from './dispatcher-plugin.js';
|
|
15
16
|
|