@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.
- package/.ai/skills/test-hardening/SKILL.md +1 -1
- package/README.md +69 -12
- package/assets/flowdular-banner.png +0 -0
- package/modules/agents/package.json +1 -1
- package/modules/auth/package.json +1 -1
- package/modules/auth/src/client/AuthenticationCore.tsrx +3 -1
- package/modules/auth/src/server/endpoints.ts +3 -1
- package/modules/auth/src/server/runtime.ts +4 -0
- package/modules/automations/package.json +1 -1
- package/modules/automations-workflows-integration/package.json +1 -1
- package/modules/profile/package.json +1 -1
- package/modules/sandbox/package.json +1 -1
- package/modules/system/package.json +1 -1
- package/modules/users/package.json +1 -1
- package/modules/workflows/package.json +1 -1
- package/package.json +5 -13
- package/packages/ai-provider/package.json +1 -1
- package/packages/cli-protocol/package.json +1 -1
- package/packages/client/package.json +1 -1
- package/packages/client/src/routing.ts +39 -2
- package/packages/client/src/shell/BrandMark.tsrx +5 -1
- package/packages/client/src/shell/navigation.ts +3 -1
- package/packages/client/src/state.ts +6 -2
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/schemas/project.schema.json +16 -42
- package/packages/database/package.json +1 -1
- package/packages/database-pglite/package.json +2 -2
- package/packages/database-pglite/src/driver.ts +21 -3
- package/packages/database-testing/package.json +1 -1
- package/packages/dev-console/package.json +1 -1
- package/packages/harness/package.json +1 -1
- package/packages/kernel/package.json +1 -1
- package/packages/server/package.json +1 -1
- package/packages/server/src/application-routes.ts +80 -8
- package/packages/server/src/index.ts +4 -1
- package/packages/server/src/web-html.ts +9 -5
- package/packages/server/src/web.ts +59 -7
- package/packages/ui/package.json +1 -1
- package/packages/coding-agent/package.json +0 -44
- package/packages/coding-agent/src/drivers/byok.ts +0 -322
- package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
- package/packages/coding-agent/src/drivers/codex.ts +0 -281
- package/packages/coding-agent/src/index.ts +0 -56
- package/packages/coding-agent/src/registry.ts +0 -119
- package/packages/coding-agent/src/roles/contract.ts +0 -60
- package/packages/coding-agent/src/roles/defaults.ts +0 -134
- package/packages/coding-agent/src/roles/handoff.ts +0 -41
- package/packages/coding-agent/src/roles/registry.ts +0 -156
- package/packages/coding-agent/src/roles/skills.ts +0 -72
- package/packages/coding-agent/src/roles/sync.ts +0 -88
- package/packages/coding-agent/src/types.ts +0 -102
- package/packages/coding-agent/src/workspace.ts +0 -275
- package/packages/sandbox/README.md +0 -691
- package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
- package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
- package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
- package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
- package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
- package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
- package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
- package/packages/sandbox/dist/client/favicon.svg +0 -1
- package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
- package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
- package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
- package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
- package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
- package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
- package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
- package/packages/sandbox/dist/server/entry.js +0 -91671
- package/packages/sandbox/dist/server/favicon.svg +0 -1
- package/packages/sandbox/dist/server/index.html +0 -161
- package/packages/sandbox/index.html +0 -159
- package/packages/sandbox/octane.config.ts +0 -36
- package/packages/sandbox/package.json +0 -83
- package/packages/sandbox/public/favicon.svg +0 -1
- package/packages/sandbox/public/og.png +0 -0
- package/packages/sandbox/src/App.tsrx +0 -924
- package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
- package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
- package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
- package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
- package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
- package/packages/sandbox/src/client/GateResults.tsrx +0 -60
- package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
- package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
- package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
- package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
- package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
- package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
- package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
- package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
- package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
- package/packages/sandbox/src/client/api.ts +0 -797
- package/packages/sandbox/src/client/attachments.ts +0 -73
- package/packages/sandbox/src/client/dashboard.ts +0 -46
- package/packages/sandbox/src/client/github-settings.ts +0 -48
- package/packages/sandbox/src/client/highlight.ts +0 -105
- package/packages/sandbox/src/client/i18n.ts +0 -32
- package/packages/sandbox/src/client/locales/en.json +0 -466
- package/packages/sandbox/src/client/locales/pl.json +0 -466
- package/packages/sandbox/src/client/session-labels.ts +0 -77
- package/packages/sandbox/src/client/state.ts +0 -108
- package/packages/sandbox/src/index.ts +0 -10
- package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
- package/packages/sandbox/src/preview/load-module.ts +0 -16
- package/packages/sandbox/src/server/attachments.ts +0 -308
- package/packages/sandbox/src/server/auto-review.ts +0 -181
- package/packages/sandbox/src/server/checkpoints.ts +0 -90
- package/packages/sandbox/src/server/config.ts +0 -460
- package/packages/sandbox/src/server/dashboard.ts +0 -245
- package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
- package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
- package/packages/sandbox/src/server/delivery/index.ts +0 -83
- package/packages/sandbox/src/server/delivery/local.ts +0 -151
- package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
- package/packages/sandbox/src/server/delivery/plan.ts +0 -218
- package/packages/sandbox/src/server/delivery/policies.ts +0 -239
- package/packages/sandbox/src/server/delivery/record.ts +0 -41
- package/packages/sandbox/src/server/delivery/steps.ts +0 -464
- package/packages/sandbox/src/server/delivery/types.ts +0 -158
- package/packages/sandbox/src/server/dependencies.ts +0 -88
- package/packages/sandbox/src/server/diff.ts +0 -230
- package/packages/sandbox/src/server/gates.ts +0 -388
- package/packages/sandbox/src/server/index.ts +0 -139
- package/packages/sandbox/src/server/path-guard.ts +0 -282
- package/packages/sandbox/src/server/planning.ts +0 -749
- package/packages/sandbox/src/server/platform-client.ts +0 -189
- package/packages/sandbox/src/server/preview-database-host.ts +0 -340
- package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
- package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
- package/packages/sandbox/src/server/preview-database.ts +0 -109
- package/packages/sandbox/src/server/preview-ipc.ts +0 -15
- package/packages/sandbox/src/server/preview-modules.ts +0 -83
- package/packages/sandbox/src/server/preview-revision.ts +0 -44
- package/packages/sandbox/src/server/preview-runtime.ts +0 -467
- package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
- package/packages/sandbox/src/server/preview-worker.ts +0 -168
- package/packages/sandbox/src/server/reference.ts +0 -251
- package/packages/sandbox/src/server/routes.ts +0 -2258
- package/packages/sandbox/src/server/runtime.ts +0 -188
- package/packages/sandbox/src/server/session-owner.ts +0 -23
- package/packages/sandbox/src/server/sessions.ts +0 -849
- package/packages/sandbox/src/server/spec.ts +0 -471
- package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
- package/packages/sandbox/src/server/turns.ts +0 -993
- package/packages/sandbox/src/server/workspace-install.ts +0 -266
- package/packages/sandbox/src/server/workspace-root.ts +0 -73
- package/packages/sandbox/src/styles.css +0 -1573
- 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
|
-
}
|