lopata 0.0.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/README.md +15 -0
- package/package.json +51 -0
- package/runtime/bindings/ai.ts +132 -0
- package/runtime/bindings/analytics-engine.ts +96 -0
- package/runtime/bindings/browser.ts +64 -0
- package/runtime/bindings/cache.ts +179 -0
- package/runtime/bindings/cf-streams.ts +56 -0
- package/runtime/bindings/container-docker.ts +225 -0
- package/runtime/bindings/container.ts +662 -0
- package/runtime/bindings/crypto-extras.ts +89 -0
- package/runtime/bindings/d1.ts +315 -0
- package/runtime/bindings/do-executor-inprocess.ts +140 -0
- package/runtime/bindings/do-executor-worker.ts +368 -0
- package/runtime/bindings/do-executor.ts +45 -0
- package/runtime/bindings/do-websocket-bridge.ts +70 -0
- package/runtime/bindings/do-worker-entry.ts +220 -0
- package/runtime/bindings/do-worker-env.ts +74 -0
- package/runtime/bindings/durable-object.ts +992 -0
- package/runtime/bindings/email.ts +180 -0
- package/runtime/bindings/html-rewriter.ts +84 -0
- package/runtime/bindings/hyperdrive.ts +130 -0
- package/runtime/bindings/images.ts +381 -0
- package/runtime/bindings/kv.ts +359 -0
- package/runtime/bindings/queue.ts +507 -0
- package/runtime/bindings/r2.ts +759 -0
- package/runtime/bindings/rpc-stub.ts +267 -0
- package/runtime/bindings/scheduled.ts +172 -0
- package/runtime/bindings/service-binding.ts +217 -0
- package/runtime/bindings/static-assets.ts +481 -0
- package/runtime/bindings/websocket-pair.ts +182 -0
- package/runtime/bindings/workflow.ts +858 -0
- package/runtime/bunflare-config.ts +56 -0
- package/runtime/cli/cache.ts +39 -0
- package/runtime/cli/context.ts +105 -0
- package/runtime/cli/d1.ts +163 -0
- package/runtime/cli/dev.ts +392 -0
- package/runtime/cli/kv.ts +84 -0
- package/runtime/cli/queues.ts +109 -0
- package/runtime/cli/r2.ts +140 -0
- package/runtime/cli/traces.ts +251 -0
- package/runtime/cli.ts +102 -0
- package/runtime/config.ts +148 -0
- package/runtime/d1-migrate.ts +37 -0
- package/runtime/dashboard/api.ts +174 -0
- package/runtime/dashboard/app.tsx +220 -0
- package/runtime/dashboard/components/breadcrumb.tsx +16 -0
- package/runtime/dashboard/components/buttons.tsx +13 -0
- package/runtime/dashboard/components/code-block.tsx +5 -0
- package/runtime/dashboard/components/detail-field.tsx +8 -0
- package/runtime/dashboard/components/empty-state.tsx +8 -0
- package/runtime/dashboard/components/filter-input.tsx +11 -0
- package/runtime/dashboard/components/index.ts +16 -0
- package/runtime/dashboard/components/key-value-table.tsx +23 -0
- package/runtime/dashboard/components/modal.tsx +23 -0
- package/runtime/dashboard/components/page-header.tsx +11 -0
- package/runtime/dashboard/components/pill-button.tsx +14 -0
- package/runtime/dashboard/components/refresh-button.tsx +7 -0
- package/runtime/dashboard/components/service-info.tsx +45 -0
- package/runtime/dashboard/components/status-badge.tsx +7 -0
- package/runtime/dashboard/components/table-link.tsx +5 -0
- package/runtime/dashboard/components/table.tsx +26 -0
- package/runtime/dashboard/components.tsx +19 -0
- package/runtime/dashboard/index.html +23 -0
- package/runtime/dashboard/lib.ts +45 -0
- package/runtime/dashboard/rpc/client.ts +20 -0
- package/runtime/dashboard/rpc/handlers/ai.ts +71 -0
- package/runtime/dashboard/rpc/handlers/analytics-engine.ts +53 -0
- package/runtime/dashboard/rpc/handlers/cache.ts +24 -0
- package/runtime/dashboard/rpc/handlers/config.ts +137 -0
- package/runtime/dashboard/rpc/handlers/containers.ts +194 -0
- package/runtime/dashboard/rpc/handlers/d1.ts +84 -0
- package/runtime/dashboard/rpc/handlers/do.ts +117 -0
- package/runtime/dashboard/rpc/handlers/email.ts +82 -0
- package/runtime/dashboard/rpc/handlers/errors.ts +32 -0
- package/runtime/dashboard/rpc/handlers/generations.ts +60 -0
- package/runtime/dashboard/rpc/handlers/kv.ts +76 -0
- package/runtime/dashboard/rpc/handlers/overview.ts +94 -0
- package/runtime/dashboard/rpc/handlers/queue.ts +79 -0
- package/runtime/dashboard/rpc/handlers/r2.ts +72 -0
- package/runtime/dashboard/rpc/handlers/scheduled.ts +91 -0
- package/runtime/dashboard/rpc/handlers/traces.ts +64 -0
- package/runtime/dashboard/rpc/handlers/workers.ts +65 -0
- package/runtime/dashboard/rpc/handlers/workflows.ts +171 -0
- package/runtime/dashboard/rpc/hooks.ts +132 -0
- package/runtime/dashboard/rpc/server.ts +70 -0
- package/runtime/dashboard/rpc/types.ts +396 -0
- package/runtime/dashboard/sql-browser/data-browser-tab.tsx +122 -0
- package/runtime/dashboard/sql-browser/editable-cell.tsx +117 -0
- package/runtime/dashboard/sql-browser/filter-row.tsx +99 -0
- package/runtime/dashboard/sql-browser/history-panels.tsx +110 -0
- package/runtime/dashboard/sql-browser/hooks.ts +137 -0
- package/runtime/dashboard/sql-browser/index.ts +4 -0
- package/runtime/dashboard/sql-browser/insert-row-form.tsx +85 -0
- package/runtime/dashboard/sql-browser/modals.tsx +116 -0
- package/runtime/dashboard/sql-browser/schema-browser-tab.tsx +67 -0
- package/runtime/dashboard/sql-browser/sql-browser.tsx +52 -0
- package/runtime/dashboard/sql-browser/sql-console-tab.tsx +124 -0
- package/runtime/dashboard/sql-browser/table-data-view.tsx +566 -0
- package/runtime/dashboard/sql-browser/table-sidebar.tsx +38 -0
- package/runtime/dashboard/sql-browser/types.ts +61 -0
- package/runtime/dashboard/sql-browser/utils.ts +167 -0
- package/runtime/dashboard/style.css +177 -0
- package/runtime/dashboard/views/ai.tsx +152 -0
- package/runtime/dashboard/views/analytics-engine.tsx +169 -0
- package/runtime/dashboard/views/cache.tsx +93 -0
- package/runtime/dashboard/views/containers.tsx +197 -0
- package/runtime/dashboard/views/d1.tsx +81 -0
- package/runtime/dashboard/views/do.tsx +168 -0
- package/runtime/dashboard/views/email.tsx +235 -0
- package/runtime/dashboard/views/errors.tsx +558 -0
- package/runtime/dashboard/views/home.tsx +287 -0
- package/runtime/dashboard/views/kv.tsx +273 -0
- package/runtime/dashboard/views/queue.tsx +193 -0
- package/runtime/dashboard/views/r2.tsx +202 -0
- package/runtime/dashboard/views/scheduled.tsx +89 -0
- package/runtime/dashboard/views/trace-waterfall.tsx +410 -0
- package/runtime/dashboard/views/traces.tsx +768 -0
- package/runtime/dashboard/views/workers.tsx +55 -0
- package/runtime/dashboard/views/workflows.tsx +473 -0
- package/runtime/db.ts +258 -0
- package/runtime/env.ts +362 -0
- package/runtime/error-page/app.tsx +394 -0
- package/runtime/error-page/build.ts +269 -0
- package/runtime/error-page/index.html +16 -0
- package/runtime/error-page/style.css +31 -0
- package/runtime/execution-context.ts +18 -0
- package/runtime/file-watcher.ts +57 -0
- package/runtime/generation-manager.ts +230 -0
- package/runtime/generation.ts +411 -0
- package/runtime/plugin.ts +292 -0
- package/runtime/request-cf.ts +28 -0
- package/runtime/rpc-validate.ts +154 -0
- package/runtime/tracing/context.ts +40 -0
- package/runtime/tracing/db.ts +73 -0
- package/runtime/tracing/frames.ts +75 -0
- package/runtime/tracing/instrument.ts +186 -0
- package/runtime/tracing/span.ts +138 -0
- package/runtime/tracing/store.ts +499 -0
- package/runtime/tracing/types.ts +47 -0
- package/runtime/vite-plugin/config-plugin.ts +68 -0
- package/runtime/vite-plugin/dev-server-plugin.ts +493 -0
- package/runtime/vite-plugin/dist/index.mjs +52333 -0
- package/runtime/vite-plugin/globals-plugin.ts +94 -0
- package/runtime/vite-plugin/index.ts +43 -0
- package/runtime/vite-plugin/modules-plugin.ts +88 -0
- package/runtime/vite-plugin/react-router-plugin.ts +95 -0
- package/runtime/worker-registry.ts +52 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare-specific crypto APIs: crypto.subtle.timingSafeEqual and crypto.DigestStream.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { timingSafeEqual } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Constant-time comparison of two buffers.
|
|
9
|
+
* Non-standard extension to Web Crypto matching Cloudflare's API.
|
|
10
|
+
*/
|
|
11
|
+
export function cfTimingSafeEqual(a: ArrayBuffer | ArrayBufferView, b: ArrayBuffer | ArrayBufferView): boolean {
|
|
12
|
+
const bufA = ArrayBuffer.isView(a) ? Buffer.from(a.buffer, a.byteOffset, a.byteLength) : Buffer.from(a);
|
|
13
|
+
const bufB = ArrayBuffer.isView(b) ? Buffer.from(b.buffer, b.byteOffset, b.byteLength) : Buffer.from(b);
|
|
14
|
+
|
|
15
|
+
if (bufA.byteLength !== bufB.byteLength) {
|
|
16
|
+
throw new TypeError("Input buffers must have the same byte length");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return timingSafeEqual(bufA, bufB);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* DigestStream — a WritableStream that computes a cryptographic hash of all written data.
|
|
24
|
+
* `digest` property is a Promise<ArrayBuffer> that resolves when the stream closes.
|
|
25
|
+
*/
|
|
26
|
+
export class DigestStream extends WritableStream<ArrayBuffer | ArrayBufferView> {
|
|
27
|
+
readonly digest: Promise<ArrayBuffer>;
|
|
28
|
+
|
|
29
|
+
constructor(algorithm: AlgorithmIdentifier) {
|
|
30
|
+
const algo = typeof algorithm === "string" ? algorithm : algorithm.name;
|
|
31
|
+
|
|
32
|
+
// Map CF algorithm names to Bun.CryptoHasher names
|
|
33
|
+
const algoMap: Record<string, string> = {
|
|
34
|
+
"SHA-1": "sha1",
|
|
35
|
+
"SHA-256": "sha256",
|
|
36
|
+
"SHA-384": "sha384",
|
|
37
|
+
"SHA-512": "sha512",
|
|
38
|
+
"MD5": "md5",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const hashName = algoMap[algo.toUpperCase()];
|
|
42
|
+
if (!hashName) {
|
|
43
|
+
throw new TypeError(`Unsupported algorithm: ${algo}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const hasher = new Bun.CryptoHasher(hashName as "sha1" | "sha256" | "sha384" | "sha512" | "md5");
|
|
47
|
+
let resolveDigest: (value: ArrayBuffer) => void;
|
|
48
|
+
|
|
49
|
+
const digestPromise = new Promise<ArrayBuffer>((resolve) => {
|
|
50
|
+
resolveDigest = resolve;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
super({
|
|
54
|
+
write(chunk) {
|
|
55
|
+
if (ArrayBuffer.isView(chunk)) {
|
|
56
|
+
hasher.update(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
|
|
57
|
+
} else {
|
|
58
|
+
hasher.update(new Uint8Array(chunk));
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
close() {
|
|
62
|
+
const result = hasher.digest();
|
|
63
|
+
resolveDigest(result.buffer as ArrayBuffer);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.digest = digestPromise;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Patches the global `crypto` object with CF-specific extensions.
|
|
73
|
+
*/
|
|
74
|
+
export function patchGlobalCrypto(): void {
|
|
75
|
+
// Add timingSafeEqual to crypto.subtle
|
|
76
|
+
const subtle = crypto.subtle;
|
|
77
|
+
Object.defineProperty(subtle, "timingSafeEqual", {
|
|
78
|
+
value: cfTimingSafeEqual,
|
|
79
|
+
writable: false,
|
|
80
|
+
configurable: true,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Add DigestStream to crypto
|
|
84
|
+
Object.defineProperty(crypto, "DigestStream", {
|
|
85
|
+
value: DigestStream,
|
|
86
|
+
writable: false,
|
|
87
|
+
configurable: true,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { Database, type SQLQueryBindings } from "bun:sqlite";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
interface D1Meta {
|
|
6
|
+
duration: number;
|
|
7
|
+
changes: number;
|
|
8
|
+
last_row_id: number;
|
|
9
|
+
served_by: string;
|
|
10
|
+
rows_read: number;
|
|
11
|
+
rows_written: number;
|
|
12
|
+
size_after: number;
|
|
13
|
+
changed_db: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface D1Result<T = Record<string, unknown>> {
|
|
17
|
+
results: T[];
|
|
18
|
+
success: boolean;
|
|
19
|
+
meta: D1Meta;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface D1ExecResult {
|
|
23
|
+
count: number;
|
|
24
|
+
duration: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildMeta(db: Database, durationMs: number, rowsRead: number, rowsWritten: number): D1Meta {
|
|
28
|
+
const changes = db.query<{ c: number }, []>("SELECT changes() as c").get()!.c;
|
|
29
|
+
const { page_count } = db.query<{ page_count: number }, []>("PRAGMA page_count").get()!;
|
|
30
|
+
const { page_size } = db.query<{ page_size: number }, []>("PRAGMA page_size").get()!;
|
|
31
|
+
return {
|
|
32
|
+
duration: durationMs,
|
|
33
|
+
changes,
|
|
34
|
+
last_row_id: db.query<{ id: number }, []>("SELECT last_insert_rowid() as id").get()!.id,
|
|
35
|
+
served_by: "bunflare-d1",
|
|
36
|
+
rows_read: rowsRead,
|
|
37
|
+
rows_written: rowsWritten,
|
|
38
|
+
size_after: page_count * page_size,
|
|
39
|
+
changed_db: changes > 0,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Split SQL text into individual statements, respecting string literals
|
|
45
|
+
* (single-quoted, double-quoted), line comments (--), and block comments.
|
|
46
|
+
*/
|
|
47
|
+
function splitStatements(sql: string): string[] {
|
|
48
|
+
const statements: string[] = [];
|
|
49
|
+
let current = "";
|
|
50
|
+
let i = 0;
|
|
51
|
+
const len = sql.length;
|
|
52
|
+
|
|
53
|
+
while (i < len) {
|
|
54
|
+
const ch = sql[i]!;
|
|
55
|
+
|
|
56
|
+
// Single-quoted string literal
|
|
57
|
+
if (ch === "'") {
|
|
58
|
+
current += ch;
|
|
59
|
+
i++;
|
|
60
|
+
while (i < len) {
|
|
61
|
+
const c = sql[i]!;
|
|
62
|
+
current += c;
|
|
63
|
+
i++;
|
|
64
|
+
if (c === "'" && i < len && sql[i] === "'") {
|
|
65
|
+
// escaped quote ''
|
|
66
|
+
current += sql[i]!;
|
|
67
|
+
i++;
|
|
68
|
+
} else if (c === "'") {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Double-quoted identifier
|
|
76
|
+
if (ch === '"') {
|
|
77
|
+
current += ch;
|
|
78
|
+
i++;
|
|
79
|
+
while (i < len) {
|
|
80
|
+
const c = sql[i]!;
|
|
81
|
+
current += c;
|
|
82
|
+
i++;
|
|
83
|
+
if (c === '"' && i < len && sql[i] === '"') {
|
|
84
|
+
current += sql[i]!;
|
|
85
|
+
i++;
|
|
86
|
+
} else if (c === '"') {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Line comment --
|
|
94
|
+
if (ch === "-" && i + 1 < len && sql[i + 1] === "-") {
|
|
95
|
+
i += 2;
|
|
96
|
+
while (i < len && sql[i] !== "\n") {
|
|
97
|
+
i++;
|
|
98
|
+
}
|
|
99
|
+
if (i < len) i++; // skip \n
|
|
100
|
+
current += " ";
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Block comment /* ... */
|
|
105
|
+
if (ch === "/" && i + 1 < len && sql[i + 1] === "*") {
|
|
106
|
+
i += 2;
|
|
107
|
+
while (i + 1 < len && !(sql[i] === "*" && sql[i + 1] === "/")) {
|
|
108
|
+
i++;
|
|
109
|
+
}
|
|
110
|
+
if (i + 1 < len) i += 2; // skip */
|
|
111
|
+
current += " ";
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Statement separator
|
|
116
|
+
if (ch === ";") {
|
|
117
|
+
const trimmed = current.trim();
|
|
118
|
+
if (trimmed.length > 0) {
|
|
119
|
+
statements.push(trimmed);
|
|
120
|
+
}
|
|
121
|
+
current = "";
|
|
122
|
+
i++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
current += ch;
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const trimmed = current.trim();
|
|
131
|
+
if (trimmed.length > 0) {
|
|
132
|
+
statements.push(trimmed);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return statements;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Convert bind parameters: boolean→int, undefined→error, ArrayBuffer→Uint8Array */
|
|
139
|
+
function convertBindParams(params: unknown[]): SQLQueryBindings[] {
|
|
140
|
+
return params.map((v, idx) => {
|
|
141
|
+
if (v === undefined) {
|
|
142
|
+
throw new Error(`D1_TYPE_ERROR: Cannot bind undefined value at index ${idx}. Use null instead.`);
|
|
143
|
+
}
|
|
144
|
+
if (typeof v === "boolean") {
|
|
145
|
+
return v ? 1 : 0;
|
|
146
|
+
}
|
|
147
|
+
if (v instanceof ArrayBuffer) {
|
|
148
|
+
return new Uint8Array(v);
|
|
149
|
+
}
|
|
150
|
+
return v as SQLQueryBindings;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Check if a SQL statement is a read query (returns rows) */
|
|
155
|
+
function isReadStatement(sql: string): boolean {
|
|
156
|
+
const upper = sql.trimStart().toUpperCase();
|
|
157
|
+
return upper.startsWith("SELECT") || upper.startsWith("WITH") || upper.startsWith("PRAGMA");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class LocalD1Database {
|
|
161
|
+
private db: Database;
|
|
162
|
+
|
|
163
|
+
constructor(db: Database) {
|
|
164
|
+
this.db = db;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
prepare(sql: string): LocalD1PreparedStatement {
|
|
168
|
+
return new LocalD1PreparedStatement(this.db, sql);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async batch<T = Record<string, unknown>>(statements: LocalD1PreparedStatement[]): Promise<D1Result<T>[]> {
|
|
172
|
+
const results: D1Result<T>[] = [];
|
|
173
|
+
this.db.run("BEGIN");
|
|
174
|
+
try {
|
|
175
|
+
for (const stmt of statements) {
|
|
176
|
+
results.push(await stmt.all<T>());
|
|
177
|
+
}
|
|
178
|
+
this.db.run("COMMIT");
|
|
179
|
+
} catch (e) {
|
|
180
|
+
this.db.run("ROLLBACK");
|
|
181
|
+
throw e;
|
|
182
|
+
}
|
|
183
|
+
return results;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async exec(sql: string): Promise<D1ExecResult> {
|
|
187
|
+
const start = performance.now();
|
|
188
|
+
let count = 0;
|
|
189
|
+
const statements = splitStatements(sql);
|
|
190
|
+
for (const stmt of statements) {
|
|
191
|
+
try {
|
|
192
|
+
this.db.run(stmt);
|
|
193
|
+
} catch (e) {
|
|
194
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
195
|
+
throw new Error(`D1_EXEC_ERROR: Error in SQL statement [${stmt}]: ${msg}`);
|
|
196
|
+
}
|
|
197
|
+
count++;
|
|
198
|
+
}
|
|
199
|
+
return { count, duration: performance.now() - start };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async dump(): Promise<ArrayBuffer> {
|
|
203
|
+
return this.db.serialize().buffer as ArrayBuffer;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
withSession(_bookmark?: string): LocalD1DatabaseSession {
|
|
207
|
+
return new LocalD1DatabaseSession(this.db);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export class LocalD1DatabaseSession {
|
|
212
|
+
private db: Database;
|
|
213
|
+
|
|
214
|
+
constructor(db: Database) {
|
|
215
|
+
this.db = db;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
prepare(sql: string): LocalD1PreparedStatement {
|
|
219
|
+
return new LocalD1PreparedStatement(this.db, sql);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async batch<T = Record<string, unknown>>(statements: LocalD1PreparedStatement[]): Promise<D1Result<T>[]> {
|
|
223
|
+
const results: D1Result<T>[] = [];
|
|
224
|
+
this.db.run("BEGIN");
|
|
225
|
+
try {
|
|
226
|
+
for (const stmt of statements) {
|
|
227
|
+
results.push(await stmt.all<T>());
|
|
228
|
+
}
|
|
229
|
+
this.db.run("COMMIT");
|
|
230
|
+
} catch (e) {
|
|
231
|
+
this.db.run("ROLLBACK");
|
|
232
|
+
throw e;
|
|
233
|
+
}
|
|
234
|
+
return results;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
getBookmark(): string | null {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export class LocalD1PreparedStatement {
|
|
243
|
+
private db: Database;
|
|
244
|
+
private sql: string;
|
|
245
|
+
private params: SQLQueryBindings[];
|
|
246
|
+
|
|
247
|
+
constructor(db: Database, sql: string) {
|
|
248
|
+
this.db = db;
|
|
249
|
+
this.sql = sql;
|
|
250
|
+
this.params = [];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
bind(...values: unknown[]): LocalD1PreparedStatement {
|
|
254
|
+
const stmt = new LocalD1PreparedStatement(this.db, this.sql);
|
|
255
|
+
stmt.params = convertBindParams(values);
|
|
256
|
+
return stmt;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async first<T = Record<string, unknown>>(column?: string): Promise<T | null> {
|
|
260
|
+
const start = performance.now();
|
|
261
|
+
const row = this.db.query(this.sql).get(...this.params) as Record<string, unknown> | null;
|
|
262
|
+
if (!row) return null;
|
|
263
|
+
if (column) {
|
|
264
|
+
if (!(column in row)) {
|
|
265
|
+
throw new Error(`D1_ERROR: Column '${column}' does not exist in the result set.`);
|
|
266
|
+
}
|
|
267
|
+
return (row[column] as T) ?? null;
|
|
268
|
+
}
|
|
269
|
+
return row as T;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async run(): Promise<D1Result> {
|
|
273
|
+
const start = performance.now();
|
|
274
|
+
this.db.query(this.sql).run(...this.params);
|
|
275
|
+
const duration = performance.now() - start;
|
|
276
|
+
const changes = this.db.query<{ c: number }, []>("SELECT changes() as c").get()!.c;
|
|
277
|
+
return {
|
|
278
|
+
results: [],
|
|
279
|
+
success: true,
|
|
280
|
+
meta: buildMeta(this.db, duration, 0, changes),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async all<T = Record<string, unknown>>(): Promise<D1Result<T>> {
|
|
285
|
+
const start = performance.now();
|
|
286
|
+
const results = this.db.query(this.sql).all(...this.params) as T[];
|
|
287
|
+
const duration = performance.now() - start;
|
|
288
|
+
const isRead = isReadStatement(this.sql);
|
|
289
|
+
const changes = this.db.query<{ c: number }, []>("SELECT changes() as c").get()!.c;
|
|
290
|
+
return {
|
|
291
|
+
results,
|
|
292
|
+
success: true,
|
|
293
|
+
meta: buildMeta(this.db, duration, isRead ? results.length : 0, isRead ? 0 : changes),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async raw<T extends unknown[] = unknown[]>(options?: { columnNames?: boolean }): Promise<T[]> {
|
|
298
|
+
const query = this.db.query(this.sql);
|
|
299
|
+
const columns = query.columnNames;
|
|
300
|
+
const rows = query.values(...this.params) as T[];
|
|
301
|
+
if (options?.columnNames) {
|
|
302
|
+
return [columns as unknown as T, ...rows];
|
|
303
|
+
}
|
|
304
|
+
return rows;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function openD1Database(dataDir: string, databaseName: string): LocalD1Database {
|
|
309
|
+
const d1Dir = join(dataDir, "d1");
|
|
310
|
+
mkdirSync(d1Dir, { recursive: true });
|
|
311
|
+
const dbPath = join(d1Dir, `${databaseName}.sqlite`);
|
|
312
|
+
const db = new Database(dbPath, { create: true });
|
|
313
|
+
db.run("PRAGMA journal_mode=WAL");
|
|
314
|
+
return new LocalD1Database(db);
|
|
315
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { DOExecutor, DOExecutorFactory, ExecutorConfig } from "./do-executor";
|
|
2
|
+
import { DurableObjectStateImpl, type DurableObjectBase } from "./durable-object";
|
|
3
|
+
import { warnInvalidRpcArgs } from "../rpc-validate";
|
|
4
|
+
import { wrapRpcReturnValue, createRpcFunctionStub } from "./rpc-stub";
|
|
5
|
+
|
|
6
|
+
export class InProcessExecutor implements DOExecutor {
|
|
7
|
+
private _state: DurableObjectStateImpl;
|
|
8
|
+
private _instance: DurableObjectBase;
|
|
9
|
+
private _containerRuntime?: import("./container").ContainerRuntime;
|
|
10
|
+
|
|
11
|
+
constructor(config: ExecutorConfig) {
|
|
12
|
+
const { id, db, namespaceName, cls, env, dataDir, limits, containerConfig, onAlarmSet } = config;
|
|
13
|
+
|
|
14
|
+
this._state = new DurableObjectStateImpl(id, db, namespaceName, dataDir, limits);
|
|
15
|
+
|
|
16
|
+
// Wire container runtime if configured
|
|
17
|
+
if (containerConfig) {
|
|
18
|
+
const { ContainerRuntime, ContainerContext } = require("./container") as typeof import("./container");
|
|
19
|
+
this._containerRuntime = new ContainerRuntime(
|
|
20
|
+
containerConfig.className,
|
|
21
|
+
id.toString(),
|
|
22
|
+
containerConfig.image,
|
|
23
|
+
containerConfig.dockerManager,
|
|
24
|
+
);
|
|
25
|
+
this._state.container = new ContainerContext(this._containerRuntime);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this._instance = new cls(this._state, env);
|
|
29
|
+
|
|
30
|
+
// Wire container runtime to ContainerBase instance
|
|
31
|
+
if (this._containerRuntime) {
|
|
32
|
+
const { ContainerBase } = require("./container") as typeof import("./container");
|
|
33
|
+
if (this._instance instanceof ContainerBase) {
|
|
34
|
+
this._instance._wireRuntime(this._containerRuntime);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Wire instance resolver for WebSocket handler delegation
|
|
39
|
+
this._state._setInstanceResolver(() => this._instance);
|
|
40
|
+
|
|
41
|
+
// Wire alarm callback
|
|
42
|
+
if (onAlarmSet) {
|
|
43
|
+
this._state.storage._setAlarmCallback(onAlarmSet);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async executeFetch(request: Request): Promise<Response> {
|
|
48
|
+
const unlock = await this._state._lock();
|
|
49
|
+
try {
|
|
50
|
+
await this._state._waitForReady();
|
|
51
|
+
const fetchFn = (this._instance as unknown as Record<string, unknown>).fetch;
|
|
52
|
+
if (typeof fetchFn !== "function") {
|
|
53
|
+
throw new Error("Durable Object does not implement fetch()");
|
|
54
|
+
}
|
|
55
|
+
return await (fetchFn as (req: Request) => Promise<Response>).call(this._instance, request);
|
|
56
|
+
} finally {
|
|
57
|
+
unlock();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async executeRpc(method: string, args: unknown[]): Promise<unknown> {
|
|
62
|
+
warnInvalidRpcArgs(args, method);
|
|
63
|
+
const unlock = await this._state._lock();
|
|
64
|
+
try {
|
|
65
|
+
await this._state._waitForReady();
|
|
66
|
+
const val = (this._instance as unknown as Record<string, unknown>)[method];
|
|
67
|
+
if (typeof val === "function") {
|
|
68
|
+
const result = await (val as (...a: unknown[]) => unknown).call(this._instance, ...args);
|
|
69
|
+
return wrapRpcReturnValue(result, method);
|
|
70
|
+
}
|
|
71
|
+
throw new Error(`"${method}" is not a method on the Durable Object`);
|
|
72
|
+
} finally {
|
|
73
|
+
unlock();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async executeRpcGet(prop: string): Promise<unknown> {
|
|
78
|
+
const unlock = await this._state._lock();
|
|
79
|
+
try {
|
|
80
|
+
await this._state._waitForReady();
|
|
81
|
+
const val = (this._instance as unknown as Record<string, unknown>)[prop];
|
|
82
|
+
if (typeof val === "function") {
|
|
83
|
+
return createRpcFunctionStub(val as Function, this._instance);
|
|
84
|
+
}
|
|
85
|
+
return wrapRpcReturnValue(val, prop);
|
|
86
|
+
} finally {
|
|
87
|
+
unlock();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async executeAlarm(retryCount: number): Promise<void> {
|
|
92
|
+
const unlock = await this._state._lock();
|
|
93
|
+
try {
|
|
94
|
+
await this._state._waitForReady();
|
|
95
|
+
const alarmFn = (this._instance as unknown as Record<string, unknown>).alarm;
|
|
96
|
+
if (typeof alarmFn === "function") {
|
|
97
|
+
await alarmFn.call(this._instance, {
|
|
98
|
+
retryCount,
|
|
99
|
+
isRetry: retryCount > 0,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
unlock();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
isActive(): boolean {
|
|
108
|
+
return this._state._hasActiveRequests();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
isBlocked(): boolean {
|
|
112
|
+
return this._state._isBlocked();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
activeWebSocketCount(): number {
|
|
116
|
+
return this._state.getWebSockets().length;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async dispose(): Promise<void> {
|
|
120
|
+
if (this._containerRuntime) {
|
|
121
|
+
await this._containerRuntime.cleanup();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @internal Get the raw DO instance (for testing/dashboard) */
|
|
126
|
+
get _rawInstance(): DurableObjectBase {
|
|
127
|
+
return this._instance;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @internal Get the state (for testing/alarm access) */
|
|
131
|
+
get _rawState(): DurableObjectStateImpl {
|
|
132
|
+
return this._state;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export class InProcessExecutorFactory implements DOExecutorFactory {
|
|
137
|
+
create(config: ExecutorConfig): DOExecutor {
|
|
138
|
+
return new InProcessExecutor(config);
|
|
139
|
+
}
|
|
140
|
+
}
|