@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,267 @@
|
|
|
1
|
+
export type SessionRuntimeFormIssueCode = 'validation' | 'auth' | 'csrf';
|
|
2
|
+
export type SessionRuntimeFormValue = string | string[];
|
|
3
|
+
export type SessionRuntimeFormValues = Record<string, SessionRuntimeFormValue>;
|
|
4
|
+
|
|
5
|
+
export interface SessionRuntimeFormIssue {
|
|
6
|
+
code?: SessionRuntimeFormIssueCode;
|
|
7
|
+
field?: string;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SessionRuntimeStoredFormState {
|
|
12
|
+
values: SessionRuntimeFormValues;
|
|
13
|
+
issues: SessionRuntimeFormIssue[];
|
|
14
|
+
createdAt: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SessionRuntimeFormState {
|
|
18
|
+
csrf: Record<string, string>;
|
|
19
|
+
states: Record<string, SessionRuntimeStoredFormState>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SessionRuntimeState {
|
|
23
|
+
form?: SessionRuntimeFormState;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SESSION_RUNTIME_KEY = Symbol.for('webstir.webstir-backend.session-runtime');
|
|
27
|
+
const EPOCH_ISO = '1970-01-01T00:00:00.000Z';
|
|
28
|
+
|
|
29
|
+
export function attachSessionRuntimeState<TSession extends Record<string, unknown>>(
|
|
30
|
+
session: TSession,
|
|
31
|
+
runtime: SessionRuntimeState | undefined,
|
|
32
|
+
): TSession {
|
|
33
|
+
const normalized = cloneSessionRuntimeState(runtime);
|
|
34
|
+
if (!hasSessionRuntimeState(normalized)) {
|
|
35
|
+
Reflect.deleteProperty(session, SESSION_RUNTIME_KEY);
|
|
36
|
+
return session;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Object.defineProperty(session, SESSION_RUNTIME_KEY, {
|
|
40
|
+
configurable: true,
|
|
41
|
+
enumerable: false,
|
|
42
|
+
value: normalized,
|
|
43
|
+
writable: true,
|
|
44
|
+
});
|
|
45
|
+
return session;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function cloneSessionRuntimeState(
|
|
49
|
+
runtime: SessionRuntimeState | undefined,
|
|
50
|
+
): SessionRuntimeState | undefined {
|
|
51
|
+
if (!runtime || !isRecord(runtime)) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const form = cloneSessionRuntimeFormState(runtime.form);
|
|
56
|
+
if (!form) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { form };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function coerceSessionRuntimeFormState(value: unknown): SessionRuntimeFormState | undefined {
|
|
64
|
+
return cloneSessionRuntimeFormState(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getFormSessionRuntimeState(
|
|
68
|
+
session: Record<string, unknown>,
|
|
69
|
+
): SessionRuntimeFormState {
|
|
70
|
+
const existing = readAttachedSessionRuntimeState(session);
|
|
71
|
+
if (existing?.form) {
|
|
72
|
+
existing.form.csrf ??= {};
|
|
73
|
+
existing.form.states ??= {};
|
|
74
|
+
return existing.form;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const created: SessionRuntimeFormState = {
|
|
78
|
+
csrf: {},
|
|
79
|
+
states: {},
|
|
80
|
+
};
|
|
81
|
+
const runtime = {
|
|
82
|
+
...(existing ?? {}),
|
|
83
|
+
form: created,
|
|
84
|
+
};
|
|
85
|
+
Object.defineProperty(session, SESSION_RUNTIME_KEY, {
|
|
86
|
+
configurable: true,
|
|
87
|
+
enumerable: false,
|
|
88
|
+
value: runtime,
|
|
89
|
+
writable: true,
|
|
90
|
+
});
|
|
91
|
+
return created;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function hasSessionRuntimeState(runtime: SessionRuntimeState | undefined): boolean {
|
|
95
|
+
if (!runtime?.form) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
Object.keys(runtime.form.csrf ?? {}).length > 0 ||
|
|
101
|
+
Object.keys(runtime.form.states ?? {}).length > 0
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function mergeSessionRuntimeState(
|
|
106
|
+
left: SessionRuntimeState | undefined,
|
|
107
|
+
right: SessionRuntimeState | undefined,
|
|
108
|
+
): SessionRuntimeState | undefined {
|
|
109
|
+
const leftClone = cloneSessionRuntimeState(left);
|
|
110
|
+
const rightClone = cloneSessionRuntimeState(right);
|
|
111
|
+
|
|
112
|
+
if (!leftClone) {
|
|
113
|
+
return rightClone;
|
|
114
|
+
}
|
|
115
|
+
if (!rightClone) {
|
|
116
|
+
return leftClone;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
form:
|
|
121
|
+
leftClone.form || rightClone.form
|
|
122
|
+
? {
|
|
123
|
+
csrf: {
|
|
124
|
+
...(leftClone.form?.csrf ?? {}),
|
|
125
|
+
...(rightClone.form?.csrf ?? {}),
|
|
126
|
+
},
|
|
127
|
+
states: {
|
|
128
|
+
...(leftClone.form?.states ?? {}),
|
|
129
|
+
...(rightClone.form?.states ?? {}),
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
: undefined,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function pruneSessionRuntimeState(session: Record<string, unknown>): void {
|
|
137
|
+
const runtime = readAttachedSessionRuntimeState(session);
|
|
138
|
+
if (!runtime) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
runtime.form &&
|
|
144
|
+
Object.keys(runtime.form.csrf ?? {}).length === 0 &&
|
|
145
|
+
Object.keys(runtime.form.states ?? {}).length === 0
|
|
146
|
+
) {
|
|
147
|
+
delete runtime.form;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!hasSessionRuntimeState(runtime)) {
|
|
151
|
+
Reflect.deleteProperty(session, SESSION_RUNTIME_KEY);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function readSessionRuntimeState(
|
|
156
|
+
session: Record<string, unknown> | null | undefined,
|
|
157
|
+
): SessionRuntimeState | undefined {
|
|
158
|
+
if (!session || !isRecord(session)) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return cloneSessionRuntimeState(readAttachedSessionRuntimeState(session));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function readAttachedSessionRuntimeState(
|
|
165
|
+
session: Record<string, unknown>,
|
|
166
|
+
): SessionRuntimeState | undefined {
|
|
167
|
+
const runtime = (session as Record<PropertyKey, unknown>)[SESSION_RUNTIME_KEY];
|
|
168
|
+
return isRecord(runtime) ? (runtime as SessionRuntimeState) : undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function cloneSessionRuntimeFormState(value: unknown): SessionRuntimeFormState | undefined {
|
|
172
|
+
if (!isRecord(value)) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
csrf: cloneSessionRuntimeCsrfState(value.csrf),
|
|
178
|
+
states: cloneSessionRuntimeStoredStateMap(value.states),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function cloneSessionRuntimeCsrfState(value: unknown): Record<string, string> {
|
|
183
|
+
if (!isRecord(value)) {
|
|
184
|
+
return {};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return Object.fromEntries(
|
|
188
|
+
Object.entries(value).flatMap(([key, candidate]) =>
|
|
189
|
+
typeof candidate === 'string' ? [[key, candidate]] : [],
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function cloneSessionRuntimeStoredStateMap(
|
|
195
|
+
value: unknown,
|
|
196
|
+
): Record<string, SessionRuntimeStoredFormState> {
|
|
197
|
+
if (!isRecord(value)) {
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return Object.fromEntries(
|
|
202
|
+
Object.entries(value).flatMap(([key, candidate]) => {
|
|
203
|
+
const state = cloneSessionRuntimeStoredFormState(candidate);
|
|
204
|
+
return state ? [[key, state]] : [];
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function cloneSessionRuntimeStoredFormState(
|
|
210
|
+
value: unknown,
|
|
211
|
+
): SessionRuntimeStoredFormState | undefined {
|
|
212
|
+
if (!isRecord(value)) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
values: cloneSessionRuntimeFormValues(value.values),
|
|
218
|
+
issues: cloneSessionRuntimeIssues(value.issues),
|
|
219
|
+
createdAt: typeof value.createdAt === 'string' ? value.createdAt : EPOCH_ISO,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function cloneSessionRuntimeFormValues(value: unknown): SessionRuntimeFormValues {
|
|
224
|
+
if (!isRecord(value)) {
|
|
225
|
+
return {};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const values: SessionRuntimeFormValues = {};
|
|
229
|
+
for (const [key, candidate] of Object.entries(value)) {
|
|
230
|
+
if (typeof candidate === 'string') {
|
|
231
|
+
values[key] = candidate;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (Array.isArray(candidate) && candidate.every((item) => typeof item === 'string')) {
|
|
235
|
+
values[key] = [...candidate];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return values;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function cloneSessionRuntimeIssues(value: unknown): SessionRuntimeFormIssue[] {
|
|
242
|
+
if (!Array.isArray(value)) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return value.flatMap((candidate) => {
|
|
247
|
+
if (!isRecord(candidate) || typeof candidate.message !== 'string') {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return [
|
|
252
|
+
{
|
|
253
|
+
code: normalizeIssueCode(candidate.code),
|
|
254
|
+
field: typeof candidate.field === 'string' ? candidate.field : undefined,
|
|
255
|
+
message: candidate.message,
|
|
256
|
+
},
|
|
257
|
+
];
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function normalizeIssueCode(value: unknown): SessionRuntimeFormIssueCode | undefined {
|
|
262
|
+
return value === 'validation' || value === 'auth' || value === 'csrf' ? value : undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function isRecord(value: unknown): value is Record<string | symbol, unknown> {
|
|
266
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
267
|
+
}
|