@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
@@ -7,71 +7,75 @@ export async function getBackendScaffoldAssets() {
7
7
  return [
8
8
  {
9
9
  sourcePath: path.join(templatesRoot, 'tsconfig.json'),
10
- targetPath: path.join('src', 'backend', 'tsconfig.json')
10
+ targetPath: path.join('src', 'backend', 'tsconfig.json'),
11
11
  },
12
12
  {
13
13
  sourcePath: path.join(templatesRoot, 'index.ts'),
14
- targetPath: path.join('src', 'backend', 'index.ts')
15
- },
16
- {
17
- sourcePath: path.join(templatesRoot, 'server', 'fastify.ts'),
18
- targetPath: path.join('src', 'backend', 'server', 'fastify.ts')
14
+ targetPath: path.join('src', 'backend', 'index.ts'),
19
15
  },
20
16
  {
21
17
  sourcePath: path.join(templatesRoot, 'module.ts'),
22
- targetPath: path.join('src', 'backend', 'module.ts')
18
+ targetPath: path.join('src', 'backend', 'module.ts'),
23
19
  },
24
20
  {
25
21
  sourcePath: path.join(templatesRoot, 'auth', 'adapter.ts'),
26
- targetPath: path.join('src', 'backend', 'auth', 'adapter.ts')
22
+ targetPath: path.join('src', 'backend', 'auth', 'adapter.ts'),
27
23
  },
28
24
  {
29
25
  sourcePath: path.join(templatesRoot, 'observability', 'logger.ts'),
30
- targetPath: path.join('src', 'backend', 'observability', 'logger.ts')
26
+ targetPath: path.join('src', 'backend', 'observability', 'logger.ts'),
31
27
  },
32
28
  {
33
29
  sourcePath: path.join(templatesRoot, 'observability', 'metrics.ts'),
34
- targetPath: path.join('src', 'backend', 'observability', 'metrics.ts')
30
+ targetPath: path.join('src', 'backend', 'observability', 'metrics.ts'),
35
31
  },
36
32
  {
37
33
  sourcePath: path.join(templatesRoot, 'env.ts'),
38
- targetPath: path.join('src', 'backend', 'env.ts')
34
+ targetPath: path.join('src', 'backend', 'env.ts'),
35
+ },
36
+ {
37
+ sourcePath: path.join(templatesRoot, 'session', 'store.ts'),
38
+ targetPath: path.join('src', 'backend', 'session', 'store.ts'),
39
+ },
40
+ {
41
+ sourcePath: path.join(templatesRoot, 'session', 'sqlite.ts'),
42
+ targetPath: path.join('src', 'backend', 'session', 'sqlite.ts'),
39
43
  },
40
44
  {
41
45
  sourcePath: path.join(templatesRoot, 'functions', 'hello', 'index.ts'),
42
- targetPath: path.join('src', 'backend', 'functions', 'hello', 'index.ts')
46
+ targetPath: path.join('src', 'backend', 'functions', 'hello', 'index.ts'),
43
47
  },
44
48
  {
45
49
  sourcePath: path.join(templatesRoot, 'jobs', 'nightly', 'index.ts'),
46
- targetPath: path.join('src', 'backend', 'jobs', 'nightly', 'index.ts')
50
+ targetPath: path.join('src', 'backend', 'jobs', 'nightly', 'index.ts'),
47
51
  },
48
52
  {
49
53
  sourcePath: path.join(templatesRoot, 'jobs', 'runtime.ts'),
50
- targetPath: path.join('src', 'backend', 'jobs', 'runtime.ts')
54
+ targetPath: path.join('src', 'backend', 'jobs', 'runtime.ts'),
51
55
  },
52
56
  {
53
57
  sourcePath: path.join(templatesRoot, 'jobs', 'scheduler.ts'),
54
- targetPath: path.join('src', 'backend', 'jobs', 'scheduler.ts')
58
+ targetPath: path.join('src', 'backend', 'jobs', 'scheduler.ts'),
55
59
  },
56
60
  {
57
61
  sourcePath: path.join(templatesRoot, 'db', 'connection.ts'),
58
- targetPath: path.join('src', 'backend', 'db', 'connection.ts')
62
+ targetPath: path.join('src', 'backend', 'db', 'connection.ts'),
59
63
  },
60
64
  {
61
65
  sourcePath: path.join(templatesRoot, 'db', 'migrate.ts'),
62
- targetPath: path.join('src', 'backend', 'db', 'migrate.ts')
66
+ targetPath: path.join('src', 'backend', 'db', 'migrate.ts'),
63
67
  },
64
68
  {
65
69
  sourcePath: path.join(templatesRoot, 'db', 'migrations', '0001-example.ts'),
66
- targetPath: path.join('src', 'backend', 'db', 'migrations', '0001-example.ts')
70
+ targetPath: path.join('src', 'backend', 'db', 'migrations', '0001-example.ts'),
67
71
  },
68
72
  {
69
73
  sourcePath: path.join(templatesRoot, 'db', 'types.d.ts'),
70
- targetPath: path.join('src', 'backend', 'db', 'types.d.ts')
74
+ targetPath: path.join('src', 'backend', 'db', 'types.d.ts'),
71
75
  },
72
76
  {
73
77
  sourcePath: path.join(templatesRoot, '.env.example'),
74
- targetPath: path.join('.env.example')
75
- }
78
+ targetPath: path.join('.env.example'),
79
+ },
76
80
  ];
77
81
  }
@@ -1,4 +1,4 @@
1
- const GLOBAL_KEY = '__webstirBackendTestContext__';
1
+ const GLOBAL_KEY = Symbol.for('webstir.backendTestContext');
2
2
  export function setBackendTestContext(context) {
3
3
  const target = globalThis;
4
4
  if (context) {
@@ -1,6 +1,6 @@
1
1
  import { getBackendTestContext, setBackendTestContext } from './context.js';
2
2
  import type { BackendTestCallback, BackendTestHarness, BackendTestHarnessOptions } from './types.js';
3
- export type { BackendTestCallback, BackendTestContext, BackendTestHarness, BackendTestHarnessOptions } from './types.js';
3
+ export type { BackendTestCallback, BackendTestContext, BackendTestHarness, BackendTestHarnessOptions, } from './types.js';
4
4
  export { getBackendTestContext, setBackendTestContext };
5
5
  export declare function createBackendTestHarness(options?: BackendTestHarnessOptions): Promise<BackendTestHarness>;
6
6
  export declare function backendTest(name: string, callback: BackendTestCallback): void;
@@ -2,67 +2,68 @@ import { spawn } from 'node:child_process';
2
2
  import { once } from 'node:events';
3
3
  import { existsSync } from 'node:fs';
4
4
  import { readFile } from 'node:fs/promises';
5
- import net from 'node:net';
6
5
  import path from 'node:path';
7
6
  import { getBackendTestContext, setBackendTestContext } from './context.js';
7
+ import { resolveWorkspaceRoot } from '../workspace.js';
8
8
  const DEFAULT_PORT = 4100;
9
9
  const DEFAULT_READY_TEXT = 'API server running';
10
10
  const DEFAULT_READY_TIMEOUT_MS = 15_000;
11
11
  export { getBackendTestContext, setBackendTestContext };
12
12
  export async function createBackendTestHarness(options = {}) {
13
- const workspaceRoot = options.workspaceRoot ?? process.env.WEBSTIR_WORKSPACE_ROOT ?? process.cwd();
14
- const buildRoot = options.buildRoot ?? process.env.WEBSTIR_BACKEND_BUILD_ROOT ?? path.join(workspaceRoot, 'build', 'backend');
15
- const entry = options.entry ?? process.env.WEBSTIR_BACKEND_TEST_ENTRY ?? path.join(buildRoot, 'index.js');
16
- const manifestPath = options.manifestPath ??
17
- process.env.WEBSTIR_BACKEND_TEST_MANIFEST ??
18
- path.join(workspaceRoot, '.webstir', 'backend-manifest.json');
19
- const readyText = options.readyText ?? process.env.WEBSTIR_BACKEND_TEST_READY ?? DEFAULT_READY_TEXT;
20
- const readyTimeoutMs = options.readyTimeoutMs ?? readInt(process.env.WEBSTIR_BACKEND_TEST_READY_TIMEOUT, DEFAULT_READY_TIMEOUT_MS);
13
+ const resolvedEnv = { ...process.env, ...(options.env ?? {}) };
14
+ const workspaceRoot = resolveWorkspaceRoot({
15
+ workspaceRoot: options.workspaceRoot,
16
+ env: resolvedEnv,
17
+ });
18
+ const buildRoot = resolveWorkspacePath(workspaceRoot, options.buildRoot ??
19
+ resolvedEnv.WEBSTIR_BACKEND_BUILD_ROOT ??
20
+ path.join(workspaceRoot, 'build', 'backend'));
21
+ const entry = resolveWorkspacePath(workspaceRoot, options.entry ?? resolvedEnv.WEBSTIR_BACKEND_TEST_ENTRY ?? path.join(buildRoot, 'index.js'));
22
+ const manifestPath = resolveWorkspacePath(workspaceRoot, options.manifestPath ??
23
+ resolvedEnv.WEBSTIR_BACKEND_TEST_MANIFEST ??
24
+ path.join(workspaceRoot, '.webstir', 'backend-manifest.json'));
25
+ const readyText = options.readyText ?? resolvedEnv.WEBSTIR_BACKEND_TEST_READY ?? DEFAULT_READY_TEXT;
26
+ const readyTimeoutMs = options.readyTimeoutMs ??
27
+ readInt(resolvedEnv.WEBSTIR_BACKEND_TEST_READY_TIMEOUT, DEFAULT_READY_TIMEOUT_MS);
21
28
  if (!existsSync(entry)) {
22
29
  throw new Error(`Backend test entry not found at ${entry}. Run the backend build before executing backend tests.`);
23
30
  }
24
- const requestedPort = options.port ?? readInt(process.env.WEBSTIR_BACKEND_TEST_PORT, DEFAULT_PORT);
25
- const port = await findOpenPort(requestedPort);
26
- const env = createRuntimeEnv({
27
- workspaceRoot,
28
- port,
29
- overrides: options.env
30
- });
31
31
  const manifest = await loadManifest(manifestPath);
32
- const child = spawn(process.execPath, [entry], {
33
- cwd: workspaceRoot,
34
- env,
35
- stdio: ['ignore', 'pipe', 'pipe']
32
+ const requestedPort = options.port ?? readInt(resolvedEnv.WEBSTIR_BACKEND_TEST_PORT, DEFAULT_PORT);
33
+ const { child, env, port } = await startBackendTestProcess({
34
+ workspaceRoot,
35
+ entry,
36
+ requestedPort,
37
+ baseEnv: resolvedEnv,
38
+ overrides: options.env,
39
+ readyText,
40
+ readyTimeoutMs,
36
41
  });
37
- try {
38
- await waitForReady(child, readyText, readyTimeoutMs);
39
- }
40
- catch (error) {
41
- await stopProcess(child);
42
- throw error;
43
- }
44
42
  const baseUrl = new URL(env.API_BASE_URL ?? `http://127.0.0.1:${port}`);
45
43
  const context = {
46
44
  baseUrl: baseUrl.toString(),
47
45
  url: baseUrl,
48
46
  port,
49
47
  manifest,
50
- routes: Array.isArray(manifest?.routes) ? manifest.routes : [],
48
+ routes: Array.isArray(manifest?.routes)
49
+ ? manifest.routes
50
+ : [],
51
51
  env,
52
52
  request: async (pathOrUrl = '/', init) => {
53
53
  const target = toUrl(baseUrl, pathOrUrl);
54
54
  return await fetch(target, init);
55
- }
55
+ },
56
56
  };
57
57
  return {
58
58
  context,
59
59
  async stop() {
60
60
  await stopProcess(child);
61
- }
61
+ },
62
62
  };
63
63
  }
64
64
  export function backendTest(name, callback) {
65
- const globalTest = globalThis.test;
65
+ const globalTest = globalThis
66
+ .test;
66
67
  if (typeof globalTest !== 'function') {
67
68
  throw new Error('backendTest() requires the @webstir-io/webstir-testing runtime.');
68
69
  }
@@ -89,28 +90,42 @@ function readInt(value, fallback) {
89
90
  const parsed = Number.parseInt(value, 10);
90
91
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
91
92
  }
92
- async function findOpenPort(start, attempts = 10) {
93
- let candidate = start;
94
- for (let index = 0; index < attempts; index += 1) {
95
- // eslint-disable-next-line no-await-in-loop
96
- if (await isPortAvailable(candidate)) {
97
- return candidate;
98
- }
99
- candidate += 1;
100
- }
101
- throw new Error(`Unable to find an open port for backend tests (tried starting at ${start}).`);
102
- }
103
- function isPortAvailable(port) {
104
- return new Promise((resolve) => {
105
- const server = net.createServer();
106
- server.once('error', () => {
107
- server.close(() => resolve(false));
93
+ async function startBackendTestProcess(options) {
94
+ let candidate = options.requestedPort;
95
+ let lastError = null;
96
+ for (let attempt = 0; attempt < 10; attempt += 1) {
97
+ const env = createRuntimeEnv({
98
+ workspaceRoot: options.workspaceRoot,
99
+ port: candidate,
100
+ baseEnv: options.baseEnv,
101
+ overrides: options.overrides,
108
102
  });
109
- server.once('listening', () => {
110
- server.close(() => resolve(true));
103
+ const child = spawn(process.execPath, [options.entry], {
104
+ cwd: options.workspaceRoot,
105
+ env,
106
+ stdio: ['ignore', 'pipe', 'pipe'],
111
107
  });
112
- server.listen(port, '127.0.0.1');
113
- });
108
+ const output = captureChildOutput(child);
109
+ try {
110
+ await waitForReady(child, options.readyText, options.readyTimeoutMs);
111
+ output.stop();
112
+ return { child, env, port: candidate };
113
+ }
114
+ catch (error) {
115
+ const captured = output.read();
116
+ output.stop();
117
+ await stopProcess(child);
118
+ const message = error instanceof Error ? error.message : String(error);
119
+ const failure = new Error(`Backend test server did not become ready on port ${candidate}.\nstdout:\n${captured.stdout}\nstderr:\n${captured.stderr}\nerror:\n${message}`);
120
+ if (attempt < 9 && indicatesPortInUse(captured.stdout, captured.stderr, message)) {
121
+ lastError = failure;
122
+ candidate += 1;
123
+ continue;
124
+ }
125
+ throw failure;
126
+ }
127
+ }
128
+ throw lastError ?? new Error('Backend test server did not become ready.');
114
129
  }
115
130
  function createRuntimeEnv(options) {
116
131
  const overrides = {};
@@ -119,16 +134,45 @@ function createRuntimeEnv(options) {
119
134
  overrides[key] = value;
120
135
  }
121
136
  }
122
- const baseUrl = overrides.API_BASE_URL ?? process.env.API_BASE_URL ?? `http://127.0.0.1:${options.port}`;
137
+ const baseUrl = overrides.API_BASE_URL ?? options.baseEnv.API_BASE_URL ?? `http://127.0.0.1:${options.port}`;
123
138
  return {
124
- ...process.env,
139
+ ...options.baseEnv,
125
140
  ...overrides,
126
141
  PORT: String(options.port),
127
142
  API_BASE_URL: baseUrl,
128
- NODE_ENV: overrides.NODE_ENV ?? process.env.NODE_ENV ?? 'test',
143
+ NODE_ENV: overrides.NODE_ENV ?? options.baseEnv.NODE_ENV ?? 'test',
129
144
  WORKSPACE_ROOT: options.workspaceRoot,
130
- WEBSTIR_BACKEND_TEST_RUN: '1'
145
+ WEBSTIR_BACKEND_TEST_RUN: '1',
146
+ };
147
+ }
148
+ function captureChildOutput(child) {
149
+ let stdout = '';
150
+ let stderr = '';
151
+ const onStdout = (chunk) => {
152
+ stdout += chunk.toString();
131
153
  };
154
+ const onStderr = (chunk) => {
155
+ stderr += chunk.toString();
156
+ };
157
+ child.stdout?.on('data', onStdout);
158
+ child.stderr?.on('data', onStderr);
159
+ return {
160
+ stop() {
161
+ child.stdout?.off('data', onStdout);
162
+ child.stderr?.off('data', onStderr);
163
+ },
164
+ read() {
165
+ return { stdout, stderr };
166
+ },
167
+ };
168
+ }
169
+ function indicatesPortInUse(stdout, stderr, message) {
170
+ return [stdout, stderr, message].some((value) => value.includes('EADDRINUSE') ||
171
+ value.includes('address already in use') ||
172
+ value.includes('Failed to listen at 127.0.0.1'));
173
+ }
174
+ function resolveWorkspacePath(workspaceRoot, value) {
175
+ return path.isAbsolute(value) ? path.resolve(value) : path.resolve(workspaceRoot, value);
132
176
  }
133
177
  async function loadManifest(manifestPath) {
134
178
  try {
@@ -148,7 +192,7 @@ async function waitForReady(child, readyText, timeoutMs) {
148
192
  .split('|')
149
193
  .map((token) => token.trim())
150
194
  .filter(Boolean);
151
- const readinessMatches = (line) => (normalized.length === 0 ? line.length > 0 : normalized.some((token) => line.includes(token)));
195
+ const readinessMatches = (line) => normalized.length === 0 ? line.length > 0 : normalized.some((token) => line.includes(token));
152
196
  await new Promise((resolve, reject) => {
153
197
  const cleanup = () => {
154
198
  child.stdout?.off('data', onStdout);
@@ -0,0 +1,2 @@
1
+ export declare function readTextFile(filePath: string): Promise<string>;
2
+ export declare function writeTextFile(filePath: string, contents: string): Promise<void>;
@@ -0,0 +1,13 @@
1
+ function getBunRuntime() {
2
+ const runtime = globalThis;
3
+ if (typeof runtime.Bun?.file === 'function' && typeof runtime.Bun?.write === 'function') {
4
+ return runtime.Bun;
5
+ }
6
+ throw new Error('[webstir-backend] Bun runtime is required for package-level IO.');
7
+ }
8
+ export async function readTextFile(filePath) {
9
+ return await getBunRuntime().file(filePath).text();
10
+ }
11
+ export async function writeTextFile(filePath, contents) {
12
+ await getBunRuntime().write(filePath, contents);
13
+ }
package/dist/watch.d.ts CHANGED
@@ -1,8 +1,20 @@
1
1
  export interface WatchHandle {
2
2
  stop(): Promise<void>;
3
3
  }
4
+ export interface BackendWatchEvent {
5
+ readonly type: 'build-start' | 'build-complete';
6
+ readonly succeeded?: boolean;
7
+ readonly errorCount?: number;
8
+ readonly warningCount?: number;
9
+ readonly durationMs?: number;
10
+ readonly bunBenchmarkSucceeded?: boolean;
11
+ readonly bunBenchmarkErrorCount?: number;
12
+ readonly bunBenchmarkWarningCount?: number;
13
+ readonly bunBenchmarkDurationMs?: number;
14
+ }
4
15
  export interface StartWatchOptions {
5
- readonly workspaceRoot: string;
16
+ readonly workspaceRoot?: string;
6
17
  readonly env?: Record<string, string | undefined>;
18
+ readonly onEvent?: (event: BackendWatchEvent) => void | Promise<void>;
7
19
  }
8
20
  export declare function startBackendWatch(options: StartWatchOptions): Promise<WatchHandle>;