@tinycloud/sdk-services 2.4.0-beta.8 → 2.4.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/dist/index.js CHANGED
@@ -67,6 +67,202 @@ function serviceError(code, message, service, options) {
67
67
  };
68
68
  }
69
69
 
70
+ // src/debug.ts
71
+ var DEBUG_FLAG = "TinyCloud_debug";
72
+ var MAX_EVENTS = 1e3;
73
+ function getGlobal() {
74
+ return globalThis;
75
+ }
76
+ function nowMs() {
77
+ const performanceNow = globalThis.performance?.now?.bind(globalThis.performance);
78
+ return typeof performanceNow === "function" ? performanceNow() : Date.now();
79
+ }
80
+ function isTrue(value) {
81
+ return value === true || value === "true" || value === "1";
82
+ }
83
+ function getProcessDebugFlag() {
84
+ const processLike = globalThis.process;
85
+ return processLike?.env?.[DEBUG_FLAG];
86
+ }
87
+ function isBrowserWindow() {
88
+ const global = getGlobal();
89
+ return global.window === globalThis;
90
+ }
91
+ function getLocalStorage() {
92
+ if (!isBrowserWindow()) {
93
+ return void 0;
94
+ }
95
+ try {
96
+ return globalThis.localStorage;
97
+ } catch {
98
+ return void 0;
99
+ }
100
+ }
101
+ function getStoredDebugFlag() {
102
+ const storage = getLocalStorage();
103
+ if (!storage) {
104
+ return void 0;
105
+ }
106
+ try {
107
+ return storage.getItem(DEBUG_FLAG);
108
+ } catch {
109
+ return void 0;
110
+ }
111
+ }
112
+ function setStoredDebugFlag(enabled) {
113
+ const storage = getLocalStorage();
114
+ if (!storage) {
115
+ return;
116
+ }
117
+ try {
118
+ if (enabled) {
119
+ storage.setItem(DEBUG_FLAG, "true");
120
+ } else {
121
+ storage.removeItem(DEBUG_FLAG);
122
+ }
123
+ } catch {
124
+ }
125
+ }
126
+ function shouldStartEnabled() {
127
+ const global = getGlobal();
128
+ return isTrue(global[DEBUG_FLAG]) || isTrue(getStoredDebugFlag()) || isTrue(getProcessDebugFlag());
129
+ }
130
+ var TinyCloudDebugLogger = class {
131
+ constructor() {
132
+ this.enabled = shouldStartEnabled();
133
+ this.sequence = 0;
134
+ this.events = [];
135
+ this.maxEvents = MAX_EVENTS;
136
+ }
137
+ isEnabled() {
138
+ return this.enabled || isTrue(getGlobal()[DEBUG_FLAG]) || isTrue(getProcessDebugFlag());
139
+ }
140
+ enable(options = {}) {
141
+ this.enabled = true;
142
+ getGlobal()[DEBUG_FLAG] = true;
143
+ if (options.persist !== false) {
144
+ setStoredDebugFlag(true);
145
+ }
146
+ this.log("debug.enabled", { persisted: options.persist !== false });
147
+ }
148
+ disable(options = {}) {
149
+ this.log("debug.disabled", { persisted: options.persist !== false });
150
+ this.enabled = false;
151
+ getGlobal()[DEBUG_FLAG] = false;
152
+ if (options.persist !== false) {
153
+ setStoredDebugFlag(false);
154
+ }
155
+ }
156
+ clear() {
157
+ this.events = [];
158
+ }
159
+ getLogs() {
160
+ return [...this.events];
161
+ }
162
+ log(event, data, message) {
163
+ if (!this.isEnabled()) {
164
+ return void 0;
165
+ }
166
+ return this.record({
167
+ event,
168
+ data,
169
+ message,
170
+ level: "debug",
171
+ timestamp: Date.now(),
172
+ timestampIso: (/* @__PURE__ */ new Date()).toISOString()
173
+ });
174
+ }
175
+ startTimer(event, data) {
176
+ if (!this.isEnabled()) {
177
+ return { stop: () => void 0 };
178
+ }
179
+ const startedAt = nowMs();
180
+ this.log(`${event}.start`, data);
181
+ return {
182
+ stop: (finishData) => {
183
+ const endedAt = nowMs();
184
+ return this.record({
185
+ event: `${event}.end`,
186
+ data: finishData,
187
+ durationMs: endedAt - startedAt,
188
+ startedAt,
189
+ endedAt,
190
+ level: "debug",
191
+ timestamp: Date.now(),
192
+ timestampIso: (/* @__PURE__ */ new Date()).toISOString()
193
+ });
194
+ }
195
+ };
196
+ }
197
+ async timeAsync(event, operation, data) {
198
+ if (!this.isEnabled()) {
199
+ return operation();
200
+ }
201
+ const timer = this.startTimer(event, data);
202
+ try {
203
+ const result = await operation();
204
+ timer.stop({ ok: true });
205
+ return result;
206
+ } catch (error) {
207
+ timer.stop({ ok: false, error });
208
+ throw error;
209
+ }
210
+ }
211
+ time(event, operation, data) {
212
+ if (!this.isEnabled()) {
213
+ return operation();
214
+ }
215
+ const timer = this.startTimer(event, data);
216
+ try {
217
+ const result = operation();
218
+ timer.stop({ ok: true });
219
+ return result;
220
+ } catch (error) {
221
+ timer.stop({ ok: false, error });
222
+ throw error;
223
+ }
224
+ }
225
+ record(event) {
226
+ const debugEvent = {
227
+ ...event,
228
+ sequence: ++this.sequence
229
+ };
230
+ this.events.push(debugEvent);
231
+ if (this.events.length > this.maxEvents) {
232
+ this.events.splice(0, this.events.length - this.maxEvents);
233
+ }
234
+ try {
235
+ globalThis.console?.debug?.("[TinyCloud]", debugEvent.event, debugEvent);
236
+ } catch {
237
+ }
238
+ return debugEvent;
239
+ }
240
+ };
241
+ var tinyCloudDebugLogger = new TinyCloudDebugLogger();
242
+ function enableTinyCloudDebug(options) {
243
+ tinyCloudDebugLogger.enable(options);
244
+ return tinyCloudDebugLogger;
245
+ }
246
+ function disableTinyCloudDebug(options) {
247
+ tinyCloudDebugLogger.disable(options);
248
+ return tinyCloudDebugLogger;
249
+ }
250
+ function getTinyCloudDebugLogs() {
251
+ return tinyCloudDebugLogger.getLogs();
252
+ }
253
+ function clearTinyCloudDebugLogs() {
254
+ tinyCloudDebugLogger.clear();
255
+ }
256
+ function installTinyCloudDebugGlobals() {
257
+ const global = getGlobal();
258
+ global.TinyCloudDebug = tinyCloudDebugLogger;
259
+ global.enableTinyCloudDebug = enableTinyCloudDebug;
260
+ global.disableTinyCloudDebug = disableTinyCloudDebug;
261
+ global.getTinyCloudDebugLogs = getTinyCloudDebugLogs;
262
+ global.clearTinyCloudDebugLogs = clearTinyCloudDebugLogs;
263
+ }
264
+ installTinyCloudDebugGlobals();
265
+
70
266
  // src/types.schema.ts
71
267
  import { z } from "zod";
72
268
  var ServiceErrorSchema = z.object({
@@ -308,9 +504,9 @@ var ServiceContext = class {
308
504
  this._services = /* @__PURE__ */ new Map();
309
505
  this._eventHandlers = /* @__PURE__ */ new Map();
310
506
  this._abortController = new AbortController();
311
- this._invoke = config.invoke;
312
- this._invokeAny = config.invokeAny;
313
- this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
507
+ this._invoke = this.wrapInvoke(config.invoke);
508
+ this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
509
+ this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
314
510
  this._hosts = config.hosts;
315
511
  this._session = config.session ?? null;
316
512
  this._retryPolicy = {
@@ -413,6 +609,7 @@ var ServiceContext = class {
413
609
  * @param data - Event data
414
610
  */
415
611
  emit(event, data) {
612
+ tinyCloudDebugLogger.log(event, data);
416
613
  if (this._telemetryEnabled && this._telemetryHandler) {
417
614
  try {
418
615
  this._telemetryHandler(event, data);
@@ -502,6 +699,77 @@ var ServiceContext = class {
502
699
  get retryPolicy() {
503
700
  return this._retryPolicy;
504
701
  }
702
+ wrapInvoke(invoke) {
703
+ return (session, service, path, action, facts) => {
704
+ if (!tinyCloudDebugLogger.isEnabled()) {
705
+ return invoke(session, service, path, action, facts);
706
+ }
707
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
708
+ service,
709
+ path,
710
+ action,
711
+ facts
712
+ });
713
+ try {
714
+ const result = invoke(session, service, path, action, facts);
715
+ timer.stop({ ok: true, service, path, action });
716
+ return result;
717
+ } catch (error) {
718
+ timer.stop({ ok: false, service, path, action, error });
719
+ throw error;
720
+ }
721
+ };
722
+ }
723
+ wrapInvokeAny(invokeAny) {
724
+ return (session, entries, facts) => {
725
+ if (!tinyCloudDebugLogger.isEnabled()) {
726
+ return invokeAny(session, entries, facts);
727
+ }
728
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
729
+ entries,
730
+ facts
731
+ });
732
+ try {
733
+ const result = invokeAny(session, entries, facts);
734
+ timer.stop({ ok: true, entries });
735
+ return result;
736
+ } catch (error) {
737
+ timer.stop({ ok: false, entries, error });
738
+ throw error;
739
+ }
740
+ };
741
+ }
742
+ wrapFetch(fetchFn) {
743
+ return async (url, init) => {
744
+ if (!tinyCloudDebugLogger.isEnabled()) {
745
+ return fetchFn(url, init);
746
+ }
747
+ const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", {
748
+ url,
749
+ method: init?.method ?? "GET",
750
+ init
751
+ });
752
+ try {
753
+ const response = await fetchFn(url, init);
754
+ timer.stop({
755
+ ok: response.ok,
756
+ url,
757
+ method: init?.method ?? "GET",
758
+ status: response.status,
759
+ statusText: response.statusText
760
+ });
761
+ return response;
762
+ } catch (error) {
763
+ timer.stop({
764
+ ok: false,
765
+ url,
766
+ method: init?.method ?? "GET",
767
+ error
768
+ });
769
+ throw error;
770
+ }
771
+ };
772
+ }
505
773
  };
506
774
 
507
775
  // src/errors.ts
@@ -1687,11 +1955,59 @@ var KVService = class extends BaseService {
1687
1955
  */
1688
1956
  KVService.serviceName = "kv";
1689
1957
 
1958
+ // src/responseErrors.ts
1959
+ function parseServiceErrorBody(errorText) {
1960
+ try {
1961
+ return JSON.parse(errorText);
1962
+ } catch {
1963
+ return {};
1964
+ }
1965
+ }
1966
+ function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
1967
+ if (parsed.message) {
1968
+ return compactMessage(parsed.message);
1969
+ }
1970
+ if (looksLikeHtml(errorText)) {
1971
+ if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
1972
+ return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
1973
+ }
1974
+ return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
1975
+ }
1976
+ return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
1977
+ }
1978
+ function responseErrorMeta(status, statusText, errorText) {
1979
+ const meta = { status, statusText };
1980
+ const snippet = compactMessage(errorText);
1981
+ if (snippet) {
1982
+ meta.responseSnippet = snippet.slice(0, 300);
1983
+ }
1984
+ return meta;
1985
+ }
1986
+ function looksLikeHtml(text) {
1987
+ return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
1988
+ }
1989
+ function compactMessage(message) {
1990
+ const compact = message.replace(/\s+/g, " ").trim();
1991
+ return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
1992
+ }
1993
+
1994
+ // src/sql/SQLMigrations.ts
1995
+ var SQLMigrations = class {
1996
+ constructor(service, dbName) {
1997
+ this.service = service;
1998
+ this.dbName = dbName;
1999
+ }
2000
+ apply(options) {
2001
+ return this.service.applyMigrationsOnDb(this.dbName, options);
2002
+ }
2003
+ };
2004
+
1690
2005
  // src/sql/DatabaseHandle.ts
1691
2006
  var DatabaseHandle = class {
1692
2007
  constructor(service, name) {
1693
2008
  this.service = service;
1694
2009
  this.name = name;
2010
+ this.migrations = new SQLMigrations(service, name);
1695
2011
  }
1696
2012
  async query(sql, params, options) {
1697
2013
  return this.service.queryOnDb(this.name, sql, params, options);
@@ -1714,7 +2030,7 @@ var DatabaseHandle = class {
1714
2030
  var SQLAction = {
1715
2031
  READ: "tinycloud.sql/read",
1716
2032
  WRITE: "tinycloud.sql/write",
1717
- DDL: "tinycloud.sql/ddl",
2033
+ SCHEMA: "tinycloud.sql/schema",
1718
2034
  ADMIN: "tinycloud.sql/admin",
1719
2035
  SELECT: "tinycloud.sql/select",
1720
2036
  INSERT: "tinycloud.sql/insert",
@@ -1727,9 +2043,20 @@ var SQLAction = {
1727
2043
 
1728
2044
  // src/sql/SQLService.ts
1729
2045
  var DDL_TOKENS = /* @__PURE__ */ new Set(["alter", "create", "drop"]);
2046
+ var MIGRATIONS_TABLE = "__tinycloud_sql_migrations";
2047
+ var MIGRATIONS_SCHEMA = `CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
2048
+ key TEXT PRIMARY KEY,
2049
+ namespace TEXT NOT NULL,
2050
+ id TEXT NOT NULL,
2051
+ applied_at TEXT NOT NULL,
2052
+ statement_count INTEGER NOT NULL
2053
+ )`;
2054
+ var MIGRATIONS_META_NAMESPACE = "tinycloud.sql.migrations";
2055
+ var MIGRATIONS_META_ID = "000_metadata";
1730
2056
  var SQLService = class extends BaseService {
1731
2057
  constructor(config = {}) {
1732
2058
  super();
2059
+ this.migrationLocks = /* @__PURE__ */ new Map();
1733
2060
  this._config = config;
1734
2061
  }
1735
2062
  get config() {
@@ -1765,6 +2092,24 @@ var SQLService = class extends BaseService {
1765
2092
  async batch(statements, options) {
1766
2093
  return this.batchOnDb(this.defaultDbName, statements, options);
1767
2094
  }
2095
+ async applyMigrationsOnDb(dbName, options) {
2096
+ const validationError = validateMigrationOptions(options);
2097
+ if (validationError) {
2098
+ return err(serviceError(ErrorCodes.INVALID_INPUT, validationError, "sql"));
2099
+ }
2100
+ const lockKey = `${dbName}\0${options.namespace}`;
2101
+ const existing = this.migrationLocks.get(lockKey);
2102
+ if (existing) {
2103
+ return existing;
2104
+ }
2105
+ const promise = this.applyMigrationsOnDbUnlocked(dbName, options);
2106
+ this.migrationLocks.set(lockKey, promise);
2107
+ try {
2108
+ return await promise;
2109
+ } finally {
2110
+ this.migrationLocks.delete(lockKey);
2111
+ }
2112
+ }
1768
2113
  // === Internal methods called by DatabaseHandle ===
1769
2114
  async queryOnDb(dbName, sql, params, options) {
1770
2115
  return this.withTelemetry("query", dbName, async () => {
@@ -1805,7 +2150,7 @@ var SQLService = class extends BaseService {
1805
2150
  const actions = [
1806
2151
  this.actionForSql(sql, SQLAction.WRITE),
1807
2152
  ...(options?.schema ?? []).map(
1808
- (statement) => this.actionForSql(statement, SQLAction.DDL)
2153
+ (statement) => this.actionForSql(statement, SQLAction.SCHEMA)
1809
2154
  )
1810
2155
  ];
1811
2156
  const response = await this.invokeSQL(
@@ -1895,6 +2240,78 @@ var SQLService = class extends BaseService {
1895
2240
  }
1896
2241
  });
1897
2242
  }
2243
+ async applyMigrationsOnDbUnlocked(dbName, options) {
2244
+ return this.withTelemetry("migrations.apply", dbName, async () => {
2245
+ const created = await this.ensureMigrationsTable(dbName, options.signal);
2246
+ if (!created.ok) return created;
2247
+ const listed = await this.queryOnDb(
2248
+ dbName,
2249
+ `SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
2250
+ [options.namespace],
2251
+ { signal: options.signal }
2252
+ );
2253
+ if (!listed.ok) return listed;
2254
+ const appliedIds = new Set(
2255
+ listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
2256
+ );
2257
+ const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
2258
+ const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
2259
+ const applied = [];
2260
+ for (const migration of pending) {
2261
+ const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
2262
+ if (!result.ok) return result;
2263
+ applied.push(migration.id);
2264
+ }
2265
+ return ok({
2266
+ database: dbName,
2267
+ namespace: options.namespace,
2268
+ status: applied.length > 0 ? "applied" : "already_current",
2269
+ applied,
2270
+ skipped
2271
+ });
2272
+ });
2273
+ }
2274
+ async applyOneMigration(dbName, namespace, migration, signal) {
2275
+ const statements = [
2276
+ ...migration.sql.map(
2277
+ (statement) => typeof statement === "string" ? { sql: statement } : statement
2278
+ ),
2279
+ {
2280
+ sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
2281
+ params: [
2282
+ migrationKey(namespace, migration.id),
2283
+ namespace,
2284
+ migration.id,
2285
+ (/* @__PURE__ */ new Date()).toISOString(),
2286
+ migration.sql.length
2287
+ ]
2288
+ }
2289
+ ];
2290
+ const result = await this.batchOnDb(dbName, statements, { signal });
2291
+ if (!result.ok) return result;
2292
+ return ok(void 0);
2293
+ }
2294
+ async ensureMigrationsTable(dbName, signal) {
2295
+ const result = await this.batchOnDb(
2296
+ dbName,
2297
+ [
2298
+ { sql: MIGRATIONS_SCHEMA },
2299
+ {
2300
+ sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
2301
+ params: [
2302
+ migrationKey(MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID),
2303
+ MIGRATIONS_META_NAMESPACE,
2304
+ MIGRATIONS_META_ID,
2305
+ (/* @__PURE__ */ new Date()).toISOString(),
2306
+ 1
2307
+ ]
2308
+ }
2309
+ ],
2310
+ { signal }
2311
+ );
2312
+ if (!result.ok) return result;
2313
+ return ok(void 0);
2314
+ }
1898
2315
  // === Private helpers ===
1899
2316
  async invokeSQL(dbName, actions, body, signal) {
1900
2317
  const session = this.context.session;
@@ -1913,7 +2330,7 @@ var SQLService = class extends BaseService {
1913
2330
  actionForSql(sql, fallback) {
1914
2331
  const token = firstSqlToken(sql);
1915
2332
  if (token === "pragma") return SQLAction.ADMIN;
1916
- if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.DDL;
2333
+ if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.SCHEMA;
1917
2334
  return fallback;
1918
2335
  }
1919
2336
  actionsForSqlBatch(statements) {
@@ -1942,17 +2359,19 @@ var SQLService = class extends BaseService {
1942
2359
  }
1943
2360
  async handleErrorResponse(response, operation) {
1944
2361
  const errorText = await response.text();
1945
- let errorBody = {};
1946
- try {
1947
- errorBody = JSON.parse(errorText);
1948
- } catch {
1949
- }
2362
+ const errorBody = parseServiceErrorBody(errorText);
1950
2363
  const errorCode = this.mapHttpStatusToErrorCode(
1951
2364
  response.status,
1952
2365
  errorBody.error
1953
2366
  );
1954
- const message = errorBody.message || `SQL ${operation} failed: ${response.status} - ${errorText}`;
1955
- const meta = { status: response.status, statusText: response.statusText };
2367
+ const message = formatServiceResponseError(
2368
+ "SQL",
2369
+ operation,
2370
+ response.status,
2371
+ errorText,
2372
+ errorBody
2373
+ );
2374
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
1956
2375
  if (response.status === 401) {
1957
2376
  const { resource, action } = parseAuthError(errorText);
1958
2377
  if (action) meta.requiredAction = action;
@@ -2012,6 +2431,53 @@ function firstSqlToken(sql) {
2012
2431
  const match = /^[A-Za-z_]+/.exec(sql.slice(index));
2013
2432
  return match?.[0].toLowerCase();
2014
2433
  }
2434
+ function validateMigrationOptions(options) {
2435
+ if (!options || typeof options !== "object") {
2436
+ return "SQL migrations require options";
2437
+ }
2438
+ if (!options.namespace || typeof options.namespace !== "string") {
2439
+ return "SQL migrations require a non-empty namespace";
2440
+ }
2441
+ if (!Array.isArray(options.migrations)) {
2442
+ return "SQL migrations require an ordered migrations array";
2443
+ }
2444
+ const ids = /* @__PURE__ */ new Set();
2445
+ for (const migration of options.migrations) {
2446
+ if (!migration.id || typeof migration.id !== "string") {
2447
+ return "SQL migrations require every migration to have a non-empty id";
2448
+ }
2449
+ if (ids.has(migration.id)) {
2450
+ return `Duplicate SQL migration id: ${migration.id}`;
2451
+ }
2452
+ ids.add(migration.id);
2453
+ if (!Array.isArray(migration.sql)) {
2454
+ return `SQL migration ${migration.id} requires a sql array`;
2455
+ }
2456
+ for (const statement of migration.sql) {
2457
+ if (typeof statement === "string") {
2458
+ if (statement.trim() === "") {
2459
+ return `SQL migration ${migration.id} contains an empty statement`;
2460
+ }
2461
+ continue;
2462
+ }
2463
+ if (!statement || typeof statement.sql !== "string" || statement.sql.trim() === "") {
2464
+ return `SQL migration ${migration.id} contains an invalid statement`;
2465
+ }
2466
+ }
2467
+ }
2468
+ return null;
2469
+ }
2470
+ function migrationKey(namespace, id) {
2471
+ return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
2472
+ }
2473
+ function rowValue(row, index) {
2474
+ if (Array.isArray(row)) return row[index];
2475
+ if (row && typeof row === "object") {
2476
+ const values = Object.values(row);
2477
+ return values[index];
2478
+ }
2479
+ return void 0;
2480
+ }
2015
2481
 
2016
2482
  // src/duckdb/DuckDbDatabaseHandle.ts
2017
2483
  var DuckDbDatabaseHandle = class {
@@ -2321,17 +2787,19 @@ var DuckDbService = class extends BaseService {
2321
2787
  }
2322
2788
  async handleErrorResponse(response, operation) {
2323
2789
  const errorText = await response.text();
2324
- let errorBody = {};
2325
- try {
2326
- errorBody = JSON.parse(errorText);
2327
- } catch {
2328
- }
2790
+ const errorBody = parseServiceErrorBody(errorText);
2329
2791
  const errorCode = this.mapHttpStatusToErrorCode(
2330
2792
  response.status,
2331
2793
  errorBody.error
2332
2794
  );
2333
- const message = errorBody.message || `DuckDB ${operation} failed: ${response.status} - ${errorText}`;
2334
- const meta = { status: response.status, statusText: response.statusText };
2795
+ const message = formatServiceResponseError(
2796
+ "DuckDB",
2797
+ operation,
2798
+ response.status,
2799
+ errorText,
2800
+ errorBody
2801
+ );
2802
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
2335
2803
  if (response.status === 401) {
2336
2804
  const { resource, action } = parseAuthError(errorText);
2337
2805
  if (action) meta.requiredAction = action;
@@ -5679,6 +6147,7 @@ export {
5679
6147
  RetryPolicySchema,
5680
6148
  SECRET_NAME_RE,
5681
6149
  SQLAction,
6150
+ SQLMigrations,
5682
6151
  SQLService,
5683
6152
  SecretsService,
5684
6153
  ServiceContext,
@@ -5690,6 +6159,7 @@ export {
5690
6159
  ServiceSessionSchema,
5691
6160
  TelemetryEvents,
5692
6161
  TelemetrySpanEventSchema,
6162
+ TinyCloudDebugLogger,
5693
6163
  TinyCloudQuota,
5694
6164
  VaultHeaders,
5695
6165
  VaultPublicSpaceKVActions,
@@ -5709,21 +6179,26 @@ export {
5709
6179
  canonicalize as canonicalizeEncryptionJson,
5710
6180
  canonicalizeSecretScope,
5711
6181
  checkDecryptInvocationInput,
6182
+ clearTinyCloudDebugLogs,
5712
6183
  createKVResponseSchema,
5713
6184
  createResultSchema,
5714
6185
  createVaultCrypto,
5715
6186
  decryptEnvelopeWithKey,
5716
6187
  defaultRetryPolicy,
5717
6188
  deriveSignedReceiverKey,
6189
+ disableTinyCloudDebug,
5718
6190
  discoverNetwork,
6191
+ enableTinyCloudDebug,
5719
6192
  encryptToNetwork,
5720
6193
  encryptionError,
5721
6194
  ensureNetworkUsableForDecrypt,
5722
6195
  err,
5723
6196
  errorResult,
5724
6197
  generateRandomReceiverKey,
6198
+ getTinyCloudDebugLogs,
5725
6199
  hexDecode,
5726
6200
  hexEncode2 as hexEncode,
6201
+ installTinyCloudDebugGlobals,
5727
6202
  isNetworkId,
5728
6203
  networkDiscoveryKey,
5729
6204
  networkError,
@@ -5739,6 +6214,7 @@ export {
5739
6214
  storageLimitReachedError,
5740
6215
  storageQuotaExceededError,
5741
6216
  timeoutError,
6217
+ tinyCloudDebugLogger,
5742
6218
  utf8Decode,
5743
6219
  utf8Encode,
5744
6220
  validateEnvelope,