@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,189 +0,0 @@
1
- import { SandboxSetupError } from './workspace-root.ts';
2
-
3
- export interface PlatformPrincipal {
4
- readonly accountId: string;
5
- readonly tenantId: string;
6
- readonly email: string;
7
- readonly displayName: string;
8
- readonly role: string;
9
- readonly scopes: readonly string[];
10
- readonly tenantName: string;
11
- readonly tenantSlug: string;
12
- }
13
-
14
- export interface PlatformAuthority {
15
- readonly principal: PlatformPrincipal;
16
- readonly authority:
17
- | {
18
- readonly granted: true;
19
- readonly grantId: string;
20
- readonly capabilities: readonly string[];
21
- readonly expiresAt: number | null;
22
- }
23
- | { readonly granted: false; readonly reason: string };
24
- }
25
-
26
- export interface BridgeRequest {
27
- readonly method: string;
28
- readonly path: string;
29
- readonly search: string;
30
- readonly accept: string | null;
31
- }
32
-
33
- export interface BridgeResponse {
34
- readonly status: number;
35
- readonly contentType: string;
36
- readonly body: string;
37
- }
38
-
39
- const BRIDGE_TIMEOUT_MS = 15_000;
40
- const BRIDGE_BODY_LIMIT = 2 * 1024 * 1024;
41
-
42
- export interface PlatformClientOptions {
43
- readonly platformUrl: string;
44
- readonly token: string;
45
- readonly fetch?: typeof globalThis.fetch;
46
- }
47
-
48
- export class PlatformClient {
49
- readonly #platformUrl: string;
50
- readonly #token: string;
51
- readonly #fetch: typeof globalThis.fetch;
52
-
53
- constructor(options: PlatformClientOptions) {
54
- this.#platformUrl = options.platformUrl.replace(/\/+$/, '');
55
- this.#token = options.token;
56
- this.#fetch = options.fetch ?? globalThis.fetch;
57
- }
58
-
59
- async authority(): Promise<PlatformAuthority> {
60
- const response = await this.#request('GET', '/api/sandbox/authority');
61
- if (response.status === 401) {
62
- throw new SandboxSetupError(
63
- 'PLATFORM_TOKEN_REJECTED',
64
- 'The platform rejected this API token. Issue a new one in Administration.',
65
- );
66
- }
67
- if (response.status === 403) {
68
- throw new SandboxSetupError(
69
- 'SANDBOX_SCOPE_MISSING',
70
- 'The token does not carry sandbox.access.use for this workspace.',
71
- );
72
- }
73
- if (response.status === 404) {
74
- throw new SandboxSetupError(
75
- 'SANDBOX_MODULE_MISSING',
76
- 'The connected application does not expose sandbox.core. Enable the module there first.',
77
- );
78
- }
79
- if (!response.ok) {
80
- throw new SandboxSetupError(
81
- 'PLATFORM_UNREACHABLE',
82
- `The platform answered ${response.status} for the sandbox authority check.`,
83
- );
84
- }
85
- return (await response.json()) as PlatformAuthority;
86
- }
87
-
88
- async registerSession(input: {
89
- readonly sessionId: string;
90
- readonly moduleId: string;
91
- readonly title: string;
92
- readonly blueprint: string;
93
- readonly driver: string;
94
- readonly mode: string;
95
- }): Promise<void> {
96
- await this.#json('POST', '/api/sandbox/sessions', input);
97
- }
98
-
99
- async updateSessionState(sessionId: string, state: string): Promise<void> {
100
- await this.#json('POST', '/api/sandbox/sessions/state', {
101
- sessionId,
102
- state,
103
- });
104
- }
105
-
106
- /* A delivered module is audit evidence on the platform, next to the
107
- session record it came from. */
108
- async recordEject(
109
- sessionId: string,
110
- metadata: Readonly<Record<string, string | number | boolean>>,
111
- ): Promise<void> {
112
- await this.#json('POST', '/api/sandbox/sessions/eject', {
113
- sessionId,
114
- metadata,
115
- });
116
- }
117
-
118
- /* The read-only data bridge. A preview screen asks the sandbox for a path
119
- the draft module does not own, and the sandbox forwards it to the real
120
- application under the connected account's own authorization. */
121
- async bridge(request: BridgeRequest): Promise<BridgeResponse> {
122
- if (request.method !== 'GET' && request.method !== 'HEAD') {
123
- return {
124
- status: 405,
125
- contentType: 'application/json',
126
- body: JSON.stringify({
127
- error: {
128
- code: 'BRIDGE_READ_ONLY',
129
- message:
130
- 'The preview data bridge forwards read requests only. Mutations must run against the real application.',
131
- },
132
- }),
133
- };
134
- }
135
- const response = await this.#request(
136
- request.method,
137
- `${request.path}${request.search}`,
138
- undefined,
139
- request.accept,
140
- );
141
- const text = await response.text();
142
- return {
143
- status: response.status,
144
- contentType: response.headers.get('content-type') ?? 'application/json',
145
- body:
146
- text.length > BRIDGE_BODY_LIMIT
147
- ? JSON.stringify({
148
- error: {
149
- code: 'BRIDGE_RESPONSE_TOO_LARGE',
150
- message: 'The bridged response exceeded the sandbox limit.',
151
- },
152
- })
153
- : text,
154
- };
155
- }
156
-
157
- async #json(method: string, path: string, body: unknown): Promise<Response> {
158
- const response = await this.#request(method, path, body);
159
- if (!response.ok) {
160
- const payload = (await response.json().catch(() => ({}))) as {
161
- error?: { message?: string };
162
- };
163
- throw new SandboxSetupError(
164
- 'PLATFORM_REQUEST_FAILED',
165
- payload.error?.message ??
166
- `The platform answered ${response.status} for ${path}.`,
167
- );
168
- }
169
- return response;
170
- }
171
-
172
- #request(
173
- method: string,
174
- path: string,
175
- body?: unknown,
176
- accept: string | null = 'application/json',
177
- ): Promise<Response> {
178
- return this.#fetch(`${this.#platformUrl}${path}`, {
179
- method,
180
- headers: {
181
- authorization: `Bearer ${this.#token}`,
182
- ...(accept ? { accept } : {}),
183
- ...(body === undefined ? {} : { 'content-type': 'application/json' }),
184
- },
185
- ...(body === undefined ? {} : { body: JSON.stringify(body) }),
186
- signal: AbortSignal.timeout(BRIDGE_TIMEOUT_MS),
187
- });
188
- }
189
- }
@@ -1,340 +0,0 @@
1
- import {
2
- DatabaseError,
3
- type DatabaseAdapterLease,
4
- type DatabaseCapabilities,
5
- type DatabaseOperationOptions,
6
- type DatabaseProvider,
7
- type DatabaseSession,
8
- type DatabaseTransaction,
9
- type DatabaseTransactionOptions,
10
- } from '@flowdular/sdk/database';
11
- import {
12
- isPreviewDatabaseRequest,
13
- previewDatabaseError,
14
- previewDatabaseFailure,
15
- PREVIEW_DATABASE_REPLY,
16
- type PreviewDatabaseCall,
17
- type PreviewDatabaseGrant,
18
- type PreviewDatabaseLeaseRequest,
19
- type PreviewDatabaseReply,
20
- type PreviewDatabaseTarget,
21
- type PreviewDatabaseTransactionOptions,
22
- } from './preview-database-protocol.ts';
23
-
24
- export interface PreviewDatabaseHostOptions {
25
- /** Opens the session engine on the first lease. Never called after close. */
26
- open(): Promise<DatabaseProvider>;
27
- send(reply: PreviewDatabaseReply): void;
28
- }
29
-
30
- export interface PreviewDatabaseHost {
31
- /** True when the message belonged to the preview database protocol. */
32
- accept(message: unknown): boolean;
33
- /** Idempotent. Rolls every open transaction back, then closes the engine. */
34
- close(): Promise<void>;
35
- }
36
-
37
- interface OpenTransaction {
38
- /** The pinned session once BEGIN landed. Rejects when it never did. */
39
- readonly session: Promise<DatabaseTransaction>;
40
- /** Serialises every operation and the commit. Never rejects. */
41
- gate: Promise<void>;
42
- settle(error: Error | null): void;
43
- readonly finished: Promise<void>;
44
- }
45
-
46
- function withoutDialectSql(
47
- capabilities: DatabaseCapabilities,
48
- ): PreviewDatabaseGrant['capabilities'] {
49
- const { sql: _dialectSql, ...serializable } = capabilities;
50
- return serializable;
51
- }
52
-
53
- function operationOptions(call: {
54
- readonly timeoutMs?: number;
55
- }): DatabaseOperationOptions {
56
- return call.timeoutMs === undefined ? {} : { timeoutMs: call.timeoutMs };
57
- }
58
-
59
- function transactionOptions(
60
- options: PreviewDatabaseTransactionOptions,
61
- ): DatabaseTransactionOptions {
62
- return {
63
- ...(options.access === undefined ? {} : { access: options.access }),
64
- ...(options.isolation === undefined
65
- ? {}
66
- : { isolation: options.isolation }),
67
- ...(options.tenantId === undefined ? {} : { tenantId: options.tenantId }),
68
- ...operationOptions(options),
69
- };
70
- }
71
-
72
- /**
73
- * Serves the preview worker's database calls against the real session engine.
74
- * The worker holds the contract; this side holds the connections, so every
75
- * transaction it opens is this side's to commit, roll back, or reclaim when the
76
- * worker dies.
77
- */
78
- export function createPreviewDatabaseHost(
79
- options: PreviewDatabaseHostOptions,
80
- ): PreviewDatabaseHost {
81
- const leases = new Map<number, DatabaseAdapterLease>();
82
- const transactions = new Map<number, OpenTransaction>();
83
- let provider: Promise<DatabaseProvider> | undefined;
84
- let closing: Promise<void> | undefined;
85
-
86
- const send = (reply: PreviewDatabaseReply) => {
87
- try {
88
- options.send(reply);
89
- } catch {
90
- /* The worker went away between its request and this reply. */
91
- }
92
- };
93
-
94
- async function grant(
95
- lease: number,
96
- request: PreviewDatabaseLeaseRequest,
97
- ): Promise<PreviewDatabaseGrant> {
98
- provider ??= options.open();
99
- const acquired = await (await provider).acquire(request);
100
- if (closing) {
101
- await acquired.release();
102
- throw new DatabaseError(
103
- 'ADAPTER_DISPOSED',
104
- 'The preview database host is closed.',
105
- );
106
- }
107
- leases.set(lease, acquired);
108
- return {
109
- adapterId: acquired.database.adapterId,
110
- dialectId: acquired.database.dialectId,
111
- capabilities: withoutDialectSql(acquired.database.capabilities),
112
- };
113
- }
114
-
115
- /* One parent connection is pinned to a transaction, and the worker may
116
- abandon a statement whose deadline fired, so the next statement and the
117
- commit both wait for the one still in flight. */
118
- function within<T>(
119
- id: number,
120
- run: (transaction: DatabaseTransaction) => Promise<T>,
121
- ): Promise<T> {
122
- const entry = transactions.get(id);
123
- if (!entry) {
124
- throw new DatabaseError(
125
- 'TRANSACTION_CONTEXT_MISUSE',
126
- 'The preview database transaction has already ended.',
127
- );
128
- }
129
- const result = entry.gate.then(() => entry.session).then(run);
130
- entry.gate = result.then(
131
- () => undefined,
132
- () => undefined,
133
- );
134
- return result;
135
- }
136
-
137
- function onTarget<T>(
138
- target: PreviewDatabaseTarget,
139
- run: (session: DatabaseSession) => Promise<T>,
140
- ): Promise<T> {
141
- if (target.transaction !== undefined)
142
- return within(target.transaction, run);
143
- const lease = leases.get(target.lease);
144
- if (!lease) {
145
- throw new DatabaseError(
146
- 'ADAPTER_DISPOSED',
147
- 'The preview database lease was released.',
148
- );
149
- }
150
- return run(lease.database);
151
- }
152
-
153
- async function begin(
154
- lease: number,
155
- id: number,
156
- wanted: PreviewDatabaseTransactionOptions,
157
- ): Promise<undefined> {
158
- const held = leases.get(lease);
159
- if (!held) {
160
- throw new DatabaseError(
161
- 'ADAPTER_DISPOSED',
162
- 'The preview database lease was released.',
163
- );
164
- }
165
- let ready!: (transaction: DatabaseTransaction) => void;
166
- const pinned = new Promise<DatabaseTransaction>((resolve) => {
167
- ready = resolve;
168
- });
169
- let settle!: (error: Error | null) => void;
170
- const body = new Promise<void>((resolve, reject) => {
171
- settle = (error) => (error ? reject(error) : resolve());
172
- });
173
- /* A begin that fails leaves nobody inside the callback to observe this. */
174
- void body.catch(() => undefined);
175
- const finished = held.database.transaction(async (transaction) => {
176
- ready(transaction);
177
- await body;
178
- }, transactionOptions(wanted));
179
- /* BEGIN, the tenant context statement, or the options themselves can fail
180
- before the callback runs. The gate must then reject rather than leave a
181
- settle request waiting forever. */
182
- const session = Promise.race([
183
- pinned,
184
- finished.then(
185
- () => {
186
- throw new DatabaseError(
187
- 'TRANSACTION_CONTEXT_MISUSE',
188
- 'The preview database transaction ended before it began.',
189
- );
190
- },
191
- (error: unknown) => {
192
- throw error;
193
- },
194
- ),
195
- ]);
196
- const entry: OpenTransaction = {
197
- session,
198
- gate: session.then(
199
- () => undefined,
200
- () => undefined,
201
- ),
202
- settle,
203
- finished,
204
- };
205
- transactions.set(id, entry);
206
- try {
207
- await session;
208
- } catch (error) {
209
- transactions.delete(id);
210
- throw error;
211
- }
212
- return undefined;
213
- }
214
-
215
- async function end(id: number, failure: Error | null): Promise<undefined> {
216
- const entry = transactions.get(id);
217
- /* An unknown token is the worker cleaning up a begin it never received. */
218
- if (!entry) return undefined;
219
- transactions.delete(id);
220
- const done = entry.gate.then(() => {
221
- entry.settle(failure);
222
- return entry.finished;
223
- });
224
- entry.gate = done.then(
225
- () => undefined,
226
- () => undefined,
227
- );
228
- try {
229
- await done;
230
- } catch (error) {
231
- /* A rollback the worker asked for rethrows the failure it already owns. */
232
- if (!failure) throw error;
233
- }
234
- return undefined;
235
- }
236
-
237
- async function perform(call: PreviewDatabaseCall): Promise<unknown> {
238
- if (closing) {
239
- throw new DatabaseError(
240
- 'ADAPTER_DISPOSED',
241
- 'The preview database host is closed.',
242
- );
243
- }
244
- switch (call.kind) {
245
- case 'acquire':
246
- return await grant(call.lease, call.request);
247
- case 'release': {
248
- const lease = leases.get(call.lease);
249
- leases.delete(call.lease);
250
- await lease?.release();
251
- return undefined;
252
- }
253
- case 'begin':
254
- return await begin(call.lease, call.transaction, call.options);
255
- case 'settle':
256
- return await end(
257
- call.transaction,
258
- call.error ? previewDatabaseError(call.error) : null,
259
- );
260
- case 'migration-lock':
261
- return await within(call.transaction, (transaction) =>
262
- transaction.acquireMigrationLock(call.namespace),
263
- );
264
- case 'query':
265
- return await onTarget(call.target, (session) =>
266
- session.query(call.statement, operationOptions(call)),
267
- );
268
- case 'execute':
269
- return await onTarget(call.target, (session) =>
270
- session.execute(call.statement, operationOptions(call)),
271
- );
272
- case 'script':
273
- return await onTarget(call.target, (session) =>
274
- session.executeScript(call.script, operationOptions(call)),
275
- );
276
- case 'has-table':
277
- return await onTarget(call.target, (session) =>
278
- session.schema.hasTable(call.name, operationOptions(call)),
279
- );
280
- case 'has-index':
281
- return await onTarget(call.target, (session) =>
282
- session.schema.hasIndex(call.name, operationOptions(call)),
283
- );
284
- case 'has-column':
285
- return await onTarget(call.target, (session) =>
286
- session.schema.hasColumn(
287
- call.table,
288
- call.column,
289
- operationOptions(call),
290
- ),
291
- );
292
- }
293
- }
294
-
295
- return {
296
- accept(message: unknown): boolean {
297
- if (!isPreviewDatabaseRequest(message)) return false;
298
- const id = message.id;
299
- void perform(message.call).then(
300
- (value) => send({ type: PREVIEW_DATABASE_REPLY, id, ok: true, value }),
301
- (error: unknown) =>
302
- send({
303
- type: PREVIEW_DATABASE_REPLY,
304
- id,
305
- ok: false,
306
- error: previewDatabaseFailure(error),
307
- }),
308
- );
309
- return true;
310
- },
311
- close(): Promise<void> {
312
- closing ??= (async () => {
313
- const open = [...transactions.values()];
314
- transactions.clear();
315
- const reason = new DatabaseError(
316
- 'ADAPTER_DISPOSED',
317
- 'The preview worker stopped while the transaction was open.',
318
- );
319
- await Promise.allSettled(
320
- open.map((entry) =>
321
- entry.gate.then(() => {
322
- entry.settle(reason);
323
- return entry.finished;
324
- }),
325
- ),
326
- );
327
- await Promise.allSettled(
328
- [...leases.values()].map((lease) => lease.release()),
329
- );
330
- leases.clear();
331
- const opened = provider;
332
- provider = undefined;
333
- if (!opened) return;
334
- const engine = await opened.catch(() => undefined);
335
- await engine?.dispose();
336
- })();
337
- return closing;
338
- },
339
- };
340
- }
@@ -1,163 +0,0 @@
1
- import {
2
- DatabaseError,
3
- type DatabaseCapabilities,
4
- type DatabaseErrorCode,
5
- type DatabaseIsolationLevel,
6
- type DatabaseProviderRequest,
7
- type DatabaseRequirements,
8
- type DatabaseStatement,
9
- } from '@flowdular/sdk/database';
10
-
11
- /* The preview worker runs under Node's permission model, which denies
12
- process.binding unconditionally, and the embedded PostgreSQL build needs it.
13
- So the engine stays in the sandbox process and the worker drives it over the
14
- IPC channel it already owns. Only structured-clone values cross that channel:
15
- no AbortSignal, no capability functions, no Error instances. */
16
-
17
- export const PREVIEW_DATABASE_REQUEST = 'flowdular.preview-database.request';
18
- export const PREVIEW_DATABASE_REPLY = 'flowdular.preview-database.reply';
19
-
20
- /** Static lease metadata, sent once when the lease is granted. */
21
- export interface PreviewDatabaseGrant {
22
- readonly adapterId: string;
23
- readonly dialectId: string;
24
- /** The dialect-owned `sql` functions are rebuilt from `dialectId`. */
25
- readonly capabilities: Omit<DatabaseCapabilities, 'sql'>;
26
- }
27
-
28
- export interface PreviewDatabaseLeaseRequest {
29
- readonly namespace: string;
30
- readonly purpose: DatabaseProviderRequest['purpose'];
31
- readonly requirements?: DatabaseRequirements;
32
- readonly timeoutMs?: number;
33
- }
34
-
35
- export interface PreviewDatabaseTransactionOptions {
36
- readonly access?: 'read' | 'write';
37
- readonly isolation?: DatabaseIsolationLevel;
38
- readonly tenantId?: string;
39
- readonly timeoutMs?: number;
40
- }
41
-
42
- /** Names the parent-side connection an operation runs on. */
43
- export interface PreviewDatabaseTarget {
44
- readonly lease: number;
45
- /** Set for a statement issued inside a transaction callback. */
46
- readonly transaction?: number;
47
- }
48
-
49
- interface PreviewDatabaseOperation {
50
- readonly target: PreviewDatabaseTarget;
51
- readonly timeoutMs?: number;
52
- }
53
-
54
- export type PreviewDatabaseCall =
55
- | {
56
- readonly kind: 'acquire';
57
- readonly lease: number;
58
- readonly request: PreviewDatabaseLeaseRequest;
59
- }
60
- | { readonly kind: 'release'; readonly lease: number }
61
- | {
62
- readonly kind: 'begin';
63
- readonly lease: number;
64
- readonly transaction: number;
65
- readonly options: PreviewDatabaseTransactionOptions;
66
- }
67
- | {
68
- readonly kind: 'settle';
69
- readonly transaction: number;
70
- /** Null commits. Anything else rolls back with that failure. */
71
- readonly error: PreviewDatabaseFailure | null;
72
- }
73
- | {
74
- readonly kind: 'migration-lock';
75
- readonly transaction: number;
76
- readonly namespace: string;
77
- }
78
- | (PreviewDatabaseOperation & {
79
- readonly kind: 'query' | 'execute';
80
- readonly statement: DatabaseStatement;
81
- })
82
- | (PreviewDatabaseOperation & {
83
- readonly kind: 'script';
84
- readonly script: string;
85
- })
86
- | (PreviewDatabaseOperation & {
87
- readonly kind: 'has-table' | 'has-index';
88
- readonly name: string;
89
- })
90
- | (PreviewDatabaseOperation & {
91
- readonly kind: 'has-column';
92
- readonly table: string;
93
- readonly column: string;
94
- });
95
-
96
- export interface PreviewDatabaseRequest {
97
- readonly type: typeof PREVIEW_DATABASE_REQUEST;
98
- readonly id: number;
99
- readonly call: PreviewDatabaseCall;
100
- }
101
-
102
- export interface PreviewDatabaseFailure {
103
- readonly name: string;
104
- readonly message: string;
105
- readonly code?: string;
106
- readonly stack?: string;
107
- }
108
-
109
- export type PreviewDatabaseReply = {
110
- readonly type: typeof PREVIEW_DATABASE_REPLY;
111
- readonly id: number;
112
- } & (
113
- | { readonly ok: true; readonly value: unknown }
114
- | { readonly ok: false; readonly error: PreviewDatabaseFailure }
115
- );
116
-
117
- export function isPreviewDatabaseRequest(
118
- message: unknown,
119
- ): message is PreviewDatabaseRequest {
120
- return (
121
- typeof message === 'object' &&
122
- message !== null &&
123
- (message as PreviewDatabaseRequest).type === PREVIEW_DATABASE_REQUEST
124
- );
125
- }
126
-
127
- export function isPreviewDatabaseReply(
128
- message: unknown,
129
- ): message is PreviewDatabaseReply {
130
- return (
131
- typeof message === 'object' &&
132
- message !== null &&
133
- (message as PreviewDatabaseReply).type === PREVIEW_DATABASE_REPLY
134
- );
135
- }
136
-
137
- /* Module code and the platform suites branch on DatabaseError.code and on the
138
- driver's own SQLSTATE, and neither survives structured cloning of an Error.
139
- Both travel as data and are put back together on the far side. */
140
- export function previewDatabaseFailure(error: unknown): PreviewDatabaseFailure {
141
- if (!(error instanceof Error)) {
142
- return { name: 'Error', message: String(error) };
143
- }
144
- const code = (error as { readonly code?: unknown }).code;
145
- return {
146
- name: error.name,
147
- message: error.message,
148
- ...(typeof code === 'string' ? { code } : {}),
149
- ...(typeof error.stack === 'string' ? { stack: error.stack } : {}),
150
- };
151
- }
152
-
153
- export function previewDatabaseError(failure: PreviewDatabaseFailure): Error {
154
- const error =
155
- failure.name === 'DatabaseError' && failure.code !== undefined
156
- ? new DatabaseError(failure.code as DatabaseErrorCode, failure.message)
157
- : Object.assign(new Error(failure.message), {
158
- name: failure.name,
159
- ...(failure.code === undefined ? {} : { code: failure.code }),
160
- });
161
- if (failure.stack !== undefined) error.stack = failure.stack;
162
- return error;
163
- }