Skip to content

Commit 9f4aa96

Browse files
committed
feat: implement local plugin loading with artifact support and update dependencies
1 parent d4160e1 commit 9f4aa96

4 files changed

Lines changed: 51 additions & 41 deletions

File tree

apps/server/objectstack.config.ts

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@
3232
*/
3333

3434
import { resolve as resolvePath } from 'node:path';
35+
import { readFile } from 'node:fs/promises';
3536
import type * as Contracts from '@objectstack/spec/contracts';
3637
import {
3738
type BasePluginsFactory,
3839
type AppBundleResolver,
40+
AppPlugin,
3941
} from '@objectstack/runtime';
4042
import { createControlPlanePlugins } from './server/control-plane-preset.js';
4143
import { templateRegistry } from './server/templates/registry.js';
@@ -59,38 +61,47 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
5961
// ── LOCAL MODE ────────────────────────────────────────────────────────────────
6062

6163
const localProjectId = process.env.OBJECTSTACK_PROJECT_ID ?? 'proj_local';
62-
const localDatabaseUrl = process.env.OBJECTSTACK_DATABASE_URL
63-
?? `file:${resolvePath(process.cwd(), '.objectstack/data/local.db')}`;
64-
const localDatabaseDriver = process.env.OBJECTSTACK_DATABASE_DRIVER ?? 'sqlite';
6564
const localArtifactPath = process.env.OBJECTSTACK_ARTIFACT_PATH
6665
?? resolvePath(process.cwd(), 'dist/objectstack.json');
6766

68-
// Lazy-loading proxy for local boot: registers ObjectQL + MetadataPlugin(local-file) + Auth
69-
// on the kernel. No control-plane, no MultiProjectPlugin.
70-
const localBootPluginProxy: any = {
71-
name: 'com.objectstack.local-boot',
72-
version: '0.0.0',
73-
async init(ctx: any) {
74-
const { ObjectQLPlugin } = await import('@objectstack/objectql');
75-
const { MetadataPlugin } = await import('@objectstack/metadata');
76-
const { AuthPlugin } = await import('@objectstack/plugin-auth');
67+
async function buildLocalPlugins() {
68+
const { ObjectQLPlugin } = await import('@objectstack/objectql');
69+
const { MetadataPlugin } = await import('@objectstack/metadata');
70+
const { AuthPlugin } = await import('@objectstack/plugin-auth');
7771

78-
await ctx.kernel.use(new ObjectQLPlugin({ environmentId: localProjectId }));
79-
await ctx.kernel.use(new MetadataPlugin({
72+
// Load artifact JSON to register app bundle (objects, views, etc.) via AppPlugin.
73+
// AppPlugin.init() calls manifest.register() → ql.registerApp() which is the
74+
// correct pathway for objects to enter the ObjectQL schema registry.
75+
let artifactBundle: any = null;
76+
try {
77+
const raw = await readFile(localArtifactPath, 'utf8');
78+
const parsed = JSON.parse(raw);
79+
// Detect envelope vs bare ObjectStackDefinition
80+
artifactBundle = (parsed?.schemaVersion && parsed?.metadata !== undefined)
81+
? parsed.metadata
82+
: parsed;
83+
} catch {
84+
// Artifact not available yet (e.g. first run before compile) — AppPlugin skipped.
85+
}
86+
87+
// MetadataPlugin must start before ObjectQLPlugin so that when ObjectQL's
88+
// start() calls loadMetadataFromService(), the artifact is already loaded.
89+
const plugins: any[] = [
90+
new MetadataPlugin({
8091
watch: false,
8192
environmentId: localProjectId,
8293
artifactSource: { mode: 'local-file', path: localArtifactPath },
83-
}));
84-
await ctx.kernel.use(new AuthPlugin({ secret: authSecret, baseUrl }));
85-
86-
ctx.logger?.info?.('[LocalBoot] plugins registered', {
87-
projectId: localProjectId,
88-
databaseUrl: localDatabaseUrl,
89-
databaseDriver: localDatabaseDriver,
90-
artifactPath: localArtifactPath,
91-
});
92-
},
93-
};
94+
}),
95+
new ObjectQLPlugin({ environmentId: localProjectId }),
96+
new AuthPlugin({ secret: authSecret, baseUrl }),
97+
];
98+
99+
if (artifactBundle) {
100+
plugins.push(new AppPlugin(artifactBundle));
101+
}
102+
103+
return plugins;
104+
}
94105

95106
// ── CLOUD MODE ────────────────────────────────────────────────────────────────
96107

@@ -171,9 +182,9 @@ const multiProjectPluginProxy: any = {
171182

172183
// ── Export ────────────────────────────────────────────────────────────────────
173184

174-
export default isLocalMode
185+
const config = isLocalMode
175186
? {
176-
plugins: [localBootPluginProxy],
187+
plugins: await buildLocalPlugins(),
177188
api: {
178189
enableProjectScoping: false,
179190
projectResolution: 'none' as const,
@@ -193,3 +204,5 @@ export default isLocalMode
193204
projectResolution: 'auto' as const,
194205
},
195206
};
207+
208+
export default config;

apps/server/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
"dependencies": {
1919
"@example/app-crm": "workspace:*",
2020
"@example/app-todo": "workspace:*",
21-
"@example/plugin-bi": "workspace:*",
2221
"@hono/node-server": "^1.19.14",
2322
"@libsql/client": "^0.17.2",
2423
"@objectstack/driver-memory": "workspace:*",

packages/metadata/src/plugin.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { readFile } from 'node:fs/promises';
44
import { createHash } from 'node:crypto';
55
import { Plugin, PluginContext } from '@objectstack/core';
66
import { NodeMetadataManager } from './node-metadata-manager.js';
7+
import { MemoryLoader } from './loaders/memory-loader.js';
78
import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel';
89
import type { MetadataPluginConfig } from '@objectstack/spec/kernel';
910
import { SysMetadataObject } from './objects/sys-metadata.object.js';
@@ -212,18 +213,28 @@ export class MetadataPlugin implements Plugin {
212213
metadata = def as Record<string, unknown[]>;
213214
}
214215

216+
// Register artifact items into a MemoryLoader so they are visible to
217+
// both `loadMany()` (used by ObjectQL's sync path) and `list()` (used
218+
// by REST meta endpoints). `register()` alone only writes to the in-memory
219+
// registry Map which `loadMany()` does not read.
220+
const memLoader = new MemoryLoader();
221+
215222
let totalRegistered = 0;
216223
for (const [field, metaType] of Object.entries(ARTIFACT_FIELD_TO_TYPE)) {
217224
const items = (metadata as any)[field];
218225
if (!Array.isArray(items) || items.length === 0) continue;
219226
for (const item of items) {
220227
const name = (item as any)?.name;
221228
if (!name) continue;
229+
await memLoader.save(metaType, name, item);
222230
await this.manager.register(metaType, name, item);
223231
totalRegistered++;
224232
}
225233
}
226234

235+
// Mount the loader so `loadMany` queries hit it.
236+
this.manager.registerLoader(memLoader);
237+
227238
ctx.logger.info('[MetadataPlugin] Artifact metadata loaded', {
228239
path: filePath,
229240
totalRegistered,

pnpm-lock.yaml

Lines changed: 0 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)