@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.cjs CHANGED
@@ -52,6 +52,7 @@ __export(index_exports, {
52
52
  RetryPolicySchema: () => RetryPolicySchema,
53
53
  SECRET_NAME_RE: () => SECRET_NAME_RE,
54
54
  SQLAction: () => SQLAction,
55
+ SQLMigrations: () => SQLMigrations,
55
56
  SQLService: () => SQLService,
56
57
  SecretsService: () => SecretsService,
57
58
  ServiceContext: () => ServiceContext,
@@ -63,6 +64,7 @@ __export(index_exports, {
63
64
  ServiceSessionSchema: () => ServiceSessionSchema,
64
65
  TelemetryEvents: () => TelemetryEvents,
65
66
  TelemetrySpanEventSchema: () => TelemetrySpanEventSchema,
67
+ TinyCloudDebugLogger: () => TinyCloudDebugLogger,
66
68
  TinyCloudQuota: () => TinyCloudQuota,
67
69
  VaultHeaders: () => VaultHeaders,
68
70
  VaultPublicSpaceKVActions: () => VaultPublicSpaceKVActions,
@@ -82,21 +84,26 @@ __export(index_exports, {
82
84
  canonicalizeEncryptionJson: () => canonicalize,
83
85
  canonicalizeSecretScope: () => canonicalizeSecretScope,
84
86
  checkDecryptInvocationInput: () => checkDecryptInvocationInput,
87
+ clearTinyCloudDebugLogs: () => clearTinyCloudDebugLogs,
85
88
  createKVResponseSchema: () => createKVResponseSchema,
86
89
  createResultSchema: () => createResultSchema,
87
90
  createVaultCrypto: () => createVaultCrypto,
88
91
  decryptEnvelopeWithKey: () => decryptEnvelopeWithKey,
89
92
  defaultRetryPolicy: () => defaultRetryPolicy,
90
93
  deriveSignedReceiverKey: () => deriveSignedReceiverKey,
94
+ disableTinyCloudDebug: () => disableTinyCloudDebug,
91
95
  discoverNetwork: () => discoverNetwork,
96
+ enableTinyCloudDebug: () => enableTinyCloudDebug,
92
97
  encryptToNetwork: () => encryptToNetwork,
93
98
  encryptionError: () => encryptionError,
94
99
  ensureNetworkUsableForDecrypt: () => ensureNetworkUsableForDecrypt,
95
100
  err: () => err,
96
101
  errorResult: () => errorResult,
97
102
  generateRandomReceiverKey: () => generateRandomReceiverKey,
103
+ getTinyCloudDebugLogs: () => getTinyCloudDebugLogs,
98
104
  hexDecode: () => hexDecode,
99
105
  hexEncode: () => hexEncode2,
106
+ installTinyCloudDebugGlobals: () => installTinyCloudDebugGlobals,
100
107
  isNetworkId: () => isNetworkId,
101
108
  networkDiscoveryKey: () => networkDiscoveryKey,
102
109
  networkError: () => networkError,
@@ -112,6 +119,7 @@ __export(index_exports, {
112
119
  storageLimitReachedError: () => storageLimitReachedError,
113
120
  storageQuotaExceededError: () => storageQuotaExceededError,
114
121
  timeoutError: () => timeoutError,
122
+ tinyCloudDebugLogger: () => tinyCloudDebugLogger,
115
123
  utf8Decode: () => utf8Decode,
116
124
  utf8Encode: () => utf8Encode,
117
125
  validateEnvelope: () => validateEnvelope,
@@ -197,6 +205,202 @@ function serviceError(code, message, service, options) {
197
205
  };
198
206
  }
199
207
 
208
+ // src/debug.ts
209
+ var DEBUG_FLAG = "TinyCloud_debug";
210
+ var MAX_EVENTS = 1e3;
211
+ function getGlobal() {
212
+ return globalThis;
213
+ }
214
+ function nowMs() {
215
+ const performanceNow = globalThis.performance?.now?.bind(globalThis.performance);
216
+ return typeof performanceNow === "function" ? performanceNow() : Date.now();
217
+ }
218
+ function isTrue(value) {
219
+ return value === true || value === "true" || value === "1";
220
+ }
221
+ function getProcessDebugFlag() {
222
+ const processLike = globalThis.process;
223
+ return processLike?.env?.[DEBUG_FLAG];
224
+ }
225
+ function isBrowserWindow() {
226
+ const global = getGlobal();
227
+ return global.window === globalThis;
228
+ }
229
+ function getLocalStorage() {
230
+ if (!isBrowserWindow()) {
231
+ return void 0;
232
+ }
233
+ try {
234
+ return globalThis.localStorage;
235
+ } catch {
236
+ return void 0;
237
+ }
238
+ }
239
+ function getStoredDebugFlag() {
240
+ const storage = getLocalStorage();
241
+ if (!storage) {
242
+ return void 0;
243
+ }
244
+ try {
245
+ return storage.getItem(DEBUG_FLAG);
246
+ } catch {
247
+ return void 0;
248
+ }
249
+ }
250
+ function setStoredDebugFlag(enabled) {
251
+ const storage = getLocalStorage();
252
+ if (!storage) {
253
+ return;
254
+ }
255
+ try {
256
+ if (enabled) {
257
+ storage.setItem(DEBUG_FLAG, "true");
258
+ } else {
259
+ storage.removeItem(DEBUG_FLAG);
260
+ }
261
+ } catch {
262
+ }
263
+ }
264
+ function shouldStartEnabled() {
265
+ const global = getGlobal();
266
+ return isTrue(global[DEBUG_FLAG]) || isTrue(getStoredDebugFlag()) || isTrue(getProcessDebugFlag());
267
+ }
268
+ var TinyCloudDebugLogger = class {
269
+ constructor() {
270
+ this.enabled = shouldStartEnabled();
271
+ this.sequence = 0;
272
+ this.events = [];
273
+ this.maxEvents = MAX_EVENTS;
274
+ }
275
+ isEnabled() {
276
+ return this.enabled || isTrue(getGlobal()[DEBUG_FLAG]) || isTrue(getProcessDebugFlag());
277
+ }
278
+ enable(options = {}) {
279
+ this.enabled = true;
280
+ getGlobal()[DEBUG_FLAG] = true;
281
+ if (options.persist !== false) {
282
+ setStoredDebugFlag(true);
283
+ }
284
+ this.log("debug.enabled", { persisted: options.persist !== false });
285
+ }
286
+ disable(options = {}) {
287
+ this.log("debug.disabled", { persisted: options.persist !== false });
288
+ this.enabled = false;
289
+ getGlobal()[DEBUG_FLAG] = false;
290
+ if (options.persist !== false) {
291
+ setStoredDebugFlag(false);
292
+ }
293
+ }
294
+ clear() {
295
+ this.events = [];
296
+ }
297
+ getLogs() {
298
+ return [...this.events];
299
+ }
300
+ log(event, data, message) {
301
+ if (!this.isEnabled()) {
302
+ return void 0;
303
+ }
304
+ return this.record({
305
+ event,
306
+ data,
307
+ message,
308
+ level: "debug",
309
+ timestamp: Date.now(),
310
+ timestampIso: (/* @__PURE__ */ new Date()).toISOString()
311
+ });
312
+ }
313
+ startTimer(event, data) {
314
+ if (!this.isEnabled()) {
315
+ return { stop: () => void 0 };
316
+ }
317
+ const startedAt = nowMs();
318
+ this.log(`${event}.start`, data);
319
+ return {
320
+ stop: (finishData) => {
321
+ const endedAt = nowMs();
322
+ return this.record({
323
+ event: `${event}.end`,
324
+ data: finishData,
325
+ durationMs: endedAt - startedAt,
326
+ startedAt,
327
+ endedAt,
328
+ level: "debug",
329
+ timestamp: Date.now(),
330
+ timestampIso: (/* @__PURE__ */ new Date()).toISOString()
331
+ });
332
+ }
333
+ };
334
+ }
335
+ async timeAsync(event, operation, data) {
336
+ if (!this.isEnabled()) {
337
+ return operation();
338
+ }
339
+ const timer = this.startTimer(event, data);
340
+ try {
341
+ const result = await operation();
342
+ timer.stop({ ok: true });
343
+ return result;
344
+ } catch (error) {
345
+ timer.stop({ ok: false, error });
346
+ throw error;
347
+ }
348
+ }
349
+ time(event, operation, data) {
350
+ if (!this.isEnabled()) {
351
+ return operation();
352
+ }
353
+ const timer = this.startTimer(event, data);
354
+ try {
355
+ const result = operation();
356
+ timer.stop({ ok: true });
357
+ return result;
358
+ } catch (error) {
359
+ timer.stop({ ok: false, error });
360
+ throw error;
361
+ }
362
+ }
363
+ record(event) {
364
+ const debugEvent = {
365
+ ...event,
366
+ sequence: ++this.sequence
367
+ };
368
+ this.events.push(debugEvent);
369
+ if (this.events.length > this.maxEvents) {
370
+ this.events.splice(0, this.events.length - this.maxEvents);
371
+ }
372
+ try {
373
+ globalThis.console?.debug?.("[TinyCloud]", debugEvent.event, debugEvent);
374
+ } catch {
375
+ }
376
+ return debugEvent;
377
+ }
378
+ };
379
+ var tinyCloudDebugLogger = new TinyCloudDebugLogger();
380
+ function enableTinyCloudDebug(options) {
381
+ tinyCloudDebugLogger.enable(options);
382
+ return tinyCloudDebugLogger;
383
+ }
384
+ function disableTinyCloudDebug(options) {
385
+ tinyCloudDebugLogger.disable(options);
386
+ return tinyCloudDebugLogger;
387
+ }
388
+ function getTinyCloudDebugLogs() {
389
+ return tinyCloudDebugLogger.getLogs();
390
+ }
391
+ function clearTinyCloudDebugLogs() {
392
+ tinyCloudDebugLogger.clear();
393
+ }
394
+ function installTinyCloudDebugGlobals() {
395
+ const global = getGlobal();
396
+ global.TinyCloudDebug = tinyCloudDebugLogger;
397
+ global.enableTinyCloudDebug = enableTinyCloudDebug;
398
+ global.disableTinyCloudDebug = disableTinyCloudDebug;
399
+ global.getTinyCloudDebugLogs = getTinyCloudDebugLogs;
400
+ global.clearTinyCloudDebugLogs = clearTinyCloudDebugLogs;
401
+ }
402
+ installTinyCloudDebugGlobals();
403
+
200
404
  // src/types.schema.ts
201
405
  var import_zod = require("zod");
202
406
  var ServiceErrorSchema = import_zod.z.object({
@@ -438,9 +642,9 @@ var ServiceContext = class {
438
642
  this._services = /* @__PURE__ */ new Map();
439
643
  this._eventHandlers = /* @__PURE__ */ new Map();
440
644
  this._abortController = new AbortController();
441
- this._invoke = config.invoke;
442
- this._invokeAny = config.invokeAny;
443
- this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
645
+ this._invoke = this.wrapInvoke(config.invoke);
646
+ this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
647
+ this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
444
648
  this._hosts = config.hosts;
445
649
  this._session = config.session ?? null;
446
650
  this._retryPolicy = {
@@ -543,6 +747,7 @@ var ServiceContext = class {
543
747
  * @param data - Event data
544
748
  */
545
749
  emit(event, data) {
750
+ tinyCloudDebugLogger.log(event, data);
546
751
  if (this._telemetryEnabled && this._telemetryHandler) {
547
752
  try {
548
753
  this._telemetryHandler(event, data);
@@ -632,6 +837,77 @@ var ServiceContext = class {
632
837
  get retryPolicy() {
633
838
  return this._retryPolicy;
634
839
  }
840
+ wrapInvoke(invoke) {
841
+ return (session, service, path, action, facts) => {
842
+ if (!tinyCloudDebugLogger.isEnabled()) {
843
+ return invoke(session, service, path, action, facts);
844
+ }
845
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
846
+ service,
847
+ path,
848
+ action,
849
+ facts
850
+ });
851
+ try {
852
+ const result = invoke(session, service, path, action, facts);
853
+ timer.stop({ ok: true, service, path, action });
854
+ return result;
855
+ } catch (error) {
856
+ timer.stop({ ok: false, service, path, action, error });
857
+ throw error;
858
+ }
859
+ };
860
+ }
861
+ wrapInvokeAny(invokeAny) {
862
+ return (session, entries, facts) => {
863
+ if (!tinyCloudDebugLogger.isEnabled()) {
864
+ return invokeAny(session, entries, facts);
865
+ }
866
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
867
+ entries,
868
+ facts
869
+ });
870
+ try {
871
+ const result = invokeAny(session, entries, facts);
872
+ timer.stop({ ok: true, entries });
873
+ return result;
874
+ } catch (error) {
875
+ timer.stop({ ok: false, entries, error });
876
+ throw error;
877
+ }
878
+ };
879
+ }
880
+ wrapFetch(fetchFn) {
881
+ return async (url, init) => {
882
+ if (!tinyCloudDebugLogger.isEnabled()) {
883
+ return fetchFn(url, init);
884
+ }
885
+ const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", {
886
+ url,
887
+ method: init?.method ?? "GET",
888
+ init
889
+ });
890
+ try {
891
+ const response = await fetchFn(url, init);
892
+ timer.stop({
893
+ ok: response.ok,
894
+ url,
895
+ method: init?.method ?? "GET",
896
+ status: response.status,
897
+ statusText: response.statusText
898
+ });
899
+ return response;
900
+ } catch (error) {
901
+ timer.stop({
902
+ ok: false,
903
+ url,
904
+ method: init?.method ?? "GET",
905
+ error
906
+ });
907
+ throw error;
908
+ }
909
+ };
910
+ }
635
911
  };
636
912
 
637
913
  // src/errors.ts
@@ -1817,11 +2093,59 @@ var KVService = class extends BaseService {
1817
2093
  */
1818
2094
  KVService.serviceName = "kv";
1819
2095
 
2096
+ // src/responseErrors.ts
2097
+ function parseServiceErrorBody(errorText) {
2098
+ try {
2099
+ return JSON.parse(errorText);
2100
+ } catch {
2101
+ return {};
2102
+ }
2103
+ }
2104
+ function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
2105
+ if (parsed.message) {
2106
+ return compactMessage(parsed.message);
2107
+ }
2108
+ if (looksLikeHtml(errorText)) {
2109
+ if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
2110
+ return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
2111
+ }
2112
+ return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
2113
+ }
2114
+ return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
2115
+ }
2116
+ function responseErrorMeta(status, statusText, errorText) {
2117
+ const meta = { status, statusText };
2118
+ const snippet = compactMessage(errorText);
2119
+ if (snippet) {
2120
+ meta.responseSnippet = snippet.slice(0, 300);
2121
+ }
2122
+ return meta;
2123
+ }
2124
+ function looksLikeHtml(text) {
2125
+ return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
2126
+ }
2127
+ function compactMessage(message) {
2128
+ const compact = message.replace(/\s+/g, " ").trim();
2129
+ return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
2130
+ }
2131
+
2132
+ // src/sql/SQLMigrations.ts
2133
+ var SQLMigrations = class {
2134
+ constructor(service, dbName) {
2135
+ this.service = service;
2136
+ this.dbName = dbName;
2137
+ }
2138
+ apply(options) {
2139
+ return this.service.applyMigrationsOnDb(this.dbName, options);
2140
+ }
2141
+ };
2142
+
1820
2143
  // src/sql/DatabaseHandle.ts
1821
2144
  var DatabaseHandle = class {
1822
2145
  constructor(service, name) {
1823
2146
  this.service = service;
1824
2147
  this.name = name;
2148
+ this.migrations = new SQLMigrations(service, name);
1825
2149
  }
1826
2150
  async query(sql, params, options) {
1827
2151
  return this.service.queryOnDb(this.name, sql, params, options);
@@ -1844,7 +2168,7 @@ var DatabaseHandle = class {
1844
2168
  var SQLAction = {
1845
2169
  READ: "tinycloud.sql/read",
1846
2170
  WRITE: "tinycloud.sql/write",
1847
- DDL: "tinycloud.sql/ddl",
2171
+ SCHEMA: "tinycloud.sql/schema",
1848
2172
  ADMIN: "tinycloud.sql/admin",
1849
2173
  SELECT: "tinycloud.sql/select",
1850
2174
  INSERT: "tinycloud.sql/insert",
@@ -1857,9 +2181,20 @@ var SQLAction = {
1857
2181
 
1858
2182
  // src/sql/SQLService.ts
1859
2183
  var DDL_TOKENS = /* @__PURE__ */ new Set(["alter", "create", "drop"]);
2184
+ var MIGRATIONS_TABLE = "__tinycloud_sql_migrations";
2185
+ var MIGRATIONS_SCHEMA = `CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
2186
+ key TEXT PRIMARY KEY,
2187
+ namespace TEXT NOT NULL,
2188
+ id TEXT NOT NULL,
2189
+ applied_at TEXT NOT NULL,
2190
+ statement_count INTEGER NOT NULL
2191
+ )`;
2192
+ var MIGRATIONS_META_NAMESPACE = "tinycloud.sql.migrations";
2193
+ var MIGRATIONS_META_ID = "000_metadata";
1860
2194
  var SQLService = class extends BaseService {
1861
2195
  constructor(config = {}) {
1862
2196
  super();
2197
+ this.migrationLocks = /* @__PURE__ */ new Map();
1863
2198
  this._config = config;
1864
2199
  }
1865
2200
  get config() {
@@ -1895,6 +2230,24 @@ var SQLService = class extends BaseService {
1895
2230
  async batch(statements, options) {
1896
2231
  return this.batchOnDb(this.defaultDbName, statements, options);
1897
2232
  }
2233
+ async applyMigrationsOnDb(dbName, options) {
2234
+ const validationError = validateMigrationOptions(options);
2235
+ if (validationError) {
2236
+ return err(serviceError(ErrorCodes.INVALID_INPUT, validationError, "sql"));
2237
+ }
2238
+ const lockKey = `${dbName}\0${options.namespace}`;
2239
+ const existing = this.migrationLocks.get(lockKey);
2240
+ if (existing) {
2241
+ return existing;
2242
+ }
2243
+ const promise = this.applyMigrationsOnDbUnlocked(dbName, options);
2244
+ this.migrationLocks.set(lockKey, promise);
2245
+ try {
2246
+ return await promise;
2247
+ } finally {
2248
+ this.migrationLocks.delete(lockKey);
2249
+ }
2250
+ }
1898
2251
  // === Internal methods called by DatabaseHandle ===
1899
2252
  async queryOnDb(dbName, sql, params, options) {
1900
2253
  return this.withTelemetry("query", dbName, async () => {
@@ -1935,7 +2288,7 @@ var SQLService = class extends BaseService {
1935
2288
  const actions = [
1936
2289
  this.actionForSql(sql, SQLAction.WRITE),
1937
2290
  ...(options?.schema ?? []).map(
1938
- (statement) => this.actionForSql(statement, SQLAction.DDL)
2291
+ (statement) => this.actionForSql(statement, SQLAction.SCHEMA)
1939
2292
  )
1940
2293
  ];
1941
2294
  const response = await this.invokeSQL(
@@ -2025,6 +2378,78 @@ var SQLService = class extends BaseService {
2025
2378
  }
2026
2379
  });
2027
2380
  }
2381
+ async applyMigrationsOnDbUnlocked(dbName, options) {
2382
+ return this.withTelemetry("migrations.apply", dbName, async () => {
2383
+ const created = await this.ensureMigrationsTable(dbName, options.signal);
2384
+ if (!created.ok) return created;
2385
+ const listed = await this.queryOnDb(
2386
+ dbName,
2387
+ `SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
2388
+ [options.namespace],
2389
+ { signal: options.signal }
2390
+ );
2391
+ if (!listed.ok) return listed;
2392
+ const appliedIds = new Set(
2393
+ listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
2394
+ );
2395
+ const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
2396
+ const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
2397
+ const applied = [];
2398
+ for (const migration of pending) {
2399
+ const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
2400
+ if (!result.ok) return result;
2401
+ applied.push(migration.id);
2402
+ }
2403
+ return ok({
2404
+ database: dbName,
2405
+ namespace: options.namespace,
2406
+ status: applied.length > 0 ? "applied" : "already_current",
2407
+ applied,
2408
+ skipped
2409
+ });
2410
+ });
2411
+ }
2412
+ async applyOneMigration(dbName, namespace, migration, signal) {
2413
+ const statements = [
2414
+ ...migration.sql.map(
2415
+ (statement) => typeof statement === "string" ? { sql: statement } : statement
2416
+ ),
2417
+ {
2418
+ sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
2419
+ params: [
2420
+ migrationKey(namespace, migration.id),
2421
+ namespace,
2422
+ migration.id,
2423
+ (/* @__PURE__ */ new Date()).toISOString(),
2424
+ migration.sql.length
2425
+ ]
2426
+ }
2427
+ ];
2428
+ const result = await this.batchOnDb(dbName, statements, { signal });
2429
+ if (!result.ok) return result;
2430
+ return ok(void 0);
2431
+ }
2432
+ async ensureMigrationsTable(dbName, signal) {
2433
+ const result = await this.batchOnDb(
2434
+ dbName,
2435
+ [
2436
+ { sql: MIGRATIONS_SCHEMA },
2437
+ {
2438
+ sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
2439
+ params: [
2440
+ migrationKey(MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID),
2441
+ MIGRATIONS_META_NAMESPACE,
2442
+ MIGRATIONS_META_ID,
2443
+ (/* @__PURE__ */ new Date()).toISOString(),
2444
+ 1
2445
+ ]
2446
+ }
2447
+ ],
2448
+ { signal }
2449
+ );
2450
+ if (!result.ok) return result;
2451
+ return ok(void 0);
2452
+ }
2028
2453
  // === Private helpers ===
2029
2454
  async invokeSQL(dbName, actions, body, signal) {
2030
2455
  const session = this.context.session;
@@ -2043,7 +2468,7 @@ var SQLService = class extends BaseService {
2043
2468
  actionForSql(sql, fallback) {
2044
2469
  const token = firstSqlToken(sql);
2045
2470
  if (token === "pragma") return SQLAction.ADMIN;
2046
- if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.DDL;
2471
+ if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.SCHEMA;
2047
2472
  return fallback;
2048
2473
  }
2049
2474
  actionsForSqlBatch(statements) {
@@ -2072,17 +2497,19 @@ var SQLService = class extends BaseService {
2072
2497
  }
2073
2498
  async handleErrorResponse(response, operation) {
2074
2499
  const errorText = await response.text();
2075
- let errorBody = {};
2076
- try {
2077
- errorBody = JSON.parse(errorText);
2078
- } catch {
2079
- }
2500
+ const errorBody = parseServiceErrorBody(errorText);
2080
2501
  const errorCode = this.mapHttpStatusToErrorCode(
2081
2502
  response.status,
2082
2503
  errorBody.error
2083
2504
  );
2084
- const message = errorBody.message || `SQL ${operation} failed: ${response.status} - ${errorText}`;
2085
- const meta = { status: response.status, statusText: response.statusText };
2505
+ const message = formatServiceResponseError(
2506
+ "SQL",
2507
+ operation,
2508
+ response.status,
2509
+ errorText,
2510
+ errorBody
2511
+ );
2512
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
2086
2513
  if (response.status === 401) {
2087
2514
  const { resource, action } = parseAuthError(errorText);
2088
2515
  if (action) meta.requiredAction = action;
@@ -2142,6 +2569,53 @@ function firstSqlToken(sql) {
2142
2569
  const match = /^[A-Za-z_]+/.exec(sql.slice(index));
2143
2570
  return match?.[0].toLowerCase();
2144
2571
  }
2572
+ function validateMigrationOptions(options) {
2573
+ if (!options || typeof options !== "object") {
2574
+ return "SQL migrations require options";
2575
+ }
2576
+ if (!options.namespace || typeof options.namespace !== "string") {
2577
+ return "SQL migrations require a non-empty namespace";
2578
+ }
2579
+ if (!Array.isArray(options.migrations)) {
2580
+ return "SQL migrations require an ordered migrations array";
2581
+ }
2582
+ const ids = /* @__PURE__ */ new Set();
2583
+ for (const migration of options.migrations) {
2584
+ if (!migration.id || typeof migration.id !== "string") {
2585
+ return "SQL migrations require every migration to have a non-empty id";
2586
+ }
2587
+ if (ids.has(migration.id)) {
2588
+ return `Duplicate SQL migration id: ${migration.id}`;
2589
+ }
2590
+ ids.add(migration.id);
2591
+ if (!Array.isArray(migration.sql)) {
2592
+ return `SQL migration ${migration.id} requires a sql array`;
2593
+ }
2594
+ for (const statement of migration.sql) {
2595
+ if (typeof statement === "string") {
2596
+ if (statement.trim() === "") {
2597
+ return `SQL migration ${migration.id} contains an empty statement`;
2598
+ }
2599
+ continue;
2600
+ }
2601
+ if (!statement || typeof statement.sql !== "string" || statement.sql.trim() === "") {
2602
+ return `SQL migration ${migration.id} contains an invalid statement`;
2603
+ }
2604
+ }
2605
+ }
2606
+ return null;
2607
+ }
2608
+ function migrationKey(namespace, id) {
2609
+ return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
2610
+ }
2611
+ function rowValue(row, index) {
2612
+ if (Array.isArray(row)) return row[index];
2613
+ if (row && typeof row === "object") {
2614
+ const values = Object.values(row);
2615
+ return values[index];
2616
+ }
2617
+ return void 0;
2618
+ }
2145
2619
 
2146
2620
  // src/duckdb/DuckDbDatabaseHandle.ts
2147
2621
  var DuckDbDatabaseHandle = class {
@@ -2451,17 +2925,19 @@ var DuckDbService = class extends BaseService {
2451
2925
  }
2452
2926
  async handleErrorResponse(response, operation) {
2453
2927
  const errorText = await response.text();
2454
- let errorBody = {};
2455
- try {
2456
- errorBody = JSON.parse(errorText);
2457
- } catch {
2458
- }
2928
+ const errorBody = parseServiceErrorBody(errorText);
2459
2929
  const errorCode = this.mapHttpStatusToErrorCode(
2460
2930
  response.status,
2461
2931
  errorBody.error
2462
2932
  );
2463
- const message = errorBody.message || `DuckDB ${operation} failed: ${response.status} - ${errorText}`;
2464
- const meta = { status: response.status, statusText: response.statusText };
2933
+ const message = formatServiceResponseError(
2934
+ "DuckDB",
2935
+ operation,
2936
+ response.status,
2937
+ errorText,
2938
+ errorBody
2939
+ );
2940
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
2465
2941
  if (response.status === 401) {
2466
2942
  const { resource, action } = parseAuthError(errorText);
2467
2943
  if (action) meta.requiredAction = action;
@@ -5810,6 +6286,7 @@ EncryptionService.serviceName = "encryption";
5810
6286
  RetryPolicySchema,
5811
6287
  SECRET_NAME_RE,
5812
6288
  SQLAction,
6289
+ SQLMigrations,
5813
6290
  SQLService,
5814
6291
  SecretsService,
5815
6292
  ServiceContext,
@@ -5821,6 +6298,7 @@ EncryptionService.serviceName = "encryption";
5821
6298
  ServiceSessionSchema,
5822
6299
  TelemetryEvents,
5823
6300
  TelemetrySpanEventSchema,
6301
+ TinyCloudDebugLogger,
5824
6302
  TinyCloudQuota,
5825
6303
  VaultHeaders,
5826
6304
  VaultPublicSpaceKVActions,
@@ -5840,21 +6318,26 @@ EncryptionService.serviceName = "encryption";
5840
6318
  canonicalizeEncryptionJson,
5841
6319
  canonicalizeSecretScope,
5842
6320
  checkDecryptInvocationInput,
6321
+ clearTinyCloudDebugLogs,
5843
6322
  createKVResponseSchema,
5844
6323
  createResultSchema,
5845
6324
  createVaultCrypto,
5846
6325
  decryptEnvelopeWithKey,
5847
6326
  defaultRetryPolicy,
5848
6327
  deriveSignedReceiverKey,
6328
+ disableTinyCloudDebug,
5849
6329
  discoverNetwork,
6330
+ enableTinyCloudDebug,
5850
6331
  encryptToNetwork,
5851
6332
  encryptionError,
5852
6333
  ensureNetworkUsableForDecrypt,
5853
6334
  err,
5854
6335
  errorResult,
5855
6336
  generateRandomReceiverKey,
6337
+ getTinyCloudDebugLogs,
5856
6338
  hexDecode,
5857
6339
  hexEncode,
6340
+ installTinyCloudDebugGlobals,
5858
6341
  isNetworkId,
5859
6342
  networkDiscoveryKey,
5860
6343
  networkError,
@@ -5870,6 +6353,7 @@ EncryptionService.serviceName = "encryption";
5870
6353
  storageLimitReachedError,
5871
6354
  storageQuotaExceededError,
5872
6355
  timeoutError,
6356
+ tinyCloudDebugLogger,
5873
6357
  utf8Decode,
5874
6358
  utf8Encode,
5875
6359
  validateEnvelope,