orez-sync-cf-host 0.5.19 → 0.5.21
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 +49 -6
- package/dist/bun-wasm-loader.d.ts +1 -0
- package/dist/bun-wasm-loader.js +20 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +93 -0
- package/{src → dist}/generated/sync_wasm_bg.wasm +0 -0
- package/dist/host.d.ts +12 -0
- package/dist/host.js +1512 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/mutation-error.d.ts +9 -0
- package/dist/mutation-error.js +23 -0
- package/dist/node-wasm-loader.d.ts +1 -0
- package/dist/node-wasm-loader.js +22 -0
- package/dist/post-commit.d.ts +13 -0
- package/dist/post-commit.js +31 -0
- package/dist/query-compiler.d.ts +4 -0
- package/dist/query-compiler.js +4 -0
- package/dist/sql-storage-adapter.d.ts +69 -0
- package/dist/sql-storage-adapter.js +194 -0
- package/dist/transaction-query.d.ts +72 -0
- package/dist/transaction-query.js +379 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.js +5 -0
- package/dist/vite-wasm-loader.d.ts +3 -0
- package/dist/vite-wasm-loader.js +24 -0
- package/dist/wasm.d.ts +1 -0
- package/dist/wasm.js +7 -0
- package/dist/write-safeguards.d.ts +53 -0
- package/dist/write-safeguards.js +186 -0
- package/package.json +59 -19
- package/src/bun-wasm-loader.ts +0 -16
- package/src/config.ts +0 -102
- package/src/harness-config.ts +0 -487
- package/src/harness-worker.ts +0 -33
- package/src/host.ts +0 -1958
- package/src/index.ts +0 -47
- package/src/ingest-harness-worker.ts +0 -535
- package/src/mutation-error.ts +0 -28
- package/src/platform-probe-worker.ts +0 -418
- package/src/post-commit.ts +0 -40
- package/src/query-compiler.ts +0 -16
- package/src/sql-storage-adapter.ts +0 -254
- package/src/transaction-query.ts +0 -494
- package/src/types.ts +0 -195
- package/src/wasm-platform.ts +0 -10
- package/src/wasm.ts +0 -34
- package/src/write-safeguards.ts +0 -248
- /package/{src → dist}/generated/package.json +0 -0
- /package/{src → dist}/generated/sync_wasm.d.ts +0 -0
- /package/{src → dist}/generated/sync_wasm.js +0 -0
- /package/{src → dist}/generated/sync_wasm_bg.wasm.d.ts +0 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createSyncDurableObject, createSyncWorker } from './host.js';
|
|
2
|
+
export { createQueryCompiler } from './query-compiler.js';
|
|
3
|
+
export { MutationApplicationError, isMutationApplicationError, registerMutators, } from './types.js';
|
|
4
|
+
export { DEFAULT_TRANSACTION_QUERY_BUDGET, TransactionQueryBudgetError, executeTransactionQueryPlan, executeTransactionQueryPlanAsync, } from './transaction-query.js';
|
|
5
|
+
export type { DeferredEffect, JsonPrimitive, JsonValue, MutatorContext, MutatorRegistry, MutatorSql, NormalizedClaims, PullCaps, QueryResolver, RegisteredMutator, SyncHostConfig, SyncHostEnv, ServiceBinding, SQLiteExecResult, SqlStatementMetadata, SyncSql, UpstreamConfig, VisibilityConfig, VisibilityFilter, ZeroSchemaConfig, } from './types.js';
|
|
6
|
+
export type { SyncDurableObjectConstructor } from './host.js';
|
|
7
|
+
export type { CompiledTransactionQueryNode, CompiledTransactionQueryPlan, CompiledTransactionQueryRelationship, TransactionQueryBinding, TransactionQueryBudget, TransactionQueryColumn, TransactionQueryColumnType, TransactionQueryExecutionOptions, TransactionQueryFormat, TransactionQueryWireValue, } from './transaction-query.js';
|
|
8
|
+
export type { TransactionQueryCompiler } from './query-compiler.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createSyncDurableObject, createSyncWorker } from './host.js';
|
|
2
|
+
export { createQueryCompiler } from './query-compiler.js';
|
|
3
|
+
export { MutationApplicationError, isMutationApplicationError, registerMutators, } from './types.js';
|
|
4
|
+
export { DEFAULT_TRANSACTION_QUERY_BUDGET, TransactionQueryBudgetError, executeTransactionQueryPlan, executeTransactionQueryPlanAsync, } from './transaction-query.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class MutationApplicationError extends Error {
|
|
2
|
+
readonly details: string;
|
|
3
|
+
constructor(details: string, message?: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function isMutationApplicationError(error: unknown): error is {
|
|
6
|
+
name: string;
|
|
7
|
+
message: string;
|
|
8
|
+
details: string;
|
|
9
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// the app-rejection contract shared by every orez sync host and by
|
|
2
|
+
// protocol-neutral authoring layers (on-zero adapters). classification is
|
|
3
|
+
// STRUCTURAL, never instanceof: adapters must not depend on orez to reject a
|
|
4
|
+
// mutation, and instanceof breaks across package copies (dual-package
|
|
5
|
+
// installs, --into tarballs, workspace duplication). any error whose name is
|
|
6
|
+
// 'MutationApplicationError' with string details/message is claiming this
|
|
7
|
+
// contract deliberately.
|
|
8
|
+
export class MutationApplicationError extends Error {
|
|
9
|
+
details;
|
|
10
|
+
constructor(details, message = details) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.details = details;
|
|
13
|
+
this.name = 'MutationApplicationError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function isMutationApplicationError(error) {
|
|
17
|
+
if (typeof error !== 'object' || error === null)
|
|
18
|
+
return false;
|
|
19
|
+
const shaped = error;
|
|
20
|
+
return (shaped.name === 'MutationApplicationError' &&
|
|
21
|
+
typeof shaped.message === 'string' &&
|
|
22
|
+
typeof shaped.details === 'string');
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import * as nodeModule from 'node:module';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const wasmModuleID = 'orez-sync-cf-host/wasm-module.wasm';
|
|
6
|
+
const wasmModulePath = fileURLToPath(import.meta.resolve(wasmModuleID));
|
|
7
|
+
const wasmBase64 = readFileSync(wasmModulePath).toString('base64');
|
|
8
|
+
const wasmModuleSource = `import { Buffer } from 'node:buffer'
|
|
9
|
+
export default new WebAssembly.Module(Buffer.from('${wasmBase64}', 'base64'))
|
|
10
|
+
`;
|
|
11
|
+
const virtualWasmModuleURL = `data:text/javascript;base64,${Buffer.from(wasmModuleSource).toString('base64')}`;
|
|
12
|
+
const registerHooks = Reflect.get(nodeModule, 'registerHooks');
|
|
13
|
+
if (!registerHooks) {
|
|
14
|
+
throw new Error('orez-sync-cf-host/node-wasm-loader requires Node 22.15 or newer');
|
|
15
|
+
}
|
|
16
|
+
registerHooks({
|
|
17
|
+
resolve(specifier, context, nextResolve) {
|
|
18
|
+
return specifier === wasmModuleID
|
|
19
|
+
? { shortCircuit: true, url: virtualWasmModuleURL }
|
|
20
|
+
: nextResolve(specifier, context);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type DeferredEffect = () => void | Promise<void>;
|
|
2
|
+
export type PostCommitEffects = {
|
|
3
|
+
/** discard effects from a storage-transaction attempt before starting another */
|
|
4
|
+
beginAttempt(): void;
|
|
5
|
+
defer(effect: DeferredEffect): void;
|
|
6
|
+
/** run the committed attempt's effects in order without rejecting */
|
|
7
|
+
runAfterCommit(onError: (error: unknown) => void): Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* collect external work during a retryable transaction and run only the effects
|
|
11
|
+
* belonging to the attempt that commits.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createPostCommitEffects(): PostCommitEffects;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* collect external work during a retryable transaction and run only the effects
|
|
3
|
+
* belonging to the attempt that commits.
|
|
4
|
+
*/
|
|
5
|
+
export function createPostCommitEffects() {
|
|
6
|
+
const effects = [];
|
|
7
|
+
return {
|
|
8
|
+
beginAttempt() {
|
|
9
|
+
effects.length = 0;
|
|
10
|
+
},
|
|
11
|
+
defer(effect) {
|
|
12
|
+
effects.push(effect);
|
|
13
|
+
},
|
|
14
|
+
async runAfterCommit(onError) {
|
|
15
|
+
const committed = effects.splice(0);
|
|
16
|
+
for (const effect of committed) {
|
|
17
|
+
try {
|
|
18
|
+
await effect();
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
try {
|
|
22
|
+
onError(error);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// reporting must not turn a committed effect failure into a rejection
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CompiledTransactionQueryPlan, TransactionQueryFormat } from './transaction-query.js';
|
|
2
|
+
import type { ZeroSchemaConfig } from './types.js';
|
|
3
|
+
export type TransactionQueryCompiler = (ast: unknown, format: TransactionQueryFormat) => CompiledTransactionQueryPlan;
|
|
4
|
+
export declare function createQueryCompiler(schema: ZeroSchemaConfig): TransactionQueryCompiler;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CompiledTransactionQueryPlan, TransactionQueryBudget, TransactionQueryFormat } from './transaction-query.js';
|
|
2
|
+
import type { MutatorSql, SQLiteExecResult, SqlStatementMetadata, SyncSql } from './types.js';
|
|
3
|
+
type WireValue = {
|
|
4
|
+
kind: 'null';
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'integer';
|
|
7
|
+
value: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'real';
|
|
10
|
+
value: number;
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'text';
|
|
13
|
+
value: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'blob';
|
|
16
|
+
value: number[];
|
|
17
|
+
};
|
|
18
|
+
type WireRow = {
|
|
19
|
+
columns: string[];
|
|
20
|
+
values: WireValue[];
|
|
21
|
+
};
|
|
22
|
+
interface JsSyncDb {
|
|
23
|
+
exec(sql: string, params: WireValue[]): void;
|
|
24
|
+
query(sql: string, params: WireValue[]): WireRow[];
|
|
25
|
+
}
|
|
26
|
+
export type AdapterStats = {
|
|
27
|
+
execCalls: number;
|
|
28
|
+
queryCalls: number;
|
|
29
|
+
sqlMs: number;
|
|
30
|
+
};
|
|
31
|
+
export declare function assertHostSql(sql: string): void;
|
|
32
|
+
export declare function decodeSqlParams(value: unknown): unknown[];
|
|
33
|
+
/**
|
|
34
|
+
* The sole JS SQL adapter used by wasm. It is synchronous, consumes every
|
|
35
|
+
* cursor before returning, accepts positional `?` only, and never starts or
|
|
36
|
+
* finishes a transaction.
|
|
37
|
+
*/
|
|
38
|
+
export declare class SqlStorageSyncDb implements JsSyncDb {
|
|
39
|
+
private readonly sql;
|
|
40
|
+
private readonly recordRowsWritten;
|
|
41
|
+
readonly stats: AdapterStats;
|
|
42
|
+
constructor(sql: SqlStorage, recordRowsWritten?: (rows: number) => void);
|
|
43
|
+
resetStats(): void;
|
|
44
|
+
exec(sql: string, params: WireValue[]): void;
|
|
45
|
+
query(sql: string, params: WireValue[]): WireRow[];
|
|
46
|
+
}
|
|
47
|
+
/** Direct application SQL surface used for initialization and admin reads. */
|
|
48
|
+
export declare class SqlStorageDirect implements SyncSql {
|
|
49
|
+
private readonly sql;
|
|
50
|
+
private readonly recordRowsWritten;
|
|
51
|
+
constructor(sql: SqlStorage, recordRowsWritten?: (rows: number) => void);
|
|
52
|
+
exec(sql: string, params?: readonly unknown[], _metadata?: SqlStatementMetadata): SQLiteExecResult;
|
|
53
|
+
query<Row extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: readonly unknown[]): Row[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Async-compatible consumer mutator surface. Each method fully completes and
|
|
57
|
+
* materializes its SqlStorage operation before its promise resolves, so an
|
|
58
|
+
* existing `await tx.query(...)` cannot carry a cursor across an await.
|
|
59
|
+
*/
|
|
60
|
+
export declare class SqlStorageMutatorTransaction implements MutatorSql {
|
|
61
|
+
private readonly direct;
|
|
62
|
+
private readonly compileQuery;
|
|
63
|
+
private readonly queryBudget?;
|
|
64
|
+
constructor(direct: SqlStorageDirect, compileQuery: (ast: unknown, format: TransactionQueryFormat) => CompiledTransactionQueryPlan, queryBudget?: Partial<TransactionQueryBudget> | undefined);
|
|
65
|
+
exec(sql: string, params?: readonly unknown[], metadata?: SqlStatementMetadata): Promise<SQLiteExecResult>;
|
|
66
|
+
query<Row extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<Row[]>;
|
|
67
|
+
queryAst<Result = unknown>(ast: unknown, format: TransactionQueryFormat, queryName?: string): Promise<Result>;
|
|
68
|
+
}
|
|
69
|
+
export {};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { executeTransactionQueryPlan } from './transaction-query.js';
|
|
2
|
+
import { trackBillableCursorRows } from './write-safeguards.js';
|
|
3
|
+
// Match transaction control only as the statement itself. CREATE TRIGGER uses
|
|
4
|
+
// `... BEGIN ... END` for its body, which is valid DO SQL and not a transaction.
|
|
5
|
+
const TX_SQL = /^\s*(BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE)(?=\s|;|$)/i;
|
|
6
|
+
const NUMBERED_PARAMETER = /\?[0-9]+/;
|
|
7
|
+
const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
|
|
8
|
+
const MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
|
|
9
|
+
export function assertHostSql(sql) {
|
|
10
|
+
if (TX_SQL.test(sql)) {
|
|
11
|
+
throw new TypeError('transaction SQL is host-owned and forbidden');
|
|
12
|
+
}
|
|
13
|
+
if (NUMBERED_PARAMETER.test(sql)) {
|
|
14
|
+
throw new TypeError('numbered parameters are forbidden; use positional ? bindings');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function decodeBinding(value, path = 'binding') {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
19
|
+
throw new TypeError(`${path} must be a typed SQL value`);
|
|
20
|
+
}
|
|
21
|
+
const binding = value;
|
|
22
|
+
const keys = Object.keys(binding);
|
|
23
|
+
const assertValueShape = () => {
|
|
24
|
+
if (keys.length !== 2 || !keys.includes('kind') || !keys.includes('value')) {
|
|
25
|
+
throw new TypeError(`${path} must contain only kind and value`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
switch (binding.kind) {
|
|
29
|
+
case 'null':
|
|
30
|
+
if (keys.length !== 1 || keys[0] !== 'kind') {
|
|
31
|
+
throw new TypeError(`${path} null binding must contain only kind`);
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
case 'integer': {
|
|
35
|
+
assertValueShape();
|
|
36
|
+
if (typeof binding.value !== 'string') {
|
|
37
|
+
throw new TypeError(`${path}.value must be an i64 decimal string`);
|
|
38
|
+
}
|
|
39
|
+
if (!/^-?\d+$/.test(binding.value)) {
|
|
40
|
+
throw new TypeError(`${path}.value must be an i64 decimal string`);
|
|
41
|
+
}
|
|
42
|
+
let exact;
|
|
43
|
+
try {
|
|
44
|
+
exact = BigInt(binding.value);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new TypeError(`${path}.value must be an i64 decimal string`);
|
|
48
|
+
}
|
|
49
|
+
if (exact < -(1n << 63n) || exact > (1n << 63n) - 1n) {
|
|
50
|
+
throw new TypeError(`${path}.value is outside the i64 range`);
|
|
51
|
+
}
|
|
52
|
+
// SqlStorage does not accept bigint. Safe values can use Number; unsafe
|
|
53
|
+
// values use decimal text and rely on the destination's INTEGER affinity.
|
|
54
|
+
return exact >= MIN_SAFE && exact <= MAX_SAFE ? Number(exact) : binding.value;
|
|
55
|
+
}
|
|
56
|
+
case 'real': {
|
|
57
|
+
assertValueShape();
|
|
58
|
+
if (typeof binding.value !== 'number' || !Number.isFinite(binding.value)) {
|
|
59
|
+
throw new TypeError(`${path}.value must be a finite number`);
|
|
60
|
+
}
|
|
61
|
+
return binding.value;
|
|
62
|
+
}
|
|
63
|
+
case 'text': {
|
|
64
|
+
assertValueShape();
|
|
65
|
+
if (typeof binding.value !== 'string') {
|
|
66
|
+
throw new TypeError(`${path}.value must be a string`);
|
|
67
|
+
}
|
|
68
|
+
return binding.value;
|
|
69
|
+
}
|
|
70
|
+
case 'blob': {
|
|
71
|
+
assertValueShape();
|
|
72
|
+
if (!Array.isArray(binding.value) ||
|
|
73
|
+
binding.value.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) {
|
|
74
|
+
throw new TypeError(`${path}.value must be an array of bytes`);
|
|
75
|
+
}
|
|
76
|
+
return Uint8Array.from(binding.value).buffer;
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
throw new TypeError(`${path}.kind must be null, integer, real, text, or blob`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function decodeSqlParams(value) {
|
|
83
|
+
if (value === undefined)
|
|
84
|
+
return [];
|
|
85
|
+
if (!Array.isArray(value))
|
|
86
|
+
throw new TypeError('params must be an array');
|
|
87
|
+
return value.map((binding, index) => decodeBinding(binding, `params[${index}]`));
|
|
88
|
+
}
|
|
89
|
+
function encodeResult(value) {
|
|
90
|
+
if (value === null)
|
|
91
|
+
return { kind: 'null' };
|
|
92
|
+
if (typeof value === 'number') {
|
|
93
|
+
return Number.isInteger(value)
|
|
94
|
+
? { kind: 'integer', value: String(value) }
|
|
95
|
+
: { kind: 'real', value };
|
|
96
|
+
}
|
|
97
|
+
if (typeof value === 'string')
|
|
98
|
+
return { kind: 'text', value };
|
|
99
|
+
if (value instanceof ArrayBuffer) {
|
|
100
|
+
return { kind: 'blob', value: Array.from(new Uint8Array(value)) };
|
|
101
|
+
}
|
|
102
|
+
if (ArrayBuffer.isView(value)) {
|
|
103
|
+
return {
|
|
104
|
+
kind: 'blob',
|
|
105
|
+
value: Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
throw new TypeError(`unsupported SqlStorage result: ${Object.prototype.toString.call(value)}`);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The sole JS SQL adapter used by wasm. It is synchronous, consumes every
|
|
112
|
+
* cursor before returning, accepts positional `?` only, and never starts or
|
|
113
|
+
* finishes a transaction.
|
|
114
|
+
*/
|
|
115
|
+
export class SqlStorageSyncDb {
|
|
116
|
+
sql;
|
|
117
|
+
recordRowsWritten;
|
|
118
|
+
stats = { execCalls: 0, queryCalls: 0, sqlMs: 0 };
|
|
119
|
+
constructor(sql, recordRowsWritten = () => { }) {
|
|
120
|
+
this.sql = sql;
|
|
121
|
+
this.recordRowsWritten = recordRowsWritten;
|
|
122
|
+
}
|
|
123
|
+
resetStats() {
|
|
124
|
+
this.stats.execCalls = 0;
|
|
125
|
+
this.stats.queryCalls = 0;
|
|
126
|
+
this.stats.sqlMs = 0;
|
|
127
|
+
}
|
|
128
|
+
exec(sql, params) {
|
|
129
|
+
assertHostSql(sql);
|
|
130
|
+
const start = performance.now();
|
|
131
|
+
trackBillableCursorRows(this.sql.exec(sql, ...params.map((value) => decodeBinding(value))), this.recordRowsWritten);
|
|
132
|
+
this.stats.execCalls++;
|
|
133
|
+
this.stats.sqlMs += performance.now() - start;
|
|
134
|
+
}
|
|
135
|
+
query(sql, params) {
|
|
136
|
+
assertHostSql(sql);
|
|
137
|
+
const start = performance.now();
|
|
138
|
+
const cursor = trackBillableCursorRows(this.sql.exec(sql, ...params.map((value) => decodeBinding(value))), this.recordRowsWritten);
|
|
139
|
+
const columns = [...cursor.columnNames];
|
|
140
|
+
// A SqlStorage cursor must be fully consumed before any await. Returning a
|
|
141
|
+
// materialized array here makes that property structural.
|
|
142
|
+
const rows = Array.from(cursor.raw()).map((values) => ({
|
|
143
|
+
columns,
|
|
144
|
+
values: values.map(encodeResult),
|
|
145
|
+
}));
|
|
146
|
+
this.stats.queryCalls++;
|
|
147
|
+
this.stats.sqlMs += performance.now() - start;
|
|
148
|
+
return rows;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Direct application SQL surface used for initialization and admin reads. */
|
|
152
|
+
export class SqlStorageDirect {
|
|
153
|
+
sql;
|
|
154
|
+
recordRowsWritten;
|
|
155
|
+
constructor(sql, recordRowsWritten = () => { }) {
|
|
156
|
+
this.sql = sql;
|
|
157
|
+
this.recordRowsWritten = recordRowsWritten;
|
|
158
|
+
}
|
|
159
|
+
exec(sql, params = [], _metadata) {
|
|
160
|
+
assertHostSql(sql);
|
|
161
|
+
trackBillableCursorRows(this.sql.exec(sql, ...params), this.recordRowsWritten).toArray();
|
|
162
|
+
const changes = Number(this.sql.exec('SELECT changes() AS changes').one()?.changes ?? 0);
|
|
163
|
+
return { changes };
|
|
164
|
+
}
|
|
165
|
+
query(sql, params = []) {
|
|
166
|
+
assertHostSql(sql);
|
|
167
|
+
return trackBillableCursorRows(this.sql.exec(sql, ...params), this.recordRowsWritten).toArray();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Async-compatible consumer mutator surface. Each method fully completes and
|
|
172
|
+
* materializes its SqlStorage operation before its promise resolves, so an
|
|
173
|
+
* existing `await tx.query(...)` cannot carry a cursor across an await.
|
|
174
|
+
*/
|
|
175
|
+
export class SqlStorageMutatorTransaction {
|
|
176
|
+
direct;
|
|
177
|
+
compileQuery;
|
|
178
|
+
queryBudget;
|
|
179
|
+
constructor(direct, compileQuery, queryBudget) {
|
|
180
|
+
this.direct = direct;
|
|
181
|
+
this.compileQuery = compileQuery;
|
|
182
|
+
this.queryBudget = queryBudget;
|
|
183
|
+
}
|
|
184
|
+
async exec(sql, params = [], metadata) {
|
|
185
|
+
return this.direct.exec(sql, params, metadata);
|
|
186
|
+
}
|
|
187
|
+
async query(sql, params = []) {
|
|
188
|
+
return this.direct.query(sql, params);
|
|
189
|
+
}
|
|
190
|
+
async queryAst(ast, format, queryName) {
|
|
191
|
+
const compiled = this.compileQuery(ast, format);
|
|
192
|
+
return executeTransactionQueryPlan(compiled, (sql, params) => this.direct.query(sql, params), { queryName, budget: this.queryBudget });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export type TransactionQueryWireValue = {
|
|
2
|
+
kind: 'null';
|
|
3
|
+
} | {
|
|
4
|
+
kind: 'integer';
|
|
5
|
+
value: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'real';
|
|
8
|
+
value: number;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'text';
|
|
11
|
+
value: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'blob';
|
|
14
|
+
value: number[];
|
|
15
|
+
};
|
|
16
|
+
export type TransactionQueryBinding = {
|
|
17
|
+
kind: 'literal';
|
|
18
|
+
value: TransactionQueryWireValue;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'parent_field';
|
|
21
|
+
field: string;
|
|
22
|
+
};
|
|
23
|
+
export type TransactionQueryColumnType = 'string' | 'number' | 'boolean' | 'json' | 'null';
|
|
24
|
+
export type TransactionQueryColumn = {
|
|
25
|
+
name: string;
|
|
26
|
+
columnType: TransactionQueryColumnType;
|
|
27
|
+
};
|
|
28
|
+
export type CompiledTransactionQueryRelationship = {
|
|
29
|
+
name: string;
|
|
30
|
+
node: CompiledTransactionQueryNode;
|
|
31
|
+
};
|
|
32
|
+
export type CompiledTransactionQueryNode = {
|
|
33
|
+
table: string;
|
|
34
|
+
singular: boolean;
|
|
35
|
+
sql: string;
|
|
36
|
+
bindings: TransactionQueryBinding[];
|
|
37
|
+
columns: TransactionQueryColumn[];
|
|
38
|
+
relationships: CompiledTransactionQueryRelationship[];
|
|
39
|
+
};
|
|
40
|
+
export type CompiledTransactionQueryPlan = {
|
|
41
|
+
rootTable: string;
|
|
42
|
+
planHash: string;
|
|
43
|
+
root: CompiledTransactionQueryNode;
|
|
44
|
+
};
|
|
45
|
+
export type TransactionQueryBudget = {
|
|
46
|
+
maxSelects: number;
|
|
47
|
+
maxRows: number;
|
|
48
|
+
};
|
|
49
|
+
export type TransactionQueryExecutionOptions = {
|
|
50
|
+
queryName?: string;
|
|
51
|
+
budget?: Partial<TransactionQueryBudget>;
|
|
52
|
+
};
|
|
53
|
+
export type TransactionQueryFormat = {
|
|
54
|
+
singular: boolean;
|
|
55
|
+
relationships: Readonly<Record<string, TransactionQueryFormat>>;
|
|
56
|
+
};
|
|
57
|
+
export declare const DEFAULT_TRANSACTION_QUERY_BUDGET: TransactionQueryBudget;
|
|
58
|
+
export declare class TransactionQueryBudgetError extends Error {
|
|
59
|
+
readonly query: string;
|
|
60
|
+
readonly selects: number;
|
|
61
|
+
readonly rows: number;
|
|
62
|
+
readonly maxSelects: number;
|
|
63
|
+
readonly maxRows: number;
|
|
64
|
+
readonly code = "transaction_query_budget_exceeded";
|
|
65
|
+
constructor(query: string, selects: number, rows: number, maxSelects: number, maxRows: number);
|
|
66
|
+
}
|
|
67
|
+
type MaterializedRow = Record<string, unknown>;
|
|
68
|
+
type ExecuteSelect = (sql: string, params: readonly unknown[]) => readonly MaterializedRow[];
|
|
69
|
+
type ExecuteSelectAsync = (sql: string, params: readonly unknown[]) => readonly MaterializedRow[] | Promise<readonly MaterializedRow[]>;
|
|
70
|
+
export declare function executeTransactionQueryPlan<Result = unknown>(plan: CompiledTransactionQueryPlan, execute: ExecuteSelect, options?: TransactionQueryExecutionOptions): Result;
|
|
71
|
+
export declare function executeTransactionQueryPlanAsync<Result = unknown>(plan: CompiledTransactionQueryPlan, execute: ExecuteSelectAsync, options?: TransactionQueryExecutionOptions): Promise<Result>;
|
|
72
|
+
export {};
|