Skip to content

Commit 089fa3f

Browse files
Copilothotlong
andcommitted
fix: resolve Vercel ERR_MODULE_NOT_FOUND for api/_kernel
- Inline _kernel.ts into api/index.ts to eliminate bare extensionless relative import ('./_kernel') that broke Node ESM resolver at Vercel runtime - Move hono from devDependencies to dependencies (needed at serverless runtime) - Use explicit .js extensions for relative imports (create-broker-shim.js, objectstack.config.js) per ESM best practice - Delete api/_kernel.ts (content is now co-located in api/index.ts) - Update CHANGELOG.md with 3.2.10 patch entry Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 35d8183 commit 089fa3f

4 files changed

Lines changed: 151 additions & 146 deletions

File tree

apps/studio/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# @objectstack/studio
22

3+
## 3.2.10
4+
5+
### Patch Changes
6+
7+
- Fix Vercel deployment crash (`ERR_MODULE_NOT_FOUND` for `api/_kernel`)
8+
- Inline `_kernel.ts` content into `api/index.ts` to eliminate the bare extensionless relative import that broke Node's ESM resolver
9+
- Move `hono` from `devDependencies` to `dependencies` so it is available in the Vercel serverless runtime
10+
- Use explicit `.js` file extensions for relative imports in the API entrypoint (`create-broker-shim.js`, `objectstack.config.js`) per ESM best practice
11+
- Delete `api/_kernel.ts` — all kernel/service initialisation is now co-located in `api/index.ts`
12+
313
## 3.2.9
414

515
### Minor Changes

apps/studio/api/_kernel.ts

Lines changed: 0 additions & 139 deletions
This file was deleted.

apps/studio/api/index.ts

Lines changed: 140 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,152 @@
33
/**
44
* Vercel Serverless API Entrypoint (Hono + Vercel Node Adapter)
55
*
6-
* Top-level Hono app that delegates all /api/* requests to the
7-
* ObjectStack Hono application. The kernel boots lazily on the first
8-
* request and persists across warm invocations.
6+
* Top-level Hono app that boots the ObjectStack kernel lazily on the first
7+
* request and delegates all /api/* traffic to the ObjectStack Hono adapter.
98
*
10-
* Vercel's `vercel.json` rewrites route all `/api/*` traffic to this
11-
* single function — no catch-all `[...path].ts` is needed.
9+
* All kernel/service initialisation is co-located here so there are no
10+
* extensionless relative module imports — which would break Node's ESM
11+
* resolver when deployed to Vercel (`"type": "module"` package).
1212
*
1313
* @see https://hono.dev/docs/getting-started/vercel
1414
*/
1515

16+
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
17+
import { ObjectQLPlugin } from '@objectstack/objectql';
18+
import { InMemoryDriver } from '@objectstack/driver-memory';
19+
import { createHonoApp } from '@objectstack/hono';
1620
import { Hono } from 'hono';
1721
import { handle } from 'hono/vercel';
18-
import { ensureApp } from './_kernel';
22+
import { createBrokerShim } from '../src/lib/create-broker-shim.js';
23+
import studioConfig from '../objectstack.config.js';
24+
25+
// ---------------------------------------------------------------------------
26+
// Singleton state — persists across warm Vercel invocations
27+
// ---------------------------------------------------------------------------
28+
29+
let _kernel: ObjectKernel | null = null;
30+
let _app: Hono | null = null;
31+
32+
/** Shared boot promise — prevents concurrent cold-start races. */
33+
let _bootPromise: Promise<ObjectKernel> | null = null;
34+
35+
// ---------------------------------------------------------------------------
36+
// Kernel bootstrap
37+
// ---------------------------------------------------------------------------
38+
39+
/**
40+
* Boot the ObjectStack kernel (one-time cold-start cost).
41+
*
42+
* Uses a shared promise so that concurrent requests during a cold start
43+
* wait for the same boot sequence rather than starting duplicates.
44+
*/
45+
async function ensureKernel(): Promise<ObjectKernel> {
46+
if (_kernel) return _kernel;
47+
if (_bootPromise) return _bootPromise;
48+
49+
_bootPromise = (async () => {
50+
console.log('[Vercel] Booting ObjectStack Kernel (server mode)...');
51+
52+
try {
53+
const kernel = new ObjectKernel();
54+
55+
await kernel.use(new ObjectQLPlugin());
56+
await kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory'));
57+
await kernel.use(new AppPlugin(studioConfig));
58+
59+
// Broker shim — bridges HttpDispatcher → ObjectQL engine
60+
(kernel as any).broker = createBrokerShim(kernel);
61+
62+
await kernel.bootstrap();
63+
64+
// Validate broker attachment
65+
if (!(kernel as any).broker) {
66+
console.warn('[Vercel] Broker shim lost during bootstrap — reattaching.');
67+
(kernel as any).broker = createBrokerShim(kernel);
68+
}
69+
70+
// Seed data from config (non-fatal — the kernel is usable without seed data)
71+
try {
72+
await seedData(kernel, [studioConfig]);
73+
} catch (seedErr: any) {
74+
console.warn('[Vercel] Seed data failed (non-fatal):', seedErr?.message || seedErr);
75+
}
76+
77+
_kernel = kernel;
78+
console.log('[Vercel] Kernel ready.');
79+
return kernel;
80+
} catch (err) {
81+
// Clear the lock so the next request can retry
82+
_bootPromise = null;
83+
console.error('[Vercel] Kernel boot failed:', (err as any)?.message || err);
84+
throw err;
85+
}
86+
})();
87+
88+
return _bootPromise;
89+
}
90+
91+
/**
92+
* Seed records defined in app configs into the ObjectQL engine.
93+
*/
94+
async function seedData(kernel: ObjectKernel, configs: any[]) {
95+
const ql = (kernel as any).context?.getService('objectql');
96+
if (!ql) return;
97+
98+
const RESERVED_NS = new Set(['base', 'system']);
99+
const toFQN = (name: string, namespace?: string) => {
100+
if (name.includes('__') || !namespace || RESERVED_NS.has(namespace)) return name;
101+
return `${namespace}__${name}`;
102+
};
103+
104+
for (const appConfig of configs) {
105+
const namespace = (appConfig.manifest || appConfig)?.namespace as string | undefined;
106+
107+
const seedDatasets: any[] = [];
108+
if (Array.isArray(appConfig.data)) {
109+
seedDatasets.push(...appConfig.data);
110+
}
111+
if (appConfig.manifest && Array.isArray(appConfig.manifest.data)) {
112+
seedDatasets.push(...appConfig.manifest.data);
113+
}
114+
115+
for (const dataset of seedDatasets) {
116+
if (!dataset.records || !dataset.object) continue;
117+
118+
const objectFQN = toFQN(dataset.object, namespace);
119+
120+
let existing = await ql.find(objectFQN);
121+
if (existing && (existing as any).value) existing = (existing as any).value;
122+
123+
if (!existing || existing.length === 0) {
124+
console.log(`[Vercel] Seeding ${dataset.records.length} records for ${objectFQN}`);
125+
for (const record of dataset.records) {
126+
await ql.insert(objectFQN, record);
127+
}
128+
}
129+
}
130+
}
131+
}
132+
133+
// ---------------------------------------------------------------------------
134+
// Hono app factory
135+
// ---------------------------------------------------------------------------
136+
137+
/**
138+
* Get (or create) the Hono application backed by the ObjectStack kernel.
139+
* The prefix `/api/v1` matches the client SDK's default API path.
140+
*/
141+
async function ensureApp(): Promise<Hono> {
142+
if (_app) return _app;
143+
144+
const kernel = await ensureKernel();
145+
_app = createHonoApp({ kernel, prefix: '/api/v1' });
146+
return _app;
147+
}
148+
149+
// ---------------------------------------------------------------------------
150+
// Vercel handler
151+
// ---------------------------------------------------------------------------
19152

20153
const app = new Hono();
21154

@@ -38,3 +171,4 @@ app.all('*', async (c) => {
38171
});
39172

40173
export default handle(app);
174+

apps/studio/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@objectstack/plugin-msw": "workspace:*",
2727
"@objectstack/runtime": "workspace:*",
2828
"@objectstack/spec": "workspace:*",
29+
"hono": "^4.12.8",
2930
"@radix-ui/react-avatar": "^1.1.11",
3031
"@radix-ui/react-checkbox": "^1.3.3",
3132
"@radix-ui/react-collapsible": "^1.1.12",
@@ -57,7 +58,6 @@
5758
"@vitejs/plugin-react": "^6.0.1",
5859
"autoprefixer": "^10.4.27",
5960
"happy-dom": "^20.8.4",
60-
"hono": "^4.12.8",
6161
"msw": "^2.12.13",
6262
"postcss": "^8.5.8",
6363
"tailwindcss": "^4.2.1",

0 commit comments

Comments
 (0)