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