@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
@@ -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
+ }