@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.
Files changed (123) hide show
  1. package/README.md +106 -79
  2. package/dist/add.d.ts +59 -0
  3. package/dist/add.js +626 -0
  4. package/dist/build/artifacts.d.ts +115 -1
  5. package/dist/build/artifacts.js +4 -4
  6. package/dist/build/entries.js +1 -1
  7. package/dist/build/pipeline.d.ts +33 -1
  8. package/dist/build/pipeline.js +307 -65
  9. package/dist/cache/diff.js +9 -8
  10. package/dist/cache/reporters.js +1 -1
  11. package/dist/deploy-cli.d.ts +2 -0
  12. package/dist/deploy-cli.js +86 -0
  13. package/dist/diagnostics/summary.js +2 -2
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +4 -0
  16. package/dist/manifest/pipeline.js +103 -32
  17. package/dist/provider.js +35 -17
  18. package/dist/runtime/bun.d.ts +51 -0
  19. package/dist/runtime/bun.js +499 -0
  20. package/dist/runtime/core.d.ts +141 -0
  21. package/dist/runtime/core.js +316 -0
  22. package/dist/runtime/deploy-backend.d.ts +20 -0
  23. package/dist/runtime/deploy-backend.js +175 -0
  24. package/dist/runtime/deploy-shared.d.ts +43 -0
  25. package/dist/runtime/deploy-shared.js +75 -0
  26. package/dist/runtime/deploy-static.d.ts +2 -0
  27. package/dist/runtime/deploy-static.js +161 -0
  28. package/dist/runtime/deploy.d.ts +3 -0
  29. package/dist/runtime/deploy.js +91 -0
  30. package/dist/runtime/forms.d.ts +73 -0
  31. package/dist/runtime/forms.js +236 -0
  32. package/dist/runtime/request-hooks.d.ts +47 -0
  33. package/dist/runtime/request-hooks.js +102 -0
  34. package/dist/runtime/session-metadata.d.ts +13 -0
  35. package/dist/runtime/session-metadata.js +98 -0
  36. package/dist/runtime/session-runtime.d.ts +28 -0
  37. package/dist/runtime/session-runtime.js +180 -0
  38. package/dist/runtime/session.d.ts +83 -0
  39. package/dist/runtime/session.js +396 -0
  40. package/dist/runtime/views.d.ts +74 -0
  41. package/dist/runtime/views.js +221 -0
  42. package/dist/scaffold/assets.js +25 -21
  43. package/dist/testing/context.js +1 -1
  44. package/dist/testing/index.d.ts +1 -1
  45. package/dist/testing/index.js +100 -56
  46. package/dist/utils/bun.d.ts +2 -0
  47. package/dist/utils/bun.js +13 -0
  48. package/dist/watch.d.ts +13 -1
  49. package/dist/watch.js +345 -97
  50. package/dist/workspace.d.ts +8 -0
  51. package/dist/workspace.js +44 -3
  52. package/package.json +49 -14
  53. package/scripts/publish.sh +2 -92
  54. package/scripts/smoke.mjs +282 -107
  55. package/scripts/update-contract.sh +12 -10
  56. package/src/add.ts +964 -0
  57. package/src/build/artifacts.ts +49 -46
  58. package/src/build/entries.ts +12 -12
  59. package/src/build/pipeline.ts +779 -403
  60. package/src/cache/diff.ts +111 -105
  61. package/src/cache/reporters.ts +26 -26
  62. package/src/deploy-cli.ts +111 -0
  63. package/src/diagnostics/summary.ts +28 -22
  64. package/src/index.ts +11 -0
  65. package/src/manifest/pipeline.ts +328 -215
  66. package/src/provider.ts +115 -98
  67. package/src/runtime/bun.ts +793 -0
  68. package/src/runtime/core.ts +598 -0
  69. package/src/runtime/deploy-backend.ts +239 -0
  70. package/src/runtime/deploy-shared.ts +136 -0
  71. package/src/runtime/deploy-static.ts +191 -0
  72. package/src/runtime/deploy.ts +143 -0
  73. package/src/runtime/forms.ts +364 -0
  74. package/src/runtime/request-hooks.ts +165 -0
  75. package/src/runtime/session-metadata.ts +135 -0
  76. package/src/runtime/session-runtime.ts +267 -0
  77. package/src/runtime/session.ts +642 -0
  78. package/src/runtime/views.ts +385 -0
  79. package/src/scaffold/assets.ts +77 -73
  80. package/src/testing/context.js +8 -9
  81. package/src/testing/context.ts +9 -9
  82. package/src/testing/index.d.ts +14 -3
  83. package/src/testing/index.js +254 -175
  84. package/src/testing/index.ts +298 -195
  85. package/src/testing/types.d.ts +18 -19
  86. package/src/testing/types.ts +18 -18
  87. package/src/utils/bun.ts +26 -0
  88. package/src/watch.ts +503 -99
  89. package/src/workspace.ts +59 -3
  90. package/templates/backend/.env.example +15 -0
  91. package/templates/backend/auth/adapter.ts +335 -36
  92. package/templates/backend/db/connection.ts +190 -65
  93. package/templates/backend/db/migrate.ts +149 -43
  94. package/templates/backend/db/types.d.ts +1 -1
  95. package/templates/backend/env.ts +132 -20
  96. package/templates/backend/functions/hello/index.ts +1 -2
  97. package/templates/backend/index.ts +15 -508
  98. package/templates/backend/jobs/nightly/index.ts +1 -1
  99. package/templates/backend/jobs/runtime.ts +24 -11
  100. package/templates/backend/jobs/scheduler.ts +208 -46
  101. package/templates/backend/module.ts +227 -13
  102. package/templates/backend/observability/logger.ts +2 -12
  103. package/templates/backend/observability/metrics.ts +8 -5
  104. package/templates/backend/session/sqlite.ts +152 -0
  105. package/templates/backend/session/store.ts +45 -0
  106. package/templates/backend/tsconfig.json +1 -1
  107. package/tests/add.test.js +327 -0
  108. package/tests/authAdapter.test.js +315 -0
  109. package/tests/bundlerParity.test.js +217 -0
  110. package/tests/cacheReporter.test.js +10 -10
  111. package/tests/dbConnection.test.js +209 -0
  112. package/tests/deploy.test.js +357 -0
  113. package/tests/envLoader.test.js +271 -17
  114. package/tests/integration.test.js +2432 -3
  115. package/tests/jobsScheduler.test.js +253 -0
  116. package/tests/manifest.test.js +287 -12
  117. package/tests/migrationRunner.test.js +249 -0
  118. package/tests/sessionScaffoldStore.test.js +752 -0
  119. package/tests/sessionStore.test.js +490 -0
  120. package/tests/testing.test.js +252 -0
  121. package/tests/watch.test.js +192 -32
  122. package/tsconfig.json +3 -10
  123. 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
- }