@flowdular/sdk 0.1.0 → 0.2.1

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 (175) hide show
  1. package/.ai/skills/test-hardening/SKILL.md +1 -1
  2. package/README.md +69 -12
  3. package/assets/flowdular-banner.png +0 -0
  4. package/modules/agents/package.json +1 -1
  5. package/modules/auth/package.json +1 -1
  6. package/modules/auth/src/client/AuthenticationCore.tsrx +3 -1
  7. package/modules/auth/src/server/endpoints.ts +3 -1
  8. package/modules/auth/src/server/runtime.ts +4 -0
  9. package/modules/automations/package.json +1 -1
  10. package/modules/automations-workflows-integration/package.json +1 -1
  11. package/modules/profile/package.json +1 -1
  12. package/modules/sandbox/package.json +1 -1
  13. package/modules/system/package.json +1 -1
  14. package/modules/users/package.json +1 -1
  15. package/modules/workflows/package.json +1 -1
  16. package/package.json +5 -13
  17. package/packages/ai-provider/package.json +1 -1
  18. package/packages/cli-protocol/package.json +1 -1
  19. package/packages/client/package.json +1 -1
  20. package/packages/client/src/routing.ts +39 -2
  21. package/packages/client/src/shell/BrandMark.tsrx +5 -1
  22. package/packages/client/src/shell/navigation.ts +3 -1
  23. package/packages/client/src/state.ts +6 -2
  24. package/packages/contracts/package.json +1 -1
  25. package/packages/contracts/schemas/project.schema.json +16 -42
  26. package/packages/database/package.json +1 -1
  27. package/packages/database-pglite/package.json +2 -2
  28. package/packages/database-pglite/src/driver.ts +21 -3
  29. package/packages/database-testing/package.json +1 -1
  30. package/packages/dev-console/package.json +1 -1
  31. package/packages/harness/package.json +1 -1
  32. package/packages/kernel/package.json +1 -1
  33. package/packages/server/package.json +1 -1
  34. package/packages/server/src/application-routes.ts +80 -8
  35. package/packages/server/src/index.ts +4 -1
  36. package/packages/server/src/web-html.ts +9 -5
  37. package/packages/server/src/web.ts +59 -7
  38. package/packages/ui/package.json +1 -1
  39. package/packages/coding-agent/package.json +0 -44
  40. package/packages/coding-agent/src/drivers/byok.ts +0 -322
  41. package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
  42. package/packages/coding-agent/src/drivers/codex.ts +0 -281
  43. package/packages/coding-agent/src/index.ts +0 -56
  44. package/packages/coding-agent/src/registry.ts +0 -119
  45. package/packages/coding-agent/src/roles/contract.ts +0 -60
  46. package/packages/coding-agent/src/roles/defaults.ts +0 -134
  47. package/packages/coding-agent/src/roles/handoff.ts +0 -41
  48. package/packages/coding-agent/src/roles/registry.ts +0 -156
  49. package/packages/coding-agent/src/roles/skills.ts +0 -72
  50. package/packages/coding-agent/src/roles/sync.ts +0 -88
  51. package/packages/coding-agent/src/types.ts +0 -102
  52. package/packages/coding-agent/src/workspace.ts +0 -275
  53. package/packages/sandbox/README.md +0 -691
  54. package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
  55. package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
  56. package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
  57. package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
  58. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  59. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  60. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  61. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  62. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  63. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  64. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  65. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  66. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  67. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  68. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  69. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  70. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  71. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  72. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  73. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  74. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  75. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  76. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  77. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  78. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  79. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  80. package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  81. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  82. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  83. package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  84. package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
  85. package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
  86. package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
  87. package/packages/sandbox/dist/client/favicon.svg +0 -1
  88. package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
  89. package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
  90. package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
  91. package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
  92. package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
  93. package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
  94. package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
  95. package/packages/sandbox/dist/server/entry.js +0 -91671
  96. package/packages/sandbox/dist/server/favicon.svg +0 -1
  97. package/packages/sandbox/dist/server/index.html +0 -161
  98. package/packages/sandbox/index.html +0 -159
  99. package/packages/sandbox/octane.config.ts +0 -36
  100. package/packages/sandbox/package.json +0 -83
  101. package/packages/sandbox/public/favicon.svg +0 -1
  102. package/packages/sandbox/public/og.png +0 -0
  103. package/packages/sandbox/src/App.tsrx +0 -924
  104. package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
  105. package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
  106. package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
  107. package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
  108. package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
  109. package/packages/sandbox/src/client/GateResults.tsrx +0 -60
  110. package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
  111. package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
  112. package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
  113. package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
  114. package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
  115. package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
  116. package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
  117. package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
  118. package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
  119. package/packages/sandbox/src/client/api.ts +0 -797
  120. package/packages/sandbox/src/client/attachments.ts +0 -73
  121. package/packages/sandbox/src/client/dashboard.ts +0 -46
  122. package/packages/sandbox/src/client/github-settings.ts +0 -48
  123. package/packages/sandbox/src/client/highlight.ts +0 -105
  124. package/packages/sandbox/src/client/i18n.ts +0 -32
  125. package/packages/sandbox/src/client/locales/en.json +0 -466
  126. package/packages/sandbox/src/client/locales/pl.json +0 -466
  127. package/packages/sandbox/src/client/session-labels.ts +0 -77
  128. package/packages/sandbox/src/client/state.ts +0 -108
  129. package/packages/sandbox/src/index.ts +0 -10
  130. package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
  131. package/packages/sandbox/src/preview/load-module.ts +0 -16
  132. package/packages/sandbox/src/server/attachments.ts +0 -308
  133. package/packages/sandbox/src/server/auto-review.ts +0 -181
  134. package/packages/sandbox/src/server/checkpoints.ts +0 -90
  135. package/packages/sandbox/src/server/config.ts +0 -460
  136. package/packages/sandbox/src/server/dashboard.ts +0 -245
  137. package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
  138. package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
  139. package/packages/sandbox/src/server/delivery/index.ts +0 -83
  140. package/packages/sandbox/src/server/delivery/local.ts +0 -151
  141. package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
  142. package/packages/sandbox/src/server/delivery/plan.ts +0 -218
  143. package/packages/sandbox/src/server/delivery/policies.ts +0 -239
  144. package/packages/sandbox/src/server/delivery/record.ts +0 -41
  145. package/packages/sandbox/src/server/delivery/steps.ts +0 -464
  146. package/packages/sandbox/src/server/delivery/types.ts +0 -158
  147. package/packages/sandbox/src/server/dependencies.ts +0 -88
  148. package/packages/sandbox/src/server/diff.ts +0 -230
  149. package/packages/sandbox/src/server/gates.ts +0 -388
  150. package/packages/sandbox/src/server/index.ts +0 -139
  151. package/packages/sandbox/src/server/path-guard.ts +0 -282
  152. package/packages/sandbox/src/server/planning.ts +0 -749
  153. package/packages/sandbox/src/server/platform-client.ts +0 -189
  154. package/packages/sandbox/src/server/preview-database-host.ts +0 -340
  155. package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
  156. package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
  157. package/packages/sandbox/src/server/preview-database.ts +0 -109
  158. package/packages/sandbox/src/server/preview-ipc.ts +0 -15
  159. package/packages/sandbox/src/server/preview-modules.ts +0 -83
  160. package/packages/sandbox/src/server/preview-revision.ts +0 -44
  161. package/packages/sandbox/src/server/preview-runtime.ts +0 -467
  162. package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
  163. package/packages/sandbox/src/server/preview-worker.ts +0 -168
  164. package/packages/sandbox/src/server/reference.ts +0 -251
  165. package/packages/sandbox/src/server/routes.ts +0 -2258
  166. package/packages/sandbox/src/server/runtime.ts +0 -188
  167. package/packages/sandbox/src/server/session-owner.ts +0 -23
  168. package/packages/sandbox/src/server/sessions.ts +0 -849
  169. package/packages/sandbox/src/server/spec.ts +0 -471
  170. package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
  171. package/packages/sandbox/src/server/turns.ts +0 -993
  172. package/packages/sandbox/src/server/workspace-install.ts +0 -266
  173. package/packages/sandbox/src/server/workspace-root.ts +0 -73
  174. package/packages/sandbox/src/styles.css +0 -1573
  175. package/packages/sandbox/vite.config.ts +0 -133
@@ -1,356 +0,0 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
- import {
3
- assertNotAborted,
4
- databaseDialectSql,
5
- DatabaseError,
6
- operationSignal,
7
- type DatabaseAdapterLease,
8
- type DatabaseCapabilities,
9
- type DatabaseCommandResult,
10
- type DatabaseHandle,
11
- type DatabaseOperationOptions,
12
- type DatabaseProvider,
13
- type DatabaseProviderRequest,
14
- type DatabaseQueryResult,
15
- type DatabaseRow,
16
- type DatabaseSchemaIntrospector,
17
- type DatabaseSession,
18
- type DatabaseStatement,
19
- type DatabaseTransaction,
20
- type DatabaseTransactionOptions,
21
- } from '@flowdular/sdk/database';
22
- import {
23
- isPreviewDatabaseReply,
24
- previewDatabaseError,
25
- previewDatabaseFailure,
26
- PREVIEW_DATABASE_REQUEST,
27
- type PreviewDatabaseCall,
28
- type PreviewDatabaseGrant,
29
- type PreviewDatabaseLeaseRequest,
30
- type PreviewDatabaseReply,
31
- type PreviewDatabaseRequest,
32
- type PreviewDatabaseTarget,
33
- type PreviewDatabaseTransactionOptions,
34
- } from './preview-database-protocol.ts';
35
-
36
- export interface PreviewDatabasePort {
37
- send(request: PreviewDatabaseRequest): void;
38
- /** Every message the host puts on the channel, database or not. */
39
- subscribe(listener: (message: unknown) => void): void;
40
- }
41
-
42
- interface LeaseMetadata {
43
- readonly adapterId: string;
44
- readonly dialectId: string;
45
- readonly capabilities: DatabaseCapabilities;
46
- }
47
-
48
- function deadline(options: DatabaseOperationOptions): {
49
- readonly timeoutMs?: number;
50
- } {
51
- return options.timeoutMs === undefined
52
- ? {}
53
- : { timeoutMs: options.timeoutMs };
54
- }
55
-
56
- function combinedSignal(
57
- base: AbortSignal | undefined,
58
- options: DatabaseOperationOptions,
59
- ): AbortSignal | undefined {
60
- const current = operationSignal(options);
61
- return base && current ? AbortSignal.any([base, current]) : (base ?? current);
62
- }
63
-
64
- function leaseRequest(
65
- request: DatabaseProviderRequest,
66
- ): PreviewDatabaseLeaseRequest {
67
- return {
68
- namespace: request.namespace,
69
- purpose: request.purpose,
70
- ...(request.requirements === undefined
71
- ? {}
72
- : { requirements: request.requirements }),
73
- ...deadline(request),
74
- };
75
- }
76
-
77
- function transactionOptions(
78
- options: DatabaseTransactionOptions,
79
- ): PreviewDatabaseTransactionOptions {
80
- return {
81
- ...(options.access === undefined ? {} : { access: options.access }),
82
- ...(options.isolation === undefined
83
- ? {}
84
- : { isolation: options.isolation }),
85
- ...(options.tenantId === undefined ? {} : { tenantId: options.tenantId }),
86
- ...deadline(options),
87
- };
88
- }
89
-
90
- /**
91
- * A database provider that satisfies the ordinary contract while its engine
92
- * lives in the parent process. Draft module code never learns it is remote.
93
- */
94
- export function createRemoteDatabaseProvider(
95
- port: PreviewDatabasePort,
96
- ): DatabaseProvider {
97
- const pending = new Map<number, (reply: PreviewDatabaseReply) => void>();
98
- const leases = new Set<number>();
99
- /* A transaction callback runs here while its connection stays pinned in the
100
- parent, which cannot see this callback at all. The root handle is refused
101
- locally instead: the embedded engine is a single connection, so a root
102
- operation would queue behind the transaction that is waiting for it. */
103
- const inTransaction = new AsyncLocalStorage<true>();
104
- let requests = 0;
105
- let resources = 0;
106
- let disposed = false;
107
-
108
- port.subscribe((message) => {
109
- if (!isPreviewDatabaseReply(message)) return;
110
- const settle = pending.get(message.id);
111
- if (!settle) return;
112
- pending.delete(message.id);
113
- settle(message);
114
- });
115
-
116
- async function call<T>(
117
- payload: PreviewDatabaseCall,
118
- signal?: AbortSignal,
119
- ): Promise<T> {
120
- assertNotAborted(signal);
121
- const id = ++requests;
122
- return await new Promise<T>((resolve, reject) => {
123
- /* The parent keeps working on a request whose local deadline fired.
124
- Dropping the entry frees the caller; the reply is discarded. */
125
- const abandon = () => {
126
- if (!pending.delete(id)) return;
127
- reject(
128
- new DatabaseError(
129
- 'OPERATION_ABORTED',
130
- 'Database operation was aborted.',
131
- { cause: signal?.reason },
132
- ),
133
- );
134
- };
135
- pending.set(id, (reply) => {
136
- signal?.removeEventListener('abort', abandon);
137
- if (reply.ok) resolve(reply.value as T);
138
- else reject(previewDatabaseError(reply.error));
139
- });
140
- signal?.addEventListener('abort', abandon, { once: true });
141
- try {
142
- port.send({ type: PREVIEW_DATABASE_REQUEST, id, call: payload });
143
- } catch (cause) {
144
- pending.delete(id);
145
- signal?.removeEventListener('abort', abandon);
146
- reject(
147
- new DatabaseError(
148
- 'ADAPTER_DISPOSED',
149
- 'The preview database channel is closed.',
150
- { cause },
151
- ),
152
- );
153
- }
154
- });
155
- }
156
-
157
- function remoteSession(
158
- target: PreviewDatabaseTarget,
159
- metadata: LeaseMetadata,
160
- base: AbortSignal | undefined,
161
- guard: () => void,
162
- ): DatabaseSession {
163
- const schema: DatabaseSchemaIntrospector = {
164
- async hasTable(name, options = {}) {
165
- guard();
166
- return await call<boolean>(
167
- { kind: 'has-table', target, name, ...deadline(options) },
168
- combinedSignal(base, options),
169
- );
170
- },
171
- async hasColumn(table, column, options = {}) {
172
- guard();
173
- return await call<boolean>(
174
- { kind: 'has-column', target, table, column, ...deadline(options) },
175
- combinedSignal(base, options),
176
- );
177
- },
178
- async hasIndex(name, options = {}) {
179
- guard();
180
- return await call<boolean>(
181
- { kind: 'has-index', target, name, ...deadline(options) },
182
- combinedSignal(base, options),
183
- );
184
- },
185
- };
186
- return {
187
- adapterId: metadata.adapterId,
188
- dialectId: metadata.dialectId,
189
- capabilities: metadata.capabilities,
190
- schema,
191
- async query<Row extends DatabaseRow = DatabaseRow>(
192
- statement: DatabaseStatement,
193
- options: DatabaseOperationOptions = {},
194
- ): Promise<DatabaseQueryResult<Row>> {
195
- guard();
196
- return await call<DatabaseQueryResult<Row>>(
197
- { kind: 'query', target, statement, ...deadline(options) },
198
- combinedSignal(base, options),
199
- );
200
- },
201
- async execute(
202
- statement: DatabaseStatement,
203
- options: DatabaseOperationOptions = {},
204
- ): Promise<DatabaseCommandResult> {
205
- guard();
206
- return await call<DatabaseCommandResult>(
207
- { kind: 'execute', target, statement, ...deadline(options) },
208
- combinedSignal(base, options),
209
- );
210
- },
211
- async executeScript(
212
- script: string,
213
- options: DatabaseOperationOptions = {},
214
- ): Promise<void> {
215
- guard();
216
- await call<undefined>(
217
- { kind: 'script', target, script, ...deadline(options) },
218
- combinedSignal(base, options),
219
- );
220
- },
221
- };
222
- }
223
-
224
- function remoteHandle(
225
- lease: number,
226
- metadata: LeaseMetadata,
227
- ): DatabaseHandle {
228
- const assertRoot = () => {
229
- if (!inTransaction.getStore()) return;
230
- throw new DatabaseError(
231
- 'TRANSACTION_CONTEXT_MISUSE',
232
- 'Use the transaction argument inside a transaction callback.',
233
- );
234
- };
235
- return {
236
- ...remoteSession({ lease }, metadata, undefined, assertRoot),
237
- async transaction<T>(
238
- operation: (transaction: DatabaseTransaction) => Promise<T>,
239
- options: DatabaseTransactionOptions = {},
240
- ): Promise<T> {
241
- assertRoot();
242
- const signal = operationSignal(options);
243
- const transaction = ++resources;
244
- try {
245
- await call<undefined>(
246
- {
247
- kind: 'begin',
248
- lease,
249
- transaction,
250
- options: transactionOptions(options),
251
- },
252
- signal,
253
- );
254
- } catch (error) {
255
- /* A begin whose local deadline fired may still have pinned the
256
- parent connection, so the rollback is asked for by token. */
257
- void call<undefined>({
258
- kind: 'settle',
259
- transaction,
260
- error: previewDatabaseFailure(error),
261
- }).catch(() => undefined);
262
- throw error;
263
- }
264
- let active = true;
265
- const guard = () => {
266
- if (active) return;
267
- throw new DatabaseError(
268
- 'TRANSACTION_CONTEXT_MISUSE',
269
- 'The preview database transaction callback has already ended.',
270
- );
271
- };
272
- const pinned: DatabaseTransaction = {
273
- ...remoteSession({ lease, transaction }, metadata, signal, guard),
274
- async acquireMigrationLock(namespace: string): Promise<void> {
275
- guard();
276
- await call<undefined>(
277
- { kind: 'migration-lock', transaction, namespace },
278
- signal,
279
- );
280
- },
281
- };
282
- let value: T;
283
- try {
284
- value = await inTransaction.run(true, () => operation(pinned));
285
- } catch (error) {
286
- active = false;
287
- /* The settle carries no signal: an aborted transaction still has
288
- to reach the parent, which is holding the connection open. */
289
- await call<undefined>({
290
- kind: 'settle',
291
- transaction,
292
- error: previewDatabaseFailure(error),
293
- }).catch(() => undefined);
294
- throw error;
295
- }
296
- active = false;
297
- await call<undefined>({ kind: 'settle', transaction, error: null });
298
- return value;
299
- },
300
- };
301
- }
302
-
303
- return {
304
- async acquire(
305
- request: DatabaseProviderRequest,
306
- ): Promise<DatabaseAdapterLease> {
307
- if (disposed) {
308
- throw new DatabaseError(
309
- 'ADAPTER_DISPOSED',
310
- 'The preview database provider is not accepting leases.',
311
- );
312
- }
313
- const signal = operationSignal(request);
314
- const lease = ++resources;
315
- let granted: PreviewDatabaseGrant;
316
- try {
317
- granted = await call<PreviewDatabaseGrant>(
318
- { kind: 'acquire', lease, request: leaseRequest(request) },
319
- signal,
320
- );
321
- } catch (error) {
322
- void call<undefined>({ kind: 'release', lease }).catch(() => undefined);
323
- throw error;
324
- }
325
- leases.add(lease);
326
- const metadata: LeaseMetadata = {
327
- adapterId: granted.adapterId,
328
- dialectId: granted.dialectId,
329
- capabilities: Object.freeze({
330
- ...granted.capabilities,
331
- sql: databaseDialectSql(granted.dialectId),
332
- }),
333
- };
334
- const database = remoteHandle(lease, metadata);
335
- let released = false;
336
- return {
337
- database,
338
- async release(): Promise<void> {
339
- if (released) return;
340
- released = true;
341
- leases.delete(lease);
342
- await call<undefined>({ kind: 'release', lease });
343
- },
344
- };
345
- },
346
- async dispose(): Promise<void> {
347
- if (disposed) return;
348
- disposed = true;
349
- const open = [...leases];
350
- leases.clear();
351
- await Promise.allSettled(
352
- open.map((lease) => call<undefined>({ kind: 'release', lease })),
353
- );
354
- },
355
- };
356
- }
@@ -1,109 +0,0 @@
1
- import { join } from 'node:path';
2
- import {
3
- assertNamespace,
4
- DatabaseError,
5
- PostgresDatabaseAdapter,
6
- type DatabaseAdapter,
7
- type DatabaseAdapterLease,
8
- type DatabaseProvider,
9
- type DatabaseProviderRequest,
10
- } from '@flowdular/sdk/database';
11
- import { createPgliteCluster } from '@flowdular/sdk/database-pglite';
12
-
13
- const RUNTIME_ROLE = 'coreloom_runtime';
14
- const BACKGROUND_ROLE = 'coreloom_background';
15
-
16
- /* Created before the first lease, so every table the migration lease creates is
17
- reachable by the runtime role without a second grant step. */
18
- const BOOTSTRAP = `DO $$
19
- BEGIN
20
- IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${RUNTIME_ROLE}') THEN
21
- CREATE ROLE ${RUNTIME_ROLE} NOSUPERUSER NOBYPASSRLS;
22
- END IF;
23
- IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${BACKGROUND_ROLE}') THEN
24
- CREATE ROLE ${BACKGROUND_ROLE} NOSUPERUSER NOBYPASSRLS;
25
- END IF;
26
- END
27
- $$;
28
- GRANT USAGE ON SCHEMA public TO ${RUNTIME_ROLE}, ${BACKGROUND_ROLE};
29
- ALTER DEFAULT PRIVILEGES IN SCHEMA public
30
- GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${RUNTIME_ROLE};
31
- `;
32
-
33
- /* A preview owns its database the way it owns its authentication runtime: one
34
- embedded PostgreSQL under the session data directory, the only place the
35
- preview worker may write, thrown away with the session. A draft therefore
36
- meets the same forced row-level security a deployment enforces, and it is
37
- never handed a deployment adapter or a DSN. */
38
- export function createPreviewDatabaseProvider(
39
- dataPath: string,
40
- ): DatabaseProvider {
41
- const cluster = createPgliteCluster({
42
- dataDirectory: join(dataPath, 'preview-postgres'),
43
- bootstrap: BOOTSTRAP,
44
- });
45
- let adapters: {
46
- migrator: DatabaseAdapter;
47
- runtime: DatabaseAdapter;
48
- background: DatabaseAdapter;
49
- } | null = null;
50
- let disposed = false;
51
-
52
- const opened = () => {
53
- adapters ??= {
54
- migrator: new PostgresDatabaseAdapter({ pool: cluster.pool() }),
55
- runtime: new PostgresDatabaseAdapter({
56
- pool: cluster.pool(RUNTIME_ROLE),
57
- tenantRequired: true,
58
- }),
59
- /* A draft that polls across tenants meets the same narrow role a
60
- deployment gives it: no default table grant, so it reads only the
61
- columns a migration named under that table's own policy. */
62
- background: new PostgresDatabaseAdapter({
63
- pool: cluster.pool(BACKGROUND_ROLE),
64
- }),
65
- };
66
- return adapters;
67
- };
68
-
69
- return {
70
- async acquire(
71
- request: DatabaseProviderRequest,
72
- ): Promise<DatabaseAdapterLease> {
73
- if (disposed) {
74
- throw new DatabaseError(
75
- 'ADAPTER_DISPOSED',
76
- 'The preview database provider is not accepting leases.',
77
- );
78
- }
79
- assertNamespace(request.namespace);
80
- const { migrator, runtime, background } = opened();
81
- /* Releasing a lease closes nothing: a draft that leaks one must not be
82
- able to keep the preview generation from retiring. */
83
- return {
84
- database:
85
- request.purpose === 'migration'
86
- ? migrator
87
- : request.purpose === 'background'
88
- ? background
89
- : runtime,
90
- release: () => Promise.resolve(),
91
- };
92
- },
93
- async dispose() {
94
- if (disposed) return;
95
- disposed = true;
96
- const open = adapters;
97
- adapters = null;
98
- if (open) {
99
- await Promise.allSettled([
100
- open.background.dispose(),
101
- open.runtime.dispose(),
102
- open.migrator.dispose(),
103
- ]);
104
- return;
105
- }
106
- await cluster.close();
107
- },
108
- };
109
- }
@@ -1,15 +0,0 @@
1
- import type { ChildProcess } from 'node:child_process';
2
- import type { PreviewDatabaseReply } from './preview-database-protocol.ts';
3
-
4
- /** connected can change between the check and the asynchronous pipe write. */
5
- export function sendPreviewDatabaseReply(
6
- child: Pick<ChildProcess, 'connected' | 'send'>,
7
- reply: PreviewDatabaseReply,
8
- ): void {
9
- if (!child.connected) return;
10
- try {
11
- child.send(reply, () => undefined);
12
- } catch {
13
- // The worker closed the channel before Node could queue the write.
14
- }
15
- }
@@ -1,83 +0,0 @@
1
- import { sandboxDirectory } from './config.ts';
2
- import { readFile, realpath } from 'node:fs/promises';
3
- import { dirname, join, resolve, sep } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import type { SandboxSession } from './sessions.ts';
6
-
7
- export interface PreviewModuleSource {
8
- readonly id: string;
9
- readonly directory: string;
10
- readonly path: string;
11
- readonly support: boolean;
12
- }
13
-
14
- export const PREVIEW_SUPPORT_ROOT = resolve(
15
- dirname(fileURLToPath(import.meta.url)),
16
- '../../../../modules',
17
- );
18
- const IDENTIFIER = /^[a-z][a-z0-9-]*\.core$/;
19
-
20
- async function manifest(
21
- path: string,
22
- optional: boolean,
23
- ): Promise<{ id?: string; dependencies?: readonly { id: string }[] }> {
24
- try {
25
- return JSON.parse(await readFile(join(path, 'module.json'), 'utf8'));
26
- } catch (error) {
27
- if (optional && (error as NodeJS.ErrnoException).code === 'ENOENT')
28
- return {};
29
- throw error;
30
- }
31
- }
32
-
33
- /** Dependencies are read-only platform sources, never copied into the draft or
34
- * included in delivery. Only explicitly declared dependencies are composed. */
35
- export async function resolvePreviewModules(
36
- workspaceRoot: string,
37
- session: SandboxSession,
38
- supportRoot = PREVIEW_SUPPORT_ROOT,
39
- ): Promise<readonly PreviewModuleSource[]> {
40
- const draftRoot = join(
41
- sandboxDirectory(workspaceRoot),
42
- 'sessions',
43
- session.id,
44
- 'workspace/modules',
45
- );
46
- const drafts = new Map(session.modules.map((module) => [module.id, module]));
47
- const visiting = new Set<string>();
48
- const visited = new Set<string>();
49
- const ordered: PreviewModuleSource[] = [];
50
- const visit = async (id: string): Promise<void> => {
51
- // The preview owns a separate auth runtime and its seeded identity.
52
- if (id === 'auth.core' || visited.has(id)) return;
53
- if (visiting.has(id)) throw new Error('PREVIEW_DEPENDENCY_CYCLE: ' + id);
54
- if (visited.size + visiting.size >= 64)
55
- throw new Error('PREVIEW_DEPENDENCY_LIMIT');
56
- visiting.add(id);
57
- const draft = drafts.get(id);
58
- if (!draft && !IDENTIFIER.test(id))
59
- throw new Error('PREVIEW_DEPENDENCY_UNAVAILABLE: ' + id);
60
- const directory = draft?.directory ?? id.slice(0, -5);
61
- if (!/^[a-z][a-z0-9-]*$/.test(directory))
62
- throw new Error('PREVIEW_MODULE_DIRECTORY_INVALID');
63
- const path = draft
64
- ? join(draftRoot, directory)
65
- : join(supportRoot, directory);
66
- if (!draft) {
67
- const physical = await realpath(path).catch(() => null);
68
- const root = await realpath(supportRoot);
69
- if (!physical?.startsWith(root + sep))
70
- throw new Error('PREVIEW_DEPENDENCY_UNAVAILABLE: ' + id);
71
- }
72
- const definition = await manifest(path, Boolean(draft));
73
- if (definition.id && definition.id !== id)
74
- throw new Error('PREVIEW_DEPENDENCY_ID_MISMATCH: ' + id);
75
- for (const dependency of definition.dependencies ?? [])
76
- await visit(dependency.id);
77
- visiting.delete(id);
78
- visited.add(id);
79
- ordered.push({ id, directory, path, support: !draft });
80
- };
81
- for (const module of session.modules) await visit(module.id);
82
- return ordered;
83
- }
@@ -1,44 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { readFile, readdir } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- import type { PreviewModuleSource } from './preview-modules.ts';
5
-
6
- /** Includes dependency sources and deletions, not only the newest entry mtime. */
7
- export async function previewRevision(
8
- sources: readonly PreviewModuleSource[],
9
- ): Promise<string> {
10
- const hash = createHash('sha256');
11
- const file = async (path: string, name: string) => {
12
- try {
13
- const content = await readFile(path);
14
- hash.update(JSON.stringify([name, content.length]));
15
- hash.update(content);
16
- } catch (error) {
17
- if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
18
- }
19
- };
20
- const directory = async (path: string, prefix: string): Promise<void> => {
21
- const entries = await readdir(path, { withFileTypes: true }).catch(
22
- (error: NodeJS.ErrnoException) => {
23
- if (error.code === 'ENOENT') return [];
24
- throw error;
25
- },
26
- );
27
- for (const entry of entries.sort((left, right) =>
28
- left.name.localeCompare(right.name),
29
- )) {
30
- if (entry.name === 'node_modules' || entry.name === 'dist') continue;
31
- const name = prefix + '/' + entry.name;
32
- if (entry.isDirectory()) await directory(join(path, entry.name), name);
33
- else if (entry.isFile()) await file(join(path, entry.name), name);
34
- }
35
- };
36
- for (const source of sources) {
37
- hash.update(JSON.stringify([source.id, source.path]));
38
- await file(join(source.path, 'module.json'), 'module.json');
39
- await file(join(source.path, 'package.json'), 'package.json');
40
- await directory(join(source.path, 'src'), 'src');
41
- await directory(join(source.path, 'migrations'), 'migrations');
42
- }
43
- return hash.digest('hex');
44
- }