@flowdular/sdk 0.1.0 → 0.2.0

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 (174) 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 +1 -1
  28. package/packages/database-testing/package.json +1 -1
  29. package/packages/dev-console/package.json +1 -1
  30. package/packages/harness/package.json +1 -1
  31. package/packages/kernel/package.json +1 -1
  32. package/packages/server/package.json +1 -1
  33. package/packages/server/src/application-routes.ts +80 -8
  34. package/packages/server/src/index.ts +4 -1
  35. package/packages/server/src/web-html.ts +9 -5
  36. package/packages/server/src/web.ts +59 -7
  37. package/packages/ui/package.json +1 -1
  38. package/packages/coding-agent/package.json +0 -44
  39. package/packages/coding-agent/src/drivers/byok.ts +0 -322
  40. package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
  41. package/packages/coding-agent/src/drivers/codex.ts +0 -281
  42. package/packages/coding-agent/src/index.ts +0 -56
  43. package/packages/coding-agent/src/registry.ts +0 -119
  44. package/packages/coding-agent/src/roles/contract.ts +0 -60
  45. package/packages/coding-agent/src/roles/defaults.ts +0 -134
  46. package/packages/coding-agent/src/roles/handoff.ts +0 -41
  47. package/packages/coding-agent/src/roles/registry.ts +0 -156
  48. package/packages/coding-agent/src/roles/skills.ts +0 -72
  49. package/packages/coding-agent/src/roles/sync.ts +0 -88
  50. package/packages/coding-agent/src/types.ts +0 -102
  51. package/packages/coding-agent/src/workspace.ts +0 -275
  52. package/packages/sandbox/README.md +0 -691
  53. package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
  54. package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
  55. package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
  56. package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
  57. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  58. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  59. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  60. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  61. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  62. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  63. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  64. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  65. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  66. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  67. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  68. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  69. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  70. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  71. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  72. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  73. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  74. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  75. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  76. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  77. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  78. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  79. package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  80. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  81. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  82. package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  83. package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
  84. package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
  85. package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
  86. package/packages/sandbox/dist/client/favicon.svg +0 -1
  87. package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
  88. package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
  89. package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
  90. package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
  91. package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
  92. package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
  93. package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
  94. package/packages/sandbox/dist/server/entry.js +0 -91671
  95. package/packages/sandbox/dist/server/favicon.svg +0 -1
  96. package/packages/sandbox/dist/server/index.html +0 -161
  97. package/packages/sandbox/index.html +0 -159
  98. package/packages/sandbox/octane.config.ts +0 -36
  99. package/packages/sandbox/package.json +0 -83
  100. package/packages/sandbox/public/favicon.svg +0 -1
  101. package/packages/sandbox/public/og.png +0 -0
  102. package/packages/sandbox/src/App.tsrx +0 -924
  103. package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
  104. package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
  105. package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
  106. package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
  107. package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
  108. package/packages/sandbox/src/client/GateResults.tsrx +0 -60
  109. package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
  110. package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
  111. package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
  112. package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
  113. package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
  114. package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
  115. package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
  116. package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
  117. package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
  118. package/packages/sandbox/src/client/api.ts +0 -797
  119. package/packages/sandbox/src/client/attachments.ts +0 -73
  120. package/packages/sandbox/src/client/dashboard.ts +0 -46
  121. package/packages/sandbox/src/client/github-settings.ts +0 -48
  122. package/packages/sandbox/src/client/highlight.ts +0 -105
  123. package/packages/sandbox/src/client/i18n.ts +0 -32
  124. package/packages/sandbox/src/client/locales/en.json +0 -466
  125. package/packages/sandbox/src/client/locales/pl.json +0 -466
  126. package/packages/sandbox/src/client/session-labels.ts +0 -77
  127. package/packages/sandbox/src/client/state.ts +0 -108
  128. package/packages/sandbox/src/index.ts +0 -10
  129. package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
  130. package/packages/sandbox/src/preview/load-module.ts +0 -16
  131. package/packages/sandbox/src/server/attachments.ts +0 -308
  132. package/packages/sandbox/src/server/auto-review.ts +0 -181
  133. package/packages/sandbox/src/server/checkpoints.ts +0 -90
  134. package/packages/sandbox/src/server/config.ts +0 -460
  135. package/packages/sandbox/src/server/dashboard.ts +0 -245
  136. package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
  137. package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
  138. package/packages/sandbox/src/server/delivery/index.ts +0 -83
  139. package/packages/sandbox/src/server/delivery/local.ts +0 -151
  140. package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
  141. package/packages/sandbox/src/server/delivery/plan.ts +0 -218
  142. package/packages/sandbox/src/server/delivery/policies.ts +0 -239
  143. package/packages/sandbox/src/server/delivery/record.ts +0 -41
  144. package/packages/sandbox/src/server/delivery/steps.ts +0 -464
  145. package/packages/sandbox/src/server/delivery/types.ts +0 -158
  146. package/packages/sandbox/src/server/dependencies.ts +0 -88
  147. package/packages/sandbox/src/server/diff.ts +0 -230
  148. package/packages/sandbox/src/server/gates.ts +0 -388
  149. package/packages/sandbox/src/server/index.ts +0 -139
  150. package/packages/sandbox/src/server/path-guard.ts +0 -282
  151. package/packages/sandbox/src/server/planning.ts +0 -749
  152. package/packages/sandbox/src/server/platform-client.ts +0 -189
  153. package/packages/sandbox/src/server/preview-database-host.ts +0 -340
  154. package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
  155. package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
  156. package/packages/sandbox/src/server/preview-database.ts +0 -109
  157. package/packages/sandbox/src/server/preview-ipc.ts +0 -15
  158. package/packages/sandbox/src/server/preview-modules.ts +0 -83
  159. package/packages/sandbox/src/server/preview-revision.ts +0 -44
  160. package/packages/sandbox/src/server/preview-runtime.ts +0 -467
  161. package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
  162. package/packages/sandbox/src/server/preview-worker.ts +0 -168
  163. package/packages/sandbox/src/server/reference.ts +0 -251
  164. package/packages/sandbox/src/server/routes.ts +0 -2258
  165. package/packages/sandbox/src/server/runtime.ts +0 -188
  166. package/packages/sandbox/src/server/session-owner.ts +0 -23
  167. package/packages/sandbox/src/server/sessions.ts +0 -849
  168. package/packages/sandbox/src/server/spec.ts +0 -471
  169. package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
  170. package/packages/sandbox/src/server/turns.ts +0 -993
  171. package/packages/sandbox/src/server/workspace-install.ts +0 -266
  172. package/packages/sandbox/src/server/workspace-root.ts +0 -73
  173. package/packages/sandbox/src/styles.css +0 -1573
  174. 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
- }