@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
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { build as esbuild } from 'esbuild';
|
|
8
|
+
|
|
9
|
+
import { backendProvider } from '../dist/index.js';
|
|
10
|
+
|
|
11
|
+
async function createTempWorkspace(prefix = 'webstir-backend-migrate-') {
|
|
12
|
+
return await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function copyFile(src, dest) {
|
|
16
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
17
|
+
await fs.copyFile(src, dest);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function seedBackendWorkspace(workspace, name) {
|
|
21
|
+
const assets = await backendProvider.getScaffoldAssets();
|
|
22
|
+
for (const asset of assets) {
|
|
23
|
+
await copyFile(asset.sourcePath, path.join(workspace, asset.targetPath));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await fs.writeFile(
|
|
27
|
+
path.join(workspace, 'package.json'),
|
|
28
|
+
JSON.stringify(
|
|
29
|
+
{
|
|
30
|
+
name,
|
|
31
|
+
version: '0.0.0',
|
|
32
|
+
type: 'module',
|
|
33
|
+
},
|
|
34
|
+
null,
|
|
35
|
+
2,
|
|
36
|
+
),
|
|
37
|
+
'utf8',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function compileTemplateDbFiles(workspace) {
|
|
42
|
+
await esbuild({
|
|
43
|
+
entryPoints: [
|
|
44
|
+
path.join(workspace, 'src', 'backend', 'env.ts'),
|
|
45
|
+
path.join(workspace, 'src', 'backend', 'db', 'connection.ts'),
|
|
46
|
+
path.join(workspace, 'src', 'backend', 'db', 'migrate.ts'),
|
|
47
|
+
],
|
|
48
|
+
bundle: false,
|
|
49
|
+
format: 'esm',
|
|
50
|
+
platform: 'node',
|
|
51
|
+
target: 'node20',
|
|
52
|
+
outdir: path.join(workspace, 'build', 'backend'),
|
|
53
|
+
outbase: path.join(workspace, 'src', 'backend'),
|
|
54
|
+
logLevel: 'silent',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function writeBuiltMigration(workspace, file, source) {
|
|
59
|
+
const target = path.join(workspace, 'build', 'backend', 'db', 'migrations', file);
|
|
60
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
61
|
+
await fs.writeFile(target, source, 'utf8');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function runMigrate(workspace, args = [], env = {}) {
|
|
65
|
+
const child = spawn(
|
|
66
|
+
'bun',
|
|
67
|
+
[path.join(workspace, 'build', 'backend', 'db', 'migrate.js'), ...args],
|
|
68
|
+
{
|
|
69
|
+
cwd: workspace,
|
|
70
|
+
env: {
|
|
71
|
+
...process.env,
|
|
72
|
+
DATABASE_URL: `file:${path.join(workspace, 'data', 'test.sqlite')}`,
|
|
73
|
+
...env,
|
|
74
|
+
},
|
|
75
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
let stdout = '';
|
|
80
|
+
let stderr = '';
|
|
81
|
+
child.stdout.on('data', (chunk) => {
|
|
82
|
+
stdout += chunk.toString();
|
|
83
|
+
});
|
|
84
|
+
child.stderr.on('data', (chunk) => {
|
|
85
|
+
stderr += chunk.toString();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const exitCode = await new Promise((resolve) => {
|
|
89
|
+
child.once('close', resolve);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return { exitCode, stdout, stderr };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function prepareWorkspace(name) {
|
|
96
|
+
const workspace = await createTempWorkspace();
|
|
97
|
+
await seedBackendWorkspace(workspace, name);
|
|
98
|
+
await compileTemplateDbFiles(workspace);
|
|
99
|
+
return workspace;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
test('migration runner rejects unsafe DATABASE_MIGRATIONS_TABLE values', async () => {
|
|
103
|
+
const workspace = await prepareWorkspace('@demo/migrate-table-validation');
|
|
104
|
+
|
|
105
|
+
const result = await runMigrate(workspace, ['--status'], {
|
|
106
|
+
DATABASE_MIGRATIONS_TABLE: '_webstir_migrations; DROP TABLE users',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
assert.equal(result.exitCode, 1);
|
|
110
|
+
assert.match(result.stderr, /DATABASE_MIGRATIONS_TABLE must be a single SQL identifier/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('migration runner rejects duplicate migration ids before running', async () => {
|
|
114
|
+
const workspace = await prepareWorkspace('@demo/migrate-duplicate-ids');
|
|
115
|
+
await writeBuiltMigration(
|
|
116
|
+
workspace,
|
|
117
|
+
'0001-first.js',
|
|
118
|
+
`export const id = 'duplicate';
|
|
119
|
+
export async function up() {}`,
|
|
120
|
+
);
|
|
121
|
+
await writeBuiltMigration(
|
|
122
|
+
workspace,
|
|
123
|
+
'0002-second.js',
|
|
124
|
+
`export const id = 'duplicate';
|
|
125
|
+
export async function up() {}`,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const result = await runMigrate(workspace, ['--list']);
|
|
129
|
+
|
|
130
|
+
assert.equal(result.exitCode, 1);
|
|
131
|
+
assert.match(result.stderr, /Duplicate migration id\(s\): duplicate/);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('migration runner rolls back failed up migrations and leaves them pending', async () => {
|
|
135
|
+
const workspace = await prepareWorkspace('@demo/migrate-failed-up');
|
|
136
|
+
await writeBuiltMigration(
|
|
137
|
+
workspace,
|
|
138
|
+
'0001-fails.js',
|
|
139
|
+
`export const id = '0001_fails';
|
|
140
|
+
export async function up(ctx) {
|
|
141
|
+
await ctx.sql('CREATE TABLE failed_probe (id TEXT PRIMARY KEY)');
|
|
142
|
+
await ctx.sql('INSERT INTO failed_probe (id) VALUES (?)', ['before-error']);
|
|
143
|
+
throw new Error('expected migration failure');
|
|
144
|
+
}`,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const failed = await runMigrate(workspace);
|
|
148
|
+
assert.equal(failed.exitCode, 1);
|
|
149
|
+
assert.match(failed.stdout, /Applying 0001_fails/);
|
|
150
|
+
assert.match(failed.stderr, /rolled back and was not recorded as applied/);
|
|
151
|
+
|
|
152
|
+
const status = await runMigrate(workspace, ['--status']);
|
|
153
|
+
assert.equal(status.exitCode, 0);
|
|
154
|
+
assert.match(status.stdout, /Applied: 0/);
|
|
155
|
+
assert.match(status.stdout, /Pending: 1/);
|
|
156
|
+
assert.match(status.stdout, /pending 0001_fails/);
|
|
157
|
+
|
|
158
|
+
await writeBuiltMigration(
|
|
159
|
+
workspace,
|
|
160
|
+
'0001-fails.js',
|
|
161
|
+
`export const id = '0001_fails';
|
|
162
|
+
export async function up(ctx) {
|
|
163
|
+
await ctx.sql('CREATE TABLE failed_probe (id TEXT PRIMARY KEY)');
|
|
164
|
+
await ctx.sql('INSERT INTO failed_probe (id) VALUES (?)', ['after-retry']);
|
|
165
|
+
}`,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const recovered = await runMigrate(workspace);
|
|
169
|
+
assert.equal(recovered.exitCode, 0, recovered.stderr);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('migration runner status reports applied and pending local migrations', async () => {
|
|
173
|
+
const workspace = await prepareWorkspace('@demo/migrate-status');
|
|
174
|
+
await writeBuiltMigration(
|
|
175
|
+
workspace,
|
|
176
|
+
'0001-applied.js',
|
|
177
|
+
`export const id = '0001_applied';
|
|
178
|
+
export async function up(ctx) {
|
|
179
|
+
await ctx.sql('CREATE TABLE applied_probe (id TEXT PRIMARY KEY)');
|
|
180
|
+
}`,
|
|
181
|
+
);
|
|
182
|
+
await writeBuiltMigration(
|
|
183
|
+
workspace,
|
|
184
|
+
'0002-pending.js',
|
|
185
|
+
`export const id = '0002_pending';
|
|
186
|
+
export async function up(ctx) {
|
|
187
|
+
await ctx.sql('CREATE TABLE pending_probe (id TEXT PRIMARY KEY)');
|
|
188
|
+
}`,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const applied = await runMigrate(workspace, ['--steps', '1']);
|
|
192
|
+
assert.equal(applied.exitCode, 0, applied.stderr);
|
|
193
|
+
|
|
194
|
+
const status = await runMigrate(workspace, ['--status']);
|
|
195
|
+
assert.equal(status.exitCode, 0);
|
|
196
|
+
assert.match(status.stdout, /Status for _webstir_migrations/);
|
|
197
|
+
assert.match(status.stdout, /Applied: 1/);
|
|
198
|
+
assert.match(status.stdout, /Pending: 1/);
|
|
199
|
+
assert.match(status.stdout, /pending 0002_pending/);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('migration runner keeps records and rolls back failed down migrations', async () => {
|
|
203
|
+
const workspace = await prepareWorkspace('@demo/migrate-failed-down');
|
|
204
|
+
await writeBuiltMigration(
|
|
205
|
+
workspace,
|
|
206
|
+
'0001-reversible.js',
|
|
207
|
+
`export const id = '0001_reversible';
|
|
208
|
+
export async function up(ctx) {
|
|
209
|
+
await ctx.sql('CREATE TABLE reversible_probe (id TEXT PRIMARY KEY)');
|
|
210
|
+
}
|
|
211
|
+
export async function down(ctx) {
|
|
212
|
+
await ctx.sql('DROP TABLE reversible_probe');
|
|
213
|
+
throw new Error('expected down failure');
|
|
214
|
+
}`,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const applied = await runMigrate(workspace);
|
|
218
|
+
assert.equal(applied.exitCode, 0, applied.stderr);
|
|
219
|
+
|
|
220
|
+
const failed = await runMigrate(workspace, ['--down']);
|
|
221
|
+
assert.equal(failed.exitCode, 1);
|
|
222
|
+
assert.match(failed.stdout, /Reverting 0001_reversible/);
|
|
223
|
+
assert.match(failed.stderr, /record was kept so it can be retried/);
|
|
224
|
+
|
|
225
|
+
const stillApplied = await runMigrate(workspace, ['--status']);
|
|
226
|
+
assert.equal(stillApplied.exitCode, 0);
|
|
227
|
+
assert.match(stillApplied.stdout, /Applied: 1/);
|
|
228
|
+
assert.match(stillApplied.stdout, /Pending: 0/);
|
|
229
|
+
|
|
230
|
+
await writeBuiltMigration(
|
|
231
|
+
workspace,
|
|
232
|
+
'0001-reversible.js',
|
|
233
|
+
`export const id = '0001_reversible';
|
|
234
|
+
export async function up(ctx) {
|
|
235
|
+
await ctx.sql('CREATE TABLE reversible_probe (id TEXT PRIMARY KEY)');
|
|
236
|
+
}
|
|
237
|
+
export async function down(ctx) {
|
|
238
|
+
await ctx.sql('DROP TABLE reversible_probe');
|
|
239
|
+
}`,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const recovered = await runMigrate(workspace, ['--down']);
|
|
243
|
+
assert.equal(recovered.exitCode, 0, recovered.stderr);
|
|
244
|
+
|
|
245
|
+
const finalStatus = await runMigrate(workspace, ['--status']);
|
|
246
|
+
assert.equal(finalStatus.exitCode, 0);
|
|
247
|
+
assert.match(finalStatus.stdout, /Applied: 0/);
|
|
248
|
+
assert.match(finalStatus.stdout, /Pending: 1/);
|
|
249
|
+
});
|