@webstir-io/webstir-backend 0.1.15 → 0.1.16
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/README.md +106 -79
- package/dist/add.d.ts +59 -0
- package/dist/add.js +626 -0
- package/dist/build/artifacts.d.ts +115 -1
- package/dist/build/artifacts.js +4 -4
- package/dist/build/entries.js +1 -1
- package/dist/build/pipeline.d.ts +33 -1
- package/dist/build/pipeline.js +307 -65
- package/dist/cache/diff.js +9 -8
- package/dist/cache/reporters.js +1 -1
- package/dist/deploy-cli.d.ts +2 -0
- package/dist/deploy-cli.js +86 -0
- package/dist/diagnostics/summary.js +2 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/manifest/pipeline.js +103 -32
- package/dist/provider.js +35 -17
- package/dist/runtime/bun.d.ts +51 -0
- package/dist/runtime/bun.js +499 -0
- package/dist/runtime/core.d.ts +141 -0
- package/dist/runtime/core.js +316 -0
- package/dist/runtime/deploy-backend.d.ts +20 -0
- package/dist/runtime/deploy-backend.js +175 -0
- package/dist/runtime/deploy-shared.d.ts +43 -0
- package/dist/runtime/deploy-shared.js +75 -0
- package/dist/runtime/deploy-static.d.ts +2 -0
- package/dist/runtime/deploy-static.js +161 -0
- package/dist/runtime/deploy.d.ts +3 -0
- package/dist/runtime/deploy.js +91 -0
- package/dist/runtime/forms.d.ts +73 -0
- package/dist/runtime/forms.js +236 -0
- package/dist/runtime/request-hooks.d.ts +47 -0
- package/dist/runtime/request-hooks.js +102 -0
- package/dist/runtime/session-metadata.d.ts +13 -0
- package/dist/runtime/session-metadata.js +98 -0
- package/dist/runtime/session-runtime.d.ts +28 -0
- package/dist/runtime/session-runtime.js +180 -0
- package/dist/runtime/session.d.ts +83 -0
- package/dist/runtime/session.js +396 -0
- package/dist/runtime/views.d.ts +74 -0
- package/dist/runtime/views.js +221 -0
- package/dist/scaffold/assets.js +25 -21
- package/dist/testing/context.js +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +100 -56
- package/dist/utils/bun.d.ts +2 -0
- package/dist/utils/bun.js +13 -0
- package/dist/watch.d.ts +13 -1
- package/dist/watch.js +345 -97
- package/dist/workspace.d.ts +8 -0
- package/dist/workspace.js +44 -3
- package/package.json +49 -14
- package/scripts/publish.sh +2 -92
- package/scripts/smoke.mjs +282 -107
- package/scripts/update-contract.sh +12 -10
- package/src/add.ts +964 -0
- package/src/build/artifacts.ts +49 -46
- package/src/build/entries.ts +12 -12
- package/src/build/pipeline.ts +779 -403
- package/src/cache/diff.ts +111 -105
- package/src/cache/reporters.ts +26 -26
- package/src/deploy-cli.ts +111 -0
- package/src/diagnostics/summary.ts +28 -22
- package/src/index.ts +11 -0
- package/src/manifest/pipeline.ts +328 -215
- package/src/provider.ts +115 -98
- package/src/runtime/bun.ts +793 -0
- package/src/runtime/core.ts +598 -0
- package/src/runtime/deploy-backend.ts +239 -0
- package/src/runtime/deploy-shared.ts +136 -0
- package/src/runtime/deploy-static.ts +191 -0
- package/src/runtime/deploy.ts +143 -0
- package/src/runtime/forms.ts +364 -0
- package/src/runtime/request-hooks.ts +165 -0
- package/src/runtime/session-metadata.ts +135 -0
- package/src/runtime/session-runtime.ts +267 -0
- package/src/runtime/session.ts +642 -0
- package/src/runtime/views.ts +385 -0
- package/src/scaffold/assets.ts +77 -73
- package/src/testing/context.js +8 -9
- package/src/testing/context.ts +9 -9
- package/src/testing/index.d.ts +14 -3
- package/src/testing/index.js +254 -175
- package/src/testing/index.ts +298 -195
- package/src/testing/types.d.ts +18 -19
- package/src/testing/types.ts +18 -18
- package/src/utils/bun.ts +26 -0
- package/src/watch.ts +503 -99
- package/src/workspace.ts +59 -3
- package/templates/backend/.env.example +15 -0
- package/templates/backend/auth/adapter.ts +335 -36
- package/templates/backend/db/connection.ts +190 -65
- package/templates/backend/db/migrate.ts +149 -43
- package/templates/backend/db/types.d.ts +1 -1
- package/templates/backend/env.ts +132 -20
- package/templates/backend/functions/hello/index.ts +1 -2
- package/templates/backend/index.ts +15 -508
- package/templates/backend/jobs/nightly/index.ts +1 -1
- package/templates/backend/jobs/runtime.ts +24 -11
- package/templates/backend/jobs/scheduler.ts +208 -46
- package/templates/backend/module.ts +227 -13
- package/templates/backend/observability/logger.ts +2 -12
- package/templates/backend/observability/metrics.ts +8 -5
- package/templates/backend/session/sqlite.ts +152 -0
- package/templates/backend/session/store.ts +45 -0
- package/templates/backend/tsconfig.json +1 -1
- package/tests/add.test.js +327 -0
- package/tests/authAdapter.test.js +315 -0
- package/tests/bundlerParity.test.js +217 -0
- package/tests/cacheReporter.test.js +10 -10
- package/tests/dbConnection.test.js +209 -0
- package/tests/deploy.test.js +357 -0
- package/tests/envLoader.test.js +271 -17
- package/tests/integration.test.js +2432 -3
- package/tests/jobsScheduler.test.js +253 -0
- package/tests/manifest.test.js +287 -12
- package/tests/migrationRunner.test.js +249 -0
- package/tests/sessionScaffoldStore.test.js +752 -0
- package/tests/sessionStore.test.js +490 -0
- package/tests/testing.test.js +252 -0
- package/tests/watch.test.js +192 -32
- package/tsconfig.json +3 -10
- package/templates/backend/server/fastify.ts +0 -288
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
// Optional Fastify server scaffold for richer routing
|
|
2
|
-
// Rename or import into your backend index to use.
|
|
3
|
-
import Fastify from 'fastify';
|
|
4
|
-
import { randomUUID } from 'node:crypto';
|
|
5
|
-
|
|
6
|
-
import { loadEnv } from '../env.js';
|
|
7
|
-
|
|
8
|
-
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
9
|
-
|
|
10
|
-
interface Logger {
|
|
11
|
-
readonly level: LogLevel;
|
|
12
|
-
log(level: LogLevel, message: string, metadata?: Record<string, unknown>): void;
|
|
13
|
-
debug(message: string, metadata?: Record<string, unknown>): void;
|
|
14
|
-
info(message: string, metadata?: Record<string, unknown>): void;
|
|
15
|
-
warn(message: string, metadata?: Record<string, unknown>): void;
|
|
16
|
-
error(message: string, metadata?: Record<string, unknown>): void;
|
|
17
|
-
with(bindings: Record<string, unknown>): Logger;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface EnvAccessor {
|
|
21
|
-
get(name: string): string | undefined;
|
|
22
|
-
require(name: string): string;
|
|
23
|
-
entries(): Record<string, string | undefined>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface ModuleRouteDefinition {
|
|
27
|
-
name?: string;
|
|
28
|
-
method?: string;
|
|
29
|
-
path?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface ModuleRoute {
|
|
33
|
-
definition?: ModuleRouteDefinition;
|
|
34
|
-
handler?: (ctx: Record<string, unknown>) => Promise<any> | any;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface ModuleManifestLike {
|
|
38
|
-
name?: string;
|
|
39
|
-
version?: string;
|
|
40
|
-
capabilities?: string[];
|
|
41
|
-
routes?: ModuleRouteDefinition[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface ModuleDefinitionLike {
|
|
45
|
-
manifest?: ModuleManifestLike;
|
|
46
|
-
routes?: ModuleRoute[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface ManifestSummary {
|
|
50
|
-
name?: string;
|
|
51
|
-
version?: string;
|
|
52
|
-
routes: number;
|
|
53
|
-
capabilities?: string[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
type ReadinessStatus = 'booting' | 'ready' | 'error';
|
|
57
|
-
type ReadinessTracker = ReturnType<typeof createReadinessTracker>;
|
|
58
|
-
|
|
59
|
-
export async function start(): Promise<void> {
|
|
60
|
-
const env = loadEnv();
|
|
61
|
-
const port = env.PORT;
|
|
62
|
-
const mode = env.NODE_ENV;
|
|
63
|
-
const readiness = createReadinessTracker();
|
|
64
|
-
readiness.booting();
|
|
65
|
-
|
|
66
|
-
const app = Fastify({ logger: false });
|
|
67
|
-
|
|
68
|
-
app.get('/api/health', async () => ({ ok: true, uptime: process.uptime() }));
|
|
69
|
-
app.get('/healthz', async () => ({ ok: true }));
|
|
70
|
-
|
|
71
|
-
let manifestSummary: ManifestSummary | undefined;
|
|
72
|
-
|
|
73
|
-
app.get('/readyz', async (_req, reply) => {
|
|
74
|
-
const snapshot = readiness.snapshot();
|
|
75
|
-
const statusCode = snapshot.status === 'ready' ? 200 : 503;
|
|
76
|
-
reply.code(statusCode);
|
|
77
|
-
return { status: snapshot.status, message: snapshot.message, manifest: manifestSummary };
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
const definition = await tryLoadModuleDefinition();
|
|
82
|
-
if (definition) {
|
|
83
|
-
manifestSummary = summarizeManifest(definition.manifest, definition.routes);
|
|
84
|
-
logManifestSummary(definition.manifest, definition.routes);
|
|
85
|
-
mountRoutes(app, definition);
|
|
86
|
-
} else {
|
|
87
|
-
console.info('[fastify] no module definition found. Routes will be empty.');
|
|
88
|
-
}
|
|
89
|
-
} catch (error) {
|
|
90
|
-
readiness.error((error as Error).message ?? 'module load failed');
|
|
91
|
-
console.error('[fastify] failed to load module definition:', error);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
await app.listen({ port, host: '0.0.0.0' });
|
|
95
|
-
|
|
96
|
-
if (readiness.snapshot().status !== 'error') {
|
|
97
|
-
readiness.ready();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Dev runner watches for this readiness line
|
|
101
|
-
console.info('API server running');
|
|
102
|
-
console.info(`[webstir-backend] mode=${mode} port=${port}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function mountRoutes(app: import('fastify').FastifyInstance, definition: ModuleDefinitionLike) {
|
|
106
|
-
const routes = Array.isArray(definition?.routes) ? definition.routes : [];
|
|
107
|
-
for (const r of routes) {
|
|
108
|
-
try {
|
|
109
|
-
const method = String(r.definition?.method ?? 'GET').toUpperCase();
|
|
110
|
-
const url = String(r.definition?.path ?? '/');
|
|
111
|
-
const handler = r.handler;
|
|
112
|
-
if (typeof handler !== 'function') continue;
|
|
113
|
-
|
|
114
|
-
app.route({
|
|
115
|
-
method: method as any,
|
|
116
|
-
url,
|
|
117
|
-
handler: async (req, reply) => {
|
|
118
|
-
const requestId = extractRequestId(req);
|
|
119
|
-
reply.header('x-request-id', requestId);
|
|
120
|
-
const envAccessor = createEnvAccessor();
|
|
121
|
-
const ctx: Record<string, unknown> = {
|
|
122
|
-
request: req,
|
|
123
|
-
reply,
|
|
124
|
-
auth: undefined,
|
|
125
|
-
session: null,
|
|
126
|
-
db: {},
|
|
127
|
-
env: envAccessor,
|
|
128
|
-
logger: createRequestLogger(requestId),
|
|
129
|
-
requestId,
|
|
130
|
-
now: () => new Date(),
|
|
131
|
-
params: (req as any).params ?? {},
|
|
132
|
-
query: (req as any).query ?? {},
|
|
133
|
-
body: (req as any).body ?? {}
|
|
134
|
-
};
|
|
135
|
-
const result = await handler(ctx);
|
|
136
|
-
const status = result?.status ?? (result?.errors ? 400 : 200);
|
|
137
|
-
const headers = result?.headers ?? { 'content-type': 'application/json' };
|
|
138
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
139
|
-
reply.header(k, String(v));
|
|
140
|
-
}
|
|
141
|
-
if (result?.errors) {
|
|
142
|
-
reply.code(status).send({ errors: result.errors });
|
|
143
|
-
} else {
|
|
144
|
-
reply.code(status).send(result?.body ?? null);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
console.info(`[fastify] mounted ${method} ${url}`);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.warn('[fastify] failed to mount route', error);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function tryLoadModuleDefinition(): Promise<ModuleDefinitionLike | undefined> {
|
|
156
|
-
const candidates = ['../module.js', '../module/index.js'];
|
|
157
|
-
for (const rel of candidates) {
|
|
158
|
-
try {
|
|
159
|
-
const url = new URL(rel, import.meta.url);
|
|
160
|
-
const mod = await import(url.toString());
|
|
161
|
-
const def = (mod && (mod.module || mod.moduleDefinition || mod.default)) as ModuleDefinitionLike;
|
|
162
|
-
if (def && typeof def === 'object') return def;
|
|
163
|
-
} catch {
|
|
164
|
-
// ignore and try next
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function summarizeManifest(manifest?: ModuleManifestLike, routes?: ModuleRoute[]): ManifestSummary | undefined {
|
|
171
|
-
if (!manifest) return undefined;
|
|
172
|
-
const routeCount = Array.isArray(manifest.routes) ? manifest.routes.length : Array.isArray(routes) ? routes.length : 0;
|
|
173
|
-
return {
|
|
174
|
-
name: manifest.name,
|
|
175
|
-
version: manifest.version,
|
|
176
|
-
routes: routeCount,
|
|
177
|
-
capabilities: manifest.capabilities && manifest.capabilities.length > 0 ? manifest.capabilities : undefined
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function logManifestSummary(manifest: ModuleManifestLike | undefined, routes?: ModuleRoute[]): void {
|
|
182
|
-
if (!manifest) {
|
|
183
|
-
console.info('[fastify] manifest metadata not found.');
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const caps = manifest.capabilities?.length ? ` [${manifest.capabilities.join(', ')}]` : '';
|
|
187
|
-
const count = Array.isArray(manifest.routes) ? manifest.routes.length : Array.isArray(routes) ? routes.length : 0;
|
|
188
|
-
console.info(`[fastify] manifest name=${manifest.name ?? 'unknown'} routes=${count}${caps}`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function createEnvAccessor(): EnvAccessor {
|
|
192
|
-
return {
|
|
193
|
-
get: (name) => process.env[name],
|
|
194
|
-
require: (name) => {
|
|
195
|
-
const value = process.env[name];
|
|
196
|
-
if (value === undefined) {
|
|
197
|
-
throw new Error(`Missing required env var ${name}`);
|
|
198
|
-
}
|
|
199
|
-
return value;
|
|
200
|
-
},
|
|
201
|
-
entries: () => ({ ...process.env })
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function createRequestLogger(requestId: string, bindings: Record<string, unknown> = {}): Logger {
|
|
206
|
-
const logWithLevel = (level: LogLevel, message: string, metadata?: Record<string, unknown>) => {
|
|
207
|
-
const bindingKeys = Object.keys(bindings);
|
|
208
|
-
const suffix = bindingKeys.length ? ` ${bindingKeys.map((k) => `${k}=${JSON.stringify(bindings[k])}`).join(' ')}` : '';
|
|
209
|
-
const writer = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
|
|
210
|
-
if (metadata) {
|
|
211
|
-
writer(`[${level}] [request ${requestId}] ${message}${suffix}`, metadata);
|
|
212
|
-
} else {
|
|
213
|
-
writer(`[${level}] [request ${requestId}] ${message}${suffix}`);
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
level: 'info',
|
|
219
|
-
log: logWithLevel,
|
|
220
|
-
debug: (message, metadata) => logWithLevel('debug', message, metadata),
|
|
221
|
-
info: (message, metadata) => logWithLevel('info', message, metadata),
|
|
222
|
-
warn: (message, metadata) => logWithLevel('warn', message, metadata),
|
|
223
|
-
error: (message, metadata) => logWithLevel('error', message, metadata),
|
|
224
|
-
with(extra) {
|
|
225
|
-
return createRequestLogger(requestId, { ...bindings, ...extra });
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function extractRequestId(req: { id?: string; headers?: Record<string, unknown> }): string {
|
|
231
|
-
if (req && typeof req.id === 'string' && req.id.length > 0) {
|
|
232
|
-
return req.id;
|
|
233
|
-
}
|
|
234
|
-
const header = req?.headers?.['x-request-id'];
|
|
235
|
-
if (typeof header === 'string' && header.length > 0) {
|
|
236
|
-
return header;
|
|
237
|
-
}
|
|
238
|
-
if (Array.isArray(header) && header.length > 0) {
|
|
239
|
-
return header[0] as string;
|
|
240
|
-
}
|
|
241
|
-
try {
|
|
242
|
-
return randomUUID();
|
|
243
|
-
} catch {
|
|
244
|
-
return `${Date.now()}`;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function createReadinessTracker() {
|
|
249
|
-
let status: ReadinessStatus = 'booting';
|
|
250
|
-
let message: string | undefined;
|
|
251
|
-
return {
|
|
252
|
-
booting() {
|
|
253
|
-
status = 'booting';
|
|
254
|
-
message = undefined;
|
|
255
|
-
},
|
|
256
|
-
ready() {
|
|
257
|
-
status = 'ready';
|
|
258
|
-
message = undefined;
|
|
259
|
-
},
|
|
260
|
-
error(reason: string) {
|
|
261
|
-
status = 'error';
|
|
262
|
-
message = reason;
|
|
263
|
-
},
|
|
264
|
-
snapshot() {
|
|
265
|
-
return { status, message };
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Execute when launched directly
|
|
271
|
-
const isMain = (() => {
|
|
272
|
-
try {
|
|
273
|
-
const argv1 = process.argv?.[1];
|
|
274
|
-
if (!argv1) return false;
|
|
275
|
-
const here = new URL(import.meta.url);
|
|
276
|
-
const run = new URL(`file://${argv1}`);
|
|
277
|
-
return here.pathname === run.pathname;
|
|
278
|
-
} catch {
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
})();
|
|
282
|
-
|
|
283
|
-
if (isMain) {
|
|
284
|
-
start().catch((err) => {
|
|
285
|
-
console.error(err);
|
|
286
|
-
process.exitCode = 1;
|
|
287
|
-
});
|
|
288
|
-
}
|