orez-sync-cf-host 0.16.7 → 0.16.8

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.
Binary file
package/dist/host.js CHANGED
@@ -531,7 +531,13 @@ export function createSyncDurableObject(config) {
531
531
  const recordRowsRead = (rows) => {
532
532
  this.#sqlBilling.rowsRead += rows;
533
533
  };
534
- this.#engineDb = new SqlStorageSyncDb(ctx.storage.sql, recordRowsWritten, recordRowsRead);
534
+ this.#engineDb = new SqlStorageSyncDb(ctx.storage.sql, recordRowsWritten, recordRowsRead, (failure) => {
535
+ console.error(JSON.stringify({
536
+ event: 'sync_sql_failure',
537
+ hostVersion: config.hostVersion,
538
+ ...failure,
539
+ }));
540
+ });
535
541
  this.#directSql = new SqlStorageDirect(ctx.storage.sql, recordRowsWritten, recordRowsRead);
536
542
  this.#mutatorSql = new SqlStorageMutatorTransaction(this.#directSql, (ast, format) => this.#wasm(() => compileQuery(ast, format)), config.transactionQueryBudget);
537
543
  const database = {
@@ -17,6 +17,22 @@ export type AdapterStats = {
17
17
  queryCalls: number;
18
18
  sqlMs: number;
19
19
  };
20
+ export type SqlStorageFailureBinding = {
21
+ index: number;
22
+ kind: WireValue['kind'];
23
+ value?: string | number | null;
24
+ bytes?: number;
25
+ truncated?: true;
26
+ };
27
+ export type SqlStorageFailure = {
28
+ operation: 'exec' | 'query';
29
+ error: string;
30
+ sql: string;
31
+ sqlBytes: number;
32
+ sqlTruncated?: true;
33
+ paramCount: number;
34
+ params: SqlStorageFailureBinding[];
35
+ };
20
36
  export declare function assertHostSql(sql: string): void;
21
37
  export declare function decodeSqlParams(value: unknown): unknown[];
22
38
  /**
@@ -28,8 +44,9 @@ export declare class SqlStorageSyncDb implements JsSyncDb {
28
44
  private readonly sql;
29
45
  private readonly recordRowsWritten;
30
46
  private readonly recordRowsRead;
47
+ private readonly recordFailure;
31
48
  readonly stats: AdapterStats;
32
- constructor(sql: SqlStorage, recordRowsWritten?: (rows: number) => void, recordRowsRead?: (rows: number) => void);
49
+ constructor(sql: SqlStorage, recordRowsWritten?: (rows: number) => void, recordRowsRead?: (rows: number) => void, recordFailure?: (failure: SqlStorageFailure) => void);
33
50
  resetStats(): void;
34
51
  exec(sql: string, params: WireValue[]): void;
35
52
  query(sql: string, params: WireValue[]): WireRow[];
@@ -90,6 +90,45 @@ export function decodeSqlParams(value) {
90
90
  function encodeResult(value) {
91
91
  return encodeSqlValue(value);
92
92
  }
93
+ const MAX_FAILURE_TEXT_BYTES = 256;
94
+ const MAX_FAILURE_SQL_BYTES = 64 * 1024;
95
+ const MAX_FAILURE_BINDINGS = 512;
96
+ const failureTextEncoder = new TextEncoder();
97
+ function describeFailureBinding(value, index) {
98
+ if (value.kind === 'null')
99
+ return { index, kind: value.kind, value: null };
100
+ if (value.kind === 'blob')
101
+ return { index, kind: value.kind, bytes: value.value.length };
102
+ if (value.kind !== 'text')
103
+ return { index, kind: value.kind, value: value.value };
104
+ const bytes = failureTextEncoder.encode(value.value).byteLength;
105
+ if (bytes <= MAX_FAILURE_TEXT_BYTES) {
106
+ return { index, kind: value.kind, value: value.value, bytes };
107
+ }
108
+ return {
109
+ index,
110
+ kind: value.kind,
111
+ value: `${value.value.slice(0, MAX_FAILURE_TEXT_BYTES / 2)}…${value.value.slice(-MAX_FAILURE_TEXT_BYTES / 2)}`,
112
+ bytes,
113
+ truncated: true,
114
+ };
115
+ }
116
+ function describeSqlFailure(operation, error, sql, params) {
117
+ const sqlBytes = failureTextEncoder.encode(sql).byteLength;
118
+ const failure = {
119
+ operation,
120
+ error: String(error),
121
+ sql: sqlBytes <= MAX_FAILURE_SQL_BYTES
122
+ ? sql
123
+ : `${sql.slice(0, MAX_FAILURE_SQL_BYTES / 2)}…${sql.slice(-MAX_FAILURE_SQL_BYTES / 2)}`,
124
+ sqlBytes,
125
+ paramCount: params.length,
126
+ params: params.slice(0, MAX_FAILURE_BINDINGS).map(describeFailureBinding),
127
+ };
128
+ if (sqlBytes > MAX_FAILURE_SQL_BYTES)
129
+ failure.sqlTruncated = true;
130
+ return failure;
131
+ }
93
132
  /**
94
133
  * The sole JS SQL adapter used by wasm. It is synchronous, consumes every
95
134
  * cursor before returning, accepts positional `?` only, and never starts or
@@ -99,11 +138,13 @@ export class SqlStorageSyncDb {
99
138
  sql;
100
139
  recordRowsWritten;
101
140
  recordRowsRead;
141
+ recordFailure;
102
142
  stats = { execCalls: 0, queryCalls: 0, sqlMs: 0 };
103
- constructor(sql, recordRowsWritten = () => { }, recordRowsRead = () => { }) {
143
+ constructor(sql, recordRowsWritten = () => { }, recordRowsRead = () => { }, recordFailure = () => { }) {
104
144
  this.sql = sql;
105
145
  this.recordRowsWritten = recordRowsWritten;
106
146
  this.recordRowsRead = recordRowsRead;
147
+ this.recordFailure = recordFailure;
107
148
  }
108
149
  resetStats() {
109
150
  this.stats.execCalls = 0;
@@ -113,21 +154,34 @@ export class SqlStorageSyncDb {
113
154
  exec(sql, params) {
114
155
  assertHostSql(sql);
115
156
  const start = performance.now();
116
- trackBillableCursorRows(this.sql.exec(sql, ...params.map((value) => decodeBinding(value))), this.recordRowsWritten, this.recordRowsRead);
157
+ try {
158
+ trackBillableCursorRows(this.sql.exec(sql, ...params.map((value) => decodeBinding(value))), this.recordRowsWritten, this.recordRowsRead);
159
+ }
160
+ catch (error) {
161
+ this.recordFailure(describeSqlFailure('exec', error, sql, params));
162
+ throw error;
163
+ }
117
164
  this.stats.execCalls++;
118
165
  this.stats.sqlMs += performance.now() - start;
119
166
  }
120
167
  query(sql, params) {
121
168
  assertHostSql(sql);
122
169
  const start = performance.now();
123
- const cursor = trackBillableCursorRows(this.sql.exec(sql, ...params.map((value) => decodeBinding(value))), this.recordRowsWritten, this.recordRowsRead);
124
- const columns = [...cursor.columnNames];
125
- // A SqlStorage cursor must be fully consumed before any await. Returning a
126
- // materialized array here makes that property structural.
127
- const rows = Array.from(cursor.raw()).map((values) => ({
128
- columns,
129
- values: values.map(encodeResult),
130
- }));
170
+ let rows;
171
+ try {
172
+ const cursor = trackBillableCursorRows(this.sql.exec(sql, ...params.map((value) => decodeBinding(value))), this.recordRowsWritten, this.recordRowsRead);
173
+ const columns = [...cursor.columnNames];
174
+ // A SqlStorage cursor must be fully consumed before any await. Returning a
175
+ // materialized array here makes that property structural.
176
+ rows = Array.from(cursor.raw()).map((values) => ({
177
+ columns,
178
+ values: values.map(encodeResult),
179
+ }));
180
+ }
181
+ catch (error) {
182
+ this.recordFailure(describeSqlFailure('query', error, sql, params));
183
+ throw error;
184
+ }
131
185
  this.stats.queryCalls++;
132
186
  this.stats.sqlMs += performance.now() - start;
133
187
  return rows;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orez-sync-cf-host",
3
- "version": "0.16.7",
3
+ "version": "0.16.8",
4
4
  "description": "Internal orez Cloudflare sync host. Do not import directly: consumers use orez-lite/cloudflare/sync.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@opentelemetry/api": "^1.9.0",
63
- "orez-sync-executor": "0.16.7"
63
+ "orez-sync-executor": "0.16.8"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@cloudflare/workers-types": "4.20260617.1",