@tinycloud/sdk-services 2.3.0-beta.6 → 2.3.0-beta.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.
@@ -71,6 +71,7 @@ var defaultRetryPolicy = {
71
71
  retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT]
72
72
  };
73
73
  var TelemetryEvents = {
74
+ SPAN: "telemetry.span",
74
75
  SERVICE_REQUEST: "service.request",
75
76
  SERVICE_RESPONSE: "service.response",
76
77
  SERVICE_ERROR: "service.error",
@@ -237,9 +238,11 @@ var BaseService = class {
237
238
  * @param key - Optional key/path being accessed
238
239
  */
239
240
  emitRequest(action, key) {
241
+ const service = this.getServiceName();
240
242
  this.emit(TelemetryEvents.SERVICE_REQUEST, {
241
- service: this.getServiceName(),
243
+ service,
242
244
  action,
245
+ span: this.spanName(action),
243
246
  key,
244
247
  timestamp: Date.now()
245
248
  });
@@ -253,11 +256,24 @@ var BaseService = class {
253
256
  * @param status - Optional HTTP status code
254
257
  */
255
258
  emitResponse(action, ok2, startTime, status) {
259
+ const service = this.getServiceName();
260
+ const durationMs = Date.now() - startTime;
261
+ const span = this.spanName(action);
256
262
  this.emit(TelemetryEvents.SERVICE_RESPONSE, {
257
- service: this.getServiceName(),
263
+ service,
258
264
  action,
265
+ span,
259
266
  ok: ok2,
260
- duration: Date.now() - startTime,
267
+ duration: durationMs,
268
+ durationMs,
269
+ status
270
+ });
271
+ this.emit(TelemetryEvents.SPAN, {
272
+ span,
273
+ service,
274
+ action,
275
+ ok: ok2,
276
+ durationMs,
261
277
  status
262
278
  });
263
279
  }
@@ -266,9 +282,11 @@ var BaseService = class {
266
282
  *
267
283
  * @param error - The service error
268
284
  */
269
- emitError(error) {
285
+ emitError(error, action) {
286
+ const span = action ? this.spanName(action) : void 0;
270
287
  this.emit(TelemetryEvents.SERVICE_ERROR, {
271
288
  service: this.getServiceName(),
289
+ ...span ? { span } : {},
272
290
  error
273
291
  });
274
292
  }
@@ -279,6 +297,12 @@ var BaseService = class {
279
297
  getServiceName() {
280
298
  return this.constructor.serviceName;
281
299
  }
300
+ /**
301
+ * Stable span name used by SDK telemetry sinks.
302
+ */
303
+ spanName(action) {
304
+ return `sdk.${this.getServiceName()}.${action}`;
305
+ }
282
306
  /**
283
307
  * Create a combined abort signal from multiple sources.
284
308
  *
@@ -316,13 +340,13 @@ var BaseService = class {
316
340
  this.emitResponse(action, true, startTime);
317
341
  } else {
318
342
  this.emitResponse(action, false, startTime);
319
- this.emitError(result.error);
343
+ this.emitError(result.error, action);
320
344
  }
321
345
  return result;
322
346
  } catch (error) {
323
347
  const serviceError2 = wrapError(this.getServiceName(), error);
324
348
  this.emitResponse(action, false, startTime);
325
- this.emitError(serviceError2);
349
+ this.emitError(serviceError2, action);
326
350
  return err(serviceError2);
327
351
  }
328
352
  }
@@ -413,7 +437,7 @@ var SQLService = class extends BaseService {
413
437
  try {
414
438
  const response = await this.invokeSQL(
415
439
  dbName,
416
- SQLAction.READ,
440
+ this.actionForSql(sql, SQLAction.READ),
417
441
  { action: "query", sql, params: params ?? [] },
418
442
  options?.signal
419
443
  );
@@ -443,7 +467,7 @@ var SQLService = class extends BaseService {
443
467
  }
444
468
  const response = await this.invokeSQL(
445
469
  dbName,
446
- SQLAction.WRITE,
470
+ this.actionForSql(sql, SQLAction.WRITE),
447
471
  body,
448
472
  options?.signal
449
473
  );
@@ -465,7 +489,7 @@ var SQLService = class extends BaseService {
465
489
  try {
466
490
  const response = await this.invokeSQL(
467
491
  dbName,
468
- SQLAction.WRITE,
492
+ this.actionForSqlBatch(statements),
469
493
  { action: "batch", statements },
470
494
  options?.signal
471
495
  );
@@ -542,6 +566,14 @@ var SQLService = class extends BaseService {
542
566
  signal: this.combineSignals(signal)
543
567
  });
544
568
  }
569
+ actionForSql(sql, fallback) {
570
+ return firstSqlToken(sql) === "pragma" ? SQLAction.ADMIN : fallback;
571
+ }
572
+ actionForSqlBatch(statements) {
573
+ return statements.some(
574
+ (statement) => this.actionForSql(statement.sql, SQLAction.WRITE) === SQLAction.ADMIN
575
+ ) ? SQLAction.ADMIN : SQLAction.WRITE;
576
+ }
545
577
  async handleErrorResponse(response, operation) {
546
578
  const errorText = await response.text();
547
579
  let errorBody = {};
@@ -587,6 +619,33 @@ var SQLService = class extends BaseService {
587
619
  }
588
620
  };
589
621
  SQLService.serviceName = "sql";
622
+ function firstSqlToken(sql) {
623
+ let index = 0;
624
+ while (index < sql.length) {
625
+ while (index < sql.length && /\s/.test(sql[index])) {
626
+ index++;
627
+ }
628
+ if (sql.startsWith("--", index)) {
629
+ const newline = sql.indexOf("\n", index + 2);
630
+ if (newline === -1) {
631
+ return void 0;
632
+ }
633
+ index = newline + 1;
634
+ continue;
635
+ }
636
+ if (sql.startsWith("/*", index)) {
637
+ const end = sql.indexOf("*/", index + 2);
638
+ if (end === -1) {
639
+ return void 0;
640
+ }
641
+ index = end + 2;
642
+ continue;
643
+ }
644
+ break;
645
+ }
646
+ const match = /^[A-Za-z_]+/.exec(sql.slice(index));
647
+ return match?.[0].toLowerCase();
648
+ }
590
649
  // Annotate the CommonJS export names for ESM import in node:
591
650
  0 && (module.exports = {
592
651
  DatabaseHandle,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sql/index.ts","../../src/types.ts","../../src/errors.ts","../../src/base/BaseService.ts","../../src/sql/DatabaseHandle.ts","../../src/sql/types.ts","../../src/sql/SQLService.ts"],"sourcesContent":["/**\n * SQL Service Module\n *\n * Provides SQL database operations for TinyCloud.\n */\n\nexport { SQLService } from \"./SQLService\";\nexport { DatabaseHandle } from \"./DatabaseHandle\";\nexport type { ISQLService, IDatabaseHandle } from \"./ISQLService\";\nexport {\n SQLAction,\n type SQLActionType,\n type SQLServiceConfig,\n type QueryOptions,\n type ExecuteOptions,\n type BatchOptions,\n type SqlValue,\n type SqlStatement,\n type QueryResponse,\n type ExecuteResponse,\n type BatchResponse,\n} from \"./types\";\n","/**\n * SDK Services - Core Types\n *\n * These types define the service architecture for TinyCloud SDK.\n * Services use dependency injection via IServiceContext for platform independence.\n */\n\n// =============================================================================\n// Result Type Pattern\n// =============================================================================\n\n/**\n * Result type for service operations.\n * Services return Result instead of throwing, making error handling explicit.\n *\n * @template T - The success data type\n * @template E - The error type (defaults to ServiceError)\n *\n * @example\n * ```typescript\n * const result = await kv.get('key');\n * if (result.ok) {\n * console.log(result.data);\n * } else {\n * console.error(result.error.code);\n * }\n * ```\n */\nexport type Result<T, E = ServiceError> =\n | { ok: true; data: T }\n | { ok: false; error: E };\n\n/**\n * Service error with structured information.\n */\nexport interface ServiceError {\n /** Error code for programmatic handling (e.g., 'KV_NOT_FOUND', 'AUTH_EXPIRED') */\n code: string;\n /** Human-readable error message */\n message: string;\n /** Service that produced the error (e.g., 'kv', 'sql') */\n service: string;\n /** Original error if this wraps another error */\n cause?: Error;\n /** Additional metadata about the error */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Storage quota information returned with quota-related errors.\n */\nexport interface StorageQuotaInfo {\n usedBytes: number;\n limitBytes: number;\n service: string;\n}\n\n/**\n * Standard error codes used across services.\n */\nexport const ErrorCodes = {\n // Common errors\n NOT_FOUND: \"NOT_FOUND\",\n AUTH_EXPIRED: \"AUTH_EXPIRED\",\n AUTH_REQUIRED: \"AUTH_REQUIRED\",\n AUTH_UNAUTHORIZED: \"AUTH_UNAUTHORIZED\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n TIMEOUT: \"TIMEOUT\",\n ABORTED: \"ABORTED\",\n INVALID_INPUT: \"INVALID_INPUT\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n\n // KV-specific errors\n KV_NOT_FOUND: \"KV_NOT_FOUND\",\n KV_WRITE_FAILED: \"KV_WRITE_FAILED\",\n\n // SQL-specific errors\n SQL_ERROR: \"SQL_ERROR\",\n SQL_PERMISSION_DENIED: \"SQL_PERMISSION_DENIED\",\n SQL_DATABASE_NOT_FOUND: \"SQL_DATABASE_NOT_FOUND\",\n SQL_RESPONSE_TOO_LARGE: \"SQL_RESPONSE_TOO_LARGE\",\n SQL_QUOTA_EXCEEDED: \"SQL_QUOTA_EXCEEDED\",\n SQL_INVALID_STATEMENT: \"SQL_INVALID_STATEMENT\",\n SQL_SCHEMA_ERROR: \"SQL_SCHEMA_ERROR\",\n SQL_READONLY_VIOLATION: \"SQL_READONLY_VIOLATION\",\n\n // Storage quota errors\n STORAGE_QUOTA_EXCEEDED: \"STORAGE_QUOTA_EXCEEDED\",\n STORAGE_LIMIT_REACHED: \"STORAGE_LIMIT_REACHED\",\n\n // DuckDB-specific errors\n DUCKDB_ERROR: \"DUCKDB_ERROR\",\n DUCKDB_PERMISSION_DENIED: \"DUCKDB_PERMISSION_DENIED\",\n DUCKDB_DATABASE_NOT_FOUND: \"DUCKDB_DATABASE_NOT_FOUND\",\n DUCKDB_RESPONSE_TOO_LARGE: \"DUCKDB_RESPONSE_TOO_LARGE\",\n DUCKDB_QUOTA_EXCEEDED: \"DUCKDB_QUOTA_EXCEEDED\",\n DUCKDB_INVALID_STATEMENT: \"DUCKDB_INVALID_STATEMENT\",\n DUCKDB_SCHEMA_ERROR: \"DUCKDB_SCHEMA_ERROR\",\n DUCKDB_READONLY_VIOLATION: \"DUCKDB_READONLY_VIOLATION\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n// =============================================================================\n// Service Session\n// =============================================================================\n\n/**\n * Session data required for authenticated service operations.\n * Both TinyCloudSession and web-sdk Session can be cast to this interface.\n */\nexport interface ServiceSession {\n /** The delegation header containing the UCAN */\n delegationHeader: { Authorization: string };\n /** The delegation CID */\n delegationCid: string;\n /** The space ID for this session */\n spaceId: string;\n /** The verification method DID */\n verificationMethod: string;\n /** The session key JWK (required for invoke) */\n jwk: object;\n}\n\n// =============================================================================\n// Platform Dependencies (Injected)\n// =============================================================================\n\n/**\n * Headers type - compatible with both browser and Node.js.\n */\nexport type ServiceHeaders = Record<string, string> | [string, string][];\n\n/**\n * A single fact object to include in the UCAN invocation.\n * Facts are key-value objects that the server reads from the UCAN facts field.\n */\nexport interface InvocationFact {\n [key: string]: unknown;\n}\n\n/**\n * Facts to include in the UCAN invocation.\n * This is an array of fact objects per the UCAN spec.\n * Used to pass additional parameters that the server reads from the UCAN facts field.\n */\nexport type InvocationFacts = InvocationFact[];\n\n/**\n * Invoke function signature - platform-specific implementation injected via DI.\n * Both node-sdk-wasm and web-sdk-wasm export this with identical signature.\n *\n * @param session - The service session with delegation data\n * @param service - Service name (e.g., \"kv\")\n * @param path - Resource path or key\n * @param action - Action to perform (e.g., \"tinycloud.kv/get\")\n * @param facts - Optional facts to include in the UCAN (e.g., for capabilities/read params)\n * @returns Headers to include in the request\n */\nexport type InvokeFunction = (\n session: ServiceSession,\n service: string,\n path: string,\n action: string,\n facts?: InvocationFacts,\n) => ServiceHeaders;\n\n/**\n * Multi-resource invocation entry.\n */\nexport interface InvokeAnyEntry {\n /**\n * Legacy space-scoped resource. Optional when `resource` is provided.\n */\n spaceId?: string;\n service: string;\n path: string;\n action: string;\n /** Optional raw resource URI. When set, WASM signs this URI directly. */\n resource?: string;\n}\n\n/**\n * Invoke function for minting a single authorization header that covers\n * multiple capabilities across one effective invoker.\n */\nexport type InvokeAnyFunction = (\n session: ServiceSession,\n entries: InvokeAnyEntry[],\n facts?: InvocationFacts,\n) => ServiceHeaders;\n\n/**\n * Fetch request options - compatible with standard fetch API.\n */\nexport interface FetchRequestInit {\n method?: string;\n headers?: ServiceHeaders;\n body?: Blob | FormData | string;\n signal?: AbortSignal;\n}\n\n/**\n * Fetch response interface - compatible with standard Response.\n */\nexport interface FetchResponse {\n ok: boolean;\n status: number;\n statusText: string;\n body?: unknown;\n headers: {\n get(name: string): string | null;\n };\n json(): Promise<unknown>;\n text(): Promise<string>;\n arrayBuffer(): Promise<ArrayBuffer>;\n blob(): Promise<Blob>;\n}\n\n/**\n * Fetch function signature - allows for custom fetch implementations.\n * Compatible with both browser fetch and Node.js fetch.\n */\nexport type FetchFunction = (\n url: string,\n init?: FetchRequestInit,\n) => Promise<FetchResponse>;\n\n// =============================================================================\n// Retry Policy\n// =============================================================================\n\n/**\n * Configuration for automatic retry of failed requests.\n */\nexport interface RetryPolicy {\n /** Maximum number of attempts (including initial) */\n maxAttempts: number;\n /** Backoff strategy between retries */\n backoff: \"none\" | \"linear\" | \"exponential\";\n /** Base delay in milliseconds for backoff calculation */\n baseDelayMs: number;\n /** Maximum delay in milliseconds between retries */\n maxDelayMs: number;\n /** Error codes that should trigger a retry */\n retryableErrors: string[];\n}\n\n/**\n * Default retry policy.\n */\nexport const defaultRetryPolicy: RetryPolicy = {\n maxAttempts: 3,\n backoff: \"exponential\",\n baseDelayMs: 1000,\n maxDelayMs: 10000,\n retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT],\n};\n\n// =============================================================================\n// Service Context\n// =============================================================================\n\n/**\n * Event handler function type.\n */\nexport type EventHandler = (data: unknown) => void;\n\n/**\n * Service interface - base contract for all services.\n */\nexport interface IService {\n /** Initialize service with context */\n initialize(context: IServiceContext): void;\n\n /** Called when session changes (sign-in, sign-out, refresh) */\n onSessionChange(session: ServiceSession | null): void;\n\n /** Called when SDK signs out - should abort pending operations */\n onSignOut(): void;\n\n /** Service-specific configuration */\n readonly config: Record<string, unknown>;\n}\n\n/**\n * Context provided to services for accessing platform dependencies.\n * The SDK creates this context and passes it to services during initialization.\n */\nexport interface IServiceContext {\n // Session management\n /** Current active session, or null if not authenticated */\n readonly session: ServiceSession | null;\n /** Whether there is an active authenticated session */\n readonly isAuthenticated: boolean;\n\n // Platform dependencies (injected by SDK)\n /** Platform-specific invoke function from WASM binding */\n readonly invoke: InvokeFunction;\n /** Optional multi-resource invoke function */\n readonly invokeAny?: InvokeAnyFunction;\n /** Fetch function (defaults to globalThis.fetch) */\n readonly fetch: FetchFunction;\n /** Available TinyCloud host URLs */\n readonly hosts: string[];\n\n // Cross-service access\n /** Get another registered service by name */\n getService<T extends IService>(name: string): T | undefined;\n\n // Telemetry/Events\n /** Emit a telemetry event */\n emit(event: string, data: unknown): void;\n /** Subscribe to events */\n on(event: string, handler: EventHandler): () => void;\n\n // Lifecycle\n /** Abort signal that fires when SDK signs out */\n readonly abortSignal: AbortSignal;\n\n // Retry policy\n /** Retry policy for failed requests */\n readonly retryPolicy: RetryPolicy;\n}\n\n// =============================================================================\n// Telemetry Events\n// =============================================================================\n\n/**\n * Event emitted before a service request.\n */\nexport interface ServiceRequestEvent {\n service: string;\n action: string;\n key?: string;\n timestamp: number;\n}\n\n/**\n * Event emitted after a service response.\n */\nexport interface ServiceResponseEvent {\n service: string;\n action: string;\n ok: boolean;\n duration: number;\n status?: number;\n}\n\n/**\n * Event emitted on service error.\n */\nexport interface ServiceErrorEvent {\n service: string;\n error: ServiceError;\n}\n\n/**\n * Event emitted on retry attempt.\n */\nexport interface ServiceRetryEvent {\n service: string;\n attempt: number;\n maxAttempts: number;\n error: ServiceError;\n}\n\n/**\n * Telemetry event names.\n */\nexport const TelemetryEvents = {\n SERVICE_REQUEST: \"service.request\",\n SERVICE_RESPONSE: \"service.response\",\n SERVICE_ERROR: \"service.error\",\n SERVICE_RETRY: \"service.retry\",\n SESSION_CHANGED: \"session.changed\",\n SESSION_EXPIRED: \"session.expired\",\n} as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Create a success result.\n */\nexport function ok<T>(data: T): Result<T> {\n return { ok: true, data };\n}\n\n/**\n * Create an error result.\n */\nexport function err<E = ServiceError>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\n/**\n * Create a ServiceError.\n */\nexport function serviceError(\n code: string,\n message: string,\n service: string,\n options?: { cause?: Error; meta?: Record<string, unknown> },\n): ServiceError {\n return {\n code,\n message,\n service,\n cause: options?.cause,\n meta: options?.meta,\n };\n}\n","/**\n * SDK Services - Error Utilities\n *\n * Utilities for creating and handling service errors.\n */\n\nimport { ServiceError, ErrorCodes, err, serviceError } from \"./types\";\n\n/**\n * Create a service error for authentication required.\n */\nexport function authRequiredError(service: string): ServiceError {\n return {\n code: ErrorCodes.AUTH_REQUIRED,\n message: \"Authentication required. Please sign in first.\",\n service,\n };\n}\n\n/**\n * Create a service error for expired authentication.\n */\nexport function authExpiredError(service: string): ServiceError {\n return {\n code: ErrorCodes.AUTH_EXPIRED,\n message: \"Session has expired. Please sign in again.\",\n service,\n };\n}\n\n/**\n * Create a service error for network issues.\n */\nexport function networkError(\n service: string,\n message: string,\n cause?: Error\n): ServiceError {\n return {\n code: ErrorCodes.NETWORK_ERROR,\n message,\n service,\n cause,\n };\n}\n\n/**\n * Create a service error for timeouts.\n */\nexport function timeoutError(service: string): ServiceError {\n return {\n code: ErrorCodes.TIMEOUT,\n message: \"Request timed out.\",\n service,\n };\n}\n\n/**\n * Create a service error for aborted requests.\n */\nexport function abortedError(service: string): ServiceError {\n return {\n code: ErrorCodes.ABORTED,\n message: \"Request was aborted.\",\n service,\n };\n}\n\n/**\n * Create a service error for not found resources.\n */\nexport function notFoundError(\n service: string,\n resource: string\n): ServiceError {\n return {\n code: ErrorCodes.NOT_FOUND,\n message: `Resource not found: ${resource}`,\n service,\n };\n}\n\n/**\n * Create a service error for permission denied.\n */\nexport function permissionDeniedError(\n service: string,\n action: string\n): ServiceError {\n return {\n code: ErrorCodes.PERMISSION_DENIED,\n message: `Permission denied for action: ${action}`,\n service,\n };\n}\n\n/**\n * Parse the server's \"Unauthorized Action: {resource} / {ability}\" pattern.\n */\nexport function parseAuthError(responseText: string): { resource?: string; action?: string } {\n const match = responseText.match(/^Unauthorized Action:\\s*(.+?)\\s*\\/\\s*(tinycloud\\.\\S+)$/m);\n if (match) {\n return { resource: match[1].trim(), action: match[2].trim() };\n }\n return {};\n}\n\n/**\n * Create a service error for unauthorized action (missing capability).\n */\nexport function authUnauthorizedError(\n service: string,\n message: string,\n meta?: Record<string, unknown>\n): ServiceError {\n return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });\n}\n\n/**\n * Create a service error for storage quota exceeded (402 Payment Required).\n */\nexport function storageQuotaExceededError(\n service: string,\n message: string,\n meta?: Record<string, unknown>\n): ServiceError {\n return {\n code: ErrorCodes.STORAGE_QUOTA_EXCEEDED,\n message,\n service,\n meta,\n };\n}\n\n/**\n * Create a service error for storage limit reached (413 Payload Too Large).\n */\nexport function storageLimitReachedError(\n service: string,\n message: string,\n meta?: Record<string, unknown>\n): ServiceError {\n return {\n code: ErrorCodes.STORAGE_LIMIT_REACHED,\n message,\n service,\n meta,\n };\n}\n\n/**\n * Wrap an unknown error in a ServiceError.\n */\nexport function wrapError(\n service: string,\n error: unknown,\n defaultCode: string = ErrorCodes.NETWORK_ERROR\n): ServiceError {\n if (error instanceof Error) {\n // Check for abort errors\n if (error.name === \"AbortError\") {\n return abortedError(service);\n }\n\n // Check for timeout errors (varies by platform)\n if (\n error.name === \"TimeoutError\" ||\n error.message.toLowerCase().includes(\"timeout\")\n ) {\n return timeoutError(service);\n }\n\n return {\n code: defaultCode,\n message: error.message,\n service,\n cause: error,\n };\n }\n\n return {\n code: defaultCode,\n message: String(error),\n service,\n };\n}\n\n/**\n * Create an error Result from a ServiceError.\n */\nexport function errorResult(error: ServiceError) {\n return err(error);\n}\n","/**\n * BaseService - Abstract base class for all TinyCloud services.\n *\n * Provides common functionality:\n * - Context management\n * - Session lifecycle hooks\n * - Abort signal handling\n * - Telemetry emission\n */\n\nimport {\n IService,\n IServiceContext,\n ServiceSession,\n ServiceError,\n TelemetryEvents,\n err,\n Result,\n} from \"../types\";\nimport { authRequiredError, wrapError } from \"../errors\";\n\n/**\n * Abstract base class for TinyCloud services.\n *\n * Services extend this class to get common functionality like\n * context management, session lifecycle, and abort handling.\n *\n * @example\n * ```typescript\n * class MyService extends BaseService implements IMyService {\n * static readonly serviceName = 'myservice';\n *\n * constructor(config: MyServiceConfig = {}) {\n * super();\n * this._config = config;\n * }\n *\n * async doSomething(): Promise<Result<Data>> {\n * if (!this.requireAuth()) {\n * return err(authRequiredError('myservice'));\n * }\n * // ... implementation\n * }\n * }\n * ```\n */\nexport abstract class BaseService implements IService {\n /**\n * Service identifier used for registration.\n * Must be overridden by subclasses.\n */\n static readonly serviceName: string;\n\n /**\n * Service context providing access to platform dependencies.\n * Set during initialize().\n */\n protected context!: IServiceContext;\n\n /**\n * Abort controller for this service's operations.\n * Reset on sign-out.\n */\n protected abortController: AbortController = new AbortController();\n\n /**\n * Service-specific configuration.\n */\n protected _config: Record<string, unknown> = {};\n\n /**\n * Get the service configuration.\n */\n get config(): Record<string, unknown> {\n return this._config;\n }\n\n /**\n * Initialize the service with context.\n * Called by the SDK after instantiation.\n *\n * @param context - The service context\n */\n initialize(context: IServiceContext): void {\n this.context = context;\n }\n\n /**\n * Called when session changes (sign-in, sign-out, refresh).\n * Override in subclasses to handle session changes.\n *\n * @param session - The new session, or null if signed out\n */\n onSessionChange(session: ServiceSession | null): void {\n // Override in subclass if needed\n }\n\n /**\n * Called when SDK signs out.\n * Aborts all pending operations.\n */\n onSignOut(): void {\n this.abortController.abort();\n this.abortController = new AbortController();\n }\n\n /**\n * Get the abort signal for this service.\n * Combines the service-level abort with context-level abort.\n */\n protected get abortSignal(): AbortSignal {\n return this.abortController.signal;\n }\n\n /**\n * Check if the service is authenticated.\n */\n protected get isAuthenticated(): boolean {\n return this.context?.isAuthenticated ?? false;\n }\n\n /**\n * Get the current session.\n * Throws if not authenticated.\n */\n protected get session(): ServiceSession {\n if (!this.context?.session) {\n throw new Error(\"Not authenticated\");\n }\n return this.context.session;\n }\n\n /**\n * Check authentication and return error result if not authenticated.\n * Use this at the start of methods that require authentication.\n *\n * @returns true if authenticated, false otherwise\n */\n protected requireAuth(): boolean {\n return this.isAuthenticated;\n }\n\n /**\n * Emit a telemetry event.\n *\n * @param event - Event name\n * @param data - Event data\n */\n protected emit(event: string, data: unknown): void {\n this.context?.emit(event, data);\n }\n\n /**\n * Emit a service request event.\n *\n * @param action - The action being performed\n * @param key - Optional key/path being accessed\n */\n protected emitRequest(action: string, key?: string): void {\n this.emit(TelemetryEvents.SERVICE_REQUEST, {\n service: this.getServiceName(),\n action,\n key,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Emit a service response event.\n *\n * @param action - The action that was performed\n * @param ok - Whether the request was successful\n * @param startTime - Start time for duration calculation\n * @param status - Optional HTTP status code\n */\n protected emitResponse(\n action: string,\n ok: boolean,\n startTime: number,\n status?: number\n ): void {\n this.emit(TelemetryEvents.SERVICE_RESPONSE, {\n service: this.getServiceName(),\n action,\n ok,\n duration: Date.now() - startTime,\n status,\n });\n }\n\n /**\n * Emit a service error event.\n *\n * @param error - The service error\n */\n protected emitError(error: ServiceError): void {\n this.emit(TelemetryEvents.SERVICE_ERROR, {\n service: this.getServiceName(),\n error,\n });\n }\n\n /**\n * Get the service name from the static property.\n * Subclasses must define static serviceName.\n */\n protected getServiceName(): string {\n return (this.constructor as typeof BaseService).serviceName;\n }\n\n /**\n * Create a combined abort signal from multiple sources.\n *\n * @param signals - Additional abort signals to combine\n * @returns A combined abort signal\n */\n protected combineSignals(...signals: (AbortSignal | undefined)[]): AbortSignal {\n const controller = new AbortController();\n const allSignals = [this.abortSignal, ...signals.filter(Boolean)] as AbortSignal[];\n\n for (const signal of allSignals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n return controller.signal;\n }\n signal.addEventListener(\"abort\", () => controller.abort(signal.reason), {\n once: true,\n });\n }\n\n return controller.signal;\n }\n\n /**\n * Wrap an operation with error handling and telemetry.\n *\n * @param action - The action name for telemetry\n * @param key - Optional key for telemetry\n * @param operation - The operation to execute\n * @returns Result of the operation\n */\n protected async withTelemetry<T>(\n action: string,\n key: string | undefined,\n operation: () => Promise<Result<T>>\n ): Promise<Result<T>> {\n const startTime = Date.now();\n this.emitRequest(action, key);\n\n try {\n const result = await operation();\n\n if (result.ok) {\n this.emitResponse(action, true, startTime);\n } else {\n this.emitResponse(action, false, startTime);\n this.emitError(result.error);\n }\n\n return result;\n } catch (error) {\n const serviceError = wrapError(this.getServiceName(), error);\n this.emitResponse(action, false, startTime);\n this.emitError(serviceError);\n return err(serviceError);\n }\n }\n}\n","/**\n * DatabaseHandle - Handle for operations on a specific named database.\n *\n * Delegates all operations to the parent SQLService with the database name.\n */\n\nimport type { Result } from \"../types\";\nimport type { IDatabaseHandle } from \"./ISQLService\";\nimport type { SQLService } from \"./SQLService\";\nimport type {\n SqlValue,\n SqlStatement,\n QueryResponse,\n ExecuteResponse,\n BatchResponse,\n QueryOptions,\n ExecuteOptions,\n BatchOptions,\n} from \"./types\";\n\nexport class DatabaseHandle implements IDatabaseHandle {\n private service: SQLService;\n public readonly name: string;\n\n constructor(service: SQLService, name: string) {\n this.service = service;\n this.name = name;\n }\n\n async query<T = Record<string, unknown>>(\n sql: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse<T>>> {\n return this.service.queryOnDb<T>(this.name, sql, params, options);\n }\n\n async execute(\n sql: string,\n params?: SqlValue[],\n options?: ExecuteOptions\n ): Promise<Result<ExecuteResponse>> {\n return this.service.executeOnDb(this.name, sql, params, options);\n }\n\n async batch(\n statements: SqlStatement[],\n options?: BatchOptions\n ): Promise<Result<BatchResponse>> {\n return this.service.batchOnDb(this.name, statements, options);\n }\n\n async executeStatement(\n name: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse | ExecuteResponse>> {\n return this.service.executeStatementOnDb(this.name, name, params, options);\n }\n\n async export(options?: QueryOptions): Promise<Result<Blob>> {\n return this.service.exportDb(this.name, options);\n }\n}\n","/**\n * SQL Service Types\n *\n * Type definitions for the SQL service operations.\n */\n\n/**\n * Configuration for SQLService.\n */\nexport interface SQLServiceConfig {\n /**\n * Default database name.\n * If not set, operations default to \"default\".\n */\n defaultDatabase?: string;\n\n /**\n * Default timeout in milliseconds for SQL operations.\n */\n timeout?: number;\n\n /** Allow additional config properties */\n [key: string]: unknown;\n}\n\n/**\n * Options for SQL query operations.\n */\nexport interface QueryOptions {\n /**\n * Custom abort signal for this operation.\n */\n signal?: AbortSignal;\n}\n\n/**\n * Options for SQL execute operations.\n */\nexport interface ExecuteOptions {\n /**\n * Schema initialization statements (CREATE TABLE IF NOT EXISTS ...).\n * Executed before the main statement on first write.\n */\n schema?: string[];\n\n /**\n * Custom abort signal for this operation.\n */\n signal?: AbortSignal;\n}\n\n/**\n * Options for SQL batch operations.\n */\nexport interface BatchOptions {\n /**\n * Custom abort signal for this operation.\n */\n signal?: AbortSignal;\n}\n\n/**\n * A SQL value: null, number, string, or binary data.\n */\nexport type SqlValue = null | number | string | Uint8Array;\n\n/**\n * A SQL statement with optional parameters.\n */\nexport interface SqlStatement {\n sql: string;\n params?: SqlValue[];\n}\n\n/**\n * Response from SQL query operations.\n */\nexport interface QueryResponse<T = Record<string, unknown>> {\n columns: string[];\n rows: T[][];\n rowCount: number;\n}\n\n/**\n * Response from SQL execute operations.\n */\nexport interface ExecuteResponse {\n changes: number;\n lastInsertRowId: number;\n}\n\n/**\n * Response from SQL batch operations.\n */\nexport interface BatchResponse {\n results: ExecuteResponse[];\n}\n\n/**\n * SQL service action types.\n */\nexport const SQLAction = {\n READ: \"tinycloud.sql/read\",\n WRITE: \"tinycloud.sql/write\",\n ADMIN: \"tinycloud.sql/admin\",\n SELECT: \"tinycloud.sql/select\",\n INSERT: \"tinycloud.sql/insert\",\n UPDATE: \"tinycloud.sql/update\",\n DELETE: \"tinycloud.sql/delete\",\n EXECUTE: \"tinycloud.sql/execute\",\n EXPORT: \"tinycloud.sql/export\",\n ALL: \"tinycloud.sql/*\",\n} as const;\n\nexport type SQLActionType = (typeof SQLAction)[keyof typeof SQLAction];\n","/**\n * SQLService - SQL database service implementation.\n *\n * Platform-agnostic SQL service that works with both web-sdk and node-sdk.\n * Uses dependency injection via IServiceContext for platform dependencies.\n */\n\nimport { BaseService } from \"../base/BaseService\";\nimport {\n Result,\n ok,\n err,\n ErrorCodes,\n serviceError,\n type FetchResponse,\n} from \"../types\";\nimport { authRequiredError, wrapError, parseAuthError } from \"../errors\";\nimport type { ISQLService } from \"./ISQLService\";\nimport type { IDatabaseHandle } from \"./ISQLService\";\nimport { DatabaseHandle } from \"./DatabaseHandle\";\nimport {\n type SQLServiceConfig,\n type QueryOptions,\n type ExecuteOptions,\n type BatchOptions,\n type SqlValue,\n type SqlStatement,\n type QueryResponse,\n type ExecuteResponse,\n type BatchResponse,\n SQLAction,\n} from \"./types\";\n\nexport class SQLService extends BaseService implements ISQLService {\n static readonly serviceName = \"sql\";\n\n declare protected _config: SQLServiceConfig;\n\n constructor(config: SQLServiceConfig = {}) {\n super();\n this._config = config;\n }\n\n get config(): SQLServiceConfig {\n return this._config;\n }\n\n private get defaultDbName(): string {\n return this._config.defaultDatabase ?? \"default\";\n }\n\n private get host(): string {\n return this.context.hosts[0];\n }\n\n /**\n * Get a handle to a named database.\n */\n db(name?: string): IDatabaseHandle {\n return new DatabaseHandle(this, name ?? this.defaultDbName);\n }\n\n /**\n * Shortcut: query the default database.\n */\n async query<T = Record<string, unknown>>(\n sql: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse<T>>> {\n return this.queryOnDb<T>(this.defaultDbName, sql, params, options);\n }\n\n /**\n * Shortcut: execute on the default database.\n */\n async execute(\n sql: string,\n params?: SqlValue[],\n options?: ExecuteOptions\n ): Promise<Result<ExecuteResponse>> {\n return this.executeOnDb(this.defaultDbName, sql, params, options);\n }\n\n /**\n * Shortcut: batch on the default database.\n */\n async batch(\n statements: SqlStatement[],\n options?: BatchOptions\n ): Promise<Result<BatchResponse>> {\n return this.batchOnDb(this.defaultDbName, statements, options);\n }\n\n // === Internal methods called by DatabaseHandle ===\n\n async queryOnDb<T = Record<string, unknown>>(\n dbName: string,\n sql: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse<T>>> {\n return this.withTelemetry(\"query\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const response = await this.invokeSQL(\n dbName,\n SQLAction.READ,\n { action: \"query\", sql, params: params ?? [] },\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"query\");\n }\n\n const data = (await response.json()) as QueryResponse<T>;\n return ok(data);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n async executeOnDb(\n dbName: string,\n sql: string,\n params?: SqlValue[],\n options?: ExecuteOptions\n ): Promise<Result<ExecuteResponse>> {\n return this.withTelemetry(\"execute\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const body: Record<string, unknown> = {\n action: \"execute\",\n sql,\n params: params ?? [],\n };\n if (options?.schema) {\n body.schema = options.schema;\n }\n\n const response = await this.invokeSQL(\n dbName,\n SQLAction.WRITE,\n body,\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"execute\");\n }\n\n const data = (await response.json()) as ExecuteResponse;\n return ok(data);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n async batchOnDb(\n dbName: string,\n statements: SqlStatement[],\n options?: BatchOptions\n ): Promise<Result<BatchResponse>> {\n return this.withTelemetry(\"batch\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const response = await this.invokeSQL(\n dbName,\n SQLAction.WRITE,\n { action: \"batch\", statements },\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"batch\");\n }\n\n const data = (await response.json()) as BatchResponse;\n return ok(data);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n async executeStatementOnDb(\n dbName: string,\n name: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse | ExecuteResponse>> {\n return this.withTelemetry(\"executeStatement\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const response = await this.invokeSQL(\n dbName,\n SQLAction.EXECUTE,\n { action: \"execute_statement\", name, params: params ?? [] },\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"executeStatement\");\n }\n\n const data = (await response.json()) as\n | QueryResponse\n | ExecuteResponse;\n return ok(data);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n async exportDb(\n dbName: string,\n options?: QueryOptions\n ): Promise<Result<Blob>> {\n return this.withTelemetry(\"export\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const response = await this.invokeSQL(\n dbName,\n SQLAction.EXPORT,\n { action: \"export\" },\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"export\");\n }\n\n // FetchResponse doesn't expose blob(), so access it from the\n // underlying response which is a standard fetch Response at runtime\n const resp = response as any;\n if (typeof resp.blob === \"function\") {\n const blob = await resp.blob();\n return ok(blob as Blob);\n }\n // If blob() is not available, return the raw text as a Blob-like\n const text = await response.text();\n return ok(text as unknown as Blob);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n // === Private helpers ===\n\n private async invokeSQL(\n dbName: string,\n action: string,\n body: Record<string, unknown>,\n signal?: AbortSignal\n ): Promise<FetchResponse> {\n const session = this.context.session!;\n const headers = this.context.invoke(session, \"sql\", dbName, action);\n\n return this.context.fetch(`${this.host}/invoke`, {\n method: \"POST\",\n headers: {\n ...(headers as Record<string, string>),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body) as any,\n signal: this.combineSignals(signal),\n });\n }\n\n private async handleErrorResponse(\n response: FetchResponse,\n operation: string\n ): Promise<Result<never>> {\n const errorText = await response.text();\n\n let errorBody: { error?: string; message?: string; code?: string } = {};\n try {\n errorBody = JSON.parse(errorText);\n } catch {\n // Not JSON\n }\n\n const errorCode = this.mapHttpStatusToErrorCode(\n response.status,\n errorBody.error\n );\n const message =\n errorBody.message ||\n `SQL ${operation} failed: ${response.status} - ${errorText}`;\n\n const meta: Record<string, unknown> = { status: response.status, statusText: response.statusText };\n\n if (response.status === 401) {\n const { resource, action } = parseAuthError(errorText);\n if (action) meta.requiredAction = action;\n if (resource) meta.resource = resource;\n }\n\n return err(\n serviceError(errorCode, message, \"sql\", { meta })\n );\n }\n\n private mapHttpStatusToErrorCode(\n status: number,\n serverError?: string\n ): string {\n switch (status) {\n case 400:\n return ErrorCodes.SQL_ERROR;\n case 401:\n return ErrorCodes.AUTH_UNAUTHORIZED;\n case 403:\n if (serverError === \"sql_readonly_violation\") {\n return ErrorCodes.SQL_READONLY_VIOLATION;\n }\n return ErrorCodes.SQL_PERMISSION_DENIED;\n case 404:\n return ErrorCodes.SQL_DATABASE_NOT_FOUND;\n case 413:\n return ErrorCodes.SQL_RESPONSE_TOO_LARGE;\n case 429:\n return ErrorCodes.SQL_QUOTA_EXCEEDED;\n default:\n return ErrorCodes.NETWORK_ERROR;\n }\n }\n}\n"],"mappings":"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","names":["ok","serviceError"]}
1
+ {"version":3,"sources":["../../src/sql/index.ts","../../src/types.ts","../../src/errors.ts","../../src/base/BaseService.ts","../../src/sql/DatabaseHandle.ts","../../src/sql/types.ts","../../src/sql/SQLService.ts"],"sourcesContent":["/**\n * SQL Service Module\n *\n * Provides SQL database operations for TinyCloud.\n */\n\nexport { SQLService } from \"./SQLService\";\nexport { DatabaseHandle } from \"./DatabaseHandle\";\nexport type { ISQLService, IDatabaseHandle } from \"./ISQLService\";\nexport {\n SQLAction,\n type SQLActionType,\n type SQLServiceConfig,\n type QueryOptions,\n type ExecuteOptions,\n type BatchOptions,\n type SqlValue,\n type SqlStatement,\n type QueryResponse,\n type ExecuteResponse,\n type BatchResponse,\n} from \"./types\";\n","/**\n * SDK Services - Core Types\n *\n * These types define the service architecture for TinyCloud SDK.\n * Services use dependency injection via IServiceContext for platform independence.\n */\n\n// =============================================================================\n// Result Type Pattern\n// =============================================================================\n\n/**\n * Result type for service operations.\n * Services return Result instead of throwing, making error handling explicit.\n *\n * @template T - The success data type\n * @template E - The error type (defaults to ServiceError)\n *\n * @example\n * ```typescript\n * const result = await kv.get('key');\n * if (result.ok) {\n * console.log(result.data);\n * } else {\n * console.error(result.error.code);\n * }\n * ```\n */\nexport type Result<T, E = ServiceError> =\n | { ok: true; data: T }\n | { ok: false; error: E };\n\n/**\n * Service error with structured information.\n */\nexport interface ServiceError {\n /** Error code for programmatic handling (e.g., 'KV_NOT_FOUND', 'AUTH_EXPIRED') */\n code: string;\n /** Human-readable error message */\n message: string;\n /** Service that produced the error (e.g., 'kv', 'sql') */\n service: string;\n /** Original error if this wraps another error */\n cause?: Error;\n /** Additional metadata about the error */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Storage quota information returned with quota-related errors.\n */\nexport interface StorageQuotaInfo {\n usedBytes: number;\n limitBytes: number;\n service: string;\n}\n\n/**\n * Standard error codes used across services.\n */\nexport const ErrorCodes = {\n // Common errors\n NOT_FOUND: \"NOT_FOUND\",\n AUTH_EXPIRED: \"AUTH_EXPIRED\",\n AUTH_REQUIRED: \"AUTH_REQUIRED\",\n AUTH_UNAUTHORIZED: \"AUTH_UNAUTHORIZED\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n TIMEOUT: \"TIMEOUT\",\n ABORTED: \"ABORTED\",\n INVALID_INPUT: \"INVALID_INPUT\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n\n // KV-specific errors\n KV_NOT_FOUND: \"KV_NOT_FOUND\",\n KV_WRITE_FAILED: \"KV_WRITE_FAILED\",\n\n // SQL-specific errors\n SQL_ERROR: \"SQL_ERROR\",\n SQL_PERMISSION_DENIED: \"SQL_PERMISSION_DENIED\",\n SQL_DATABASE_NOT_FOUND: \"SQL_DATABASE_NOT_FOUND\",\n SQL_RESPONSE_TOO_LARGE: \"SQL_RESPONSE_TOO_LARGE\",\n SQL_QUOTA_EXCEEDED: \"SQL_QUOTA_EXCEEDED\",\n SQL_INVALID_STATEMENT: \"SQL_INVALID_STATEMENT\",\n SQL_SCHEMA_ERROR: \"SQL_SCHEMA_ERROR\",\n SQL_READONLY_VIOLATION: \"SQL_READONLY_VIOLATION\",\n\n // Storage quota errors\n STORAGE_QUOTA_EXCEEDED: \"STORAGE_QUOTA_EXCEEDED\",\n STORAGE_LIMIT_REACHED: \"STORAGE_LIMIT_REACHED\",\n\n // DuckDB-specific errors\n DUCKDB_ERROR: \"DUCKDB_ERROR\",\n DUCKDB_PERMISSION_DENIED: \"DUCKDB_PERMISSION_DENIED\",\n DUCKDB_DATABASE_NOT_FOUND: \"DUCKDB_DATABASE_NOT_FOUND\",\n DUCKDB_RESPONSE_TOO_LARGE: \"DUCKDB_RESPONSE_TOO_LARGE\",\n DUCKDB_QUOTA_EXCEEDED: \"DUCKDB_QUOTA_EXCEEDED\",\n DUCKDB_INVALID_STATEMENT: \"DUCKDB_INVALID_STATEMENT\",\n DUCKDB_SCHEMA_ERROR: \"DUCKDB_SCHEMA_ERROR\",\n DUCKDB_READONLY_VIOLATION: \"DUCKDB_READONLY_VIOLATION\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n// =============================================================================\n// Service Session\n// =============================================================================\n\n/**\n * Session data required for authenticated service operations.\n * Both TinyCloudSession and web-sdk Session can be cast to this interface.\n */\nexport interface ServiceSession {\n /** The delegation header containing the UCAN */\n delegationHeader: { Authorization: string };\n /** The delegation CID */\n delegationCid: string;\n /** The space ID for this session */\n spaceId: string;\n /** The verification method DID */\n verificationMethod: string;\n /** The session key JWK (required for invoke) */\n jwk: object;\n}\n\n// =============================================================================\n// Platform Dependencies (Injected)\n// =============================================================================\n\n/**\n * Headers type - compatible with both browser and Node.js.\n */\nexport type ServiceHeaders = Record<string, string> | [string, string][];\n\n/**\n * A single fact object to include in the UCAN invocation.\n * Facts are key-value objects that the server reads from the UCAN facts field.\n */\nexport interface InvocationFact {\n [key: string]: unknown;\n}\n\n/**\n * Facts to include in the UCAN invocation.\n * This is an array of fact objects per the UCAN spec.\n * Used to pass additional parameters that the server reads from the UCAN facts field.\n */\nexport type InvocationFacts = InvocationFact[];\n\n/**\n * Invoke function signature - platform-specific implementation injected via DI.\n * Both node-sdk-wasm and web-sdk-wasm export this with identical signature.\n *\n * @param session - The service session with delegation data\n * @param service - Service name (e.g., \"kv\")\n * @param path - Resource path or key\n * @param action - Action to perform (e.g., \"tinycloud.kv/get\")\n * @param facts - Optional facts to include in the UCAN (e.g., for capabilities/read params)\n * @returns Headers to include in the request\n */\nexport type InvokeFunction = (\n session: ServiceSession,\n service: string,\n path: string,\n action: string,\n facts?: InvocationFacts,\n) => ServiceHeaders;\n\n/**\n * Multi-resource invocation entry.\n */\nexport interface InvokeAnyEntry {\n /**\n * Legacy space-scoped resource. Optional when `resource` is provided.\n */\n spaceId?: string;\n service: string;\n path: string;\n action: string;\n /** Optional raw resource URI. When set, WASM signs this URI directly. */\n resource?: string;\n}\n\n/**\n * Invoke function for minting a single authorization header that covers\n * multiple capabilities across one effective invoker.\n */\nexport type InvokeAnyFunction = (\n session: ServiceSession,\n entries: InvokeAnyEntry[],\n facts?: InvocationFacts,\n) => ServiceHeaders;\n\n/**\n * Fetch request options - compatible with standard fetch API.\n */\nexport interface FetchRequestInit {\n method?: string;\n headers?: ServiceHeaders;\n body?: Blob | FormData | string;\n signal?: AbortSignal;\n}\n\n/**\n * Fetch response interface - compatible with standard Response.\n */\nexport interface FetchResponse {\n ok: boolean;\n status: number;\n statusText: string;\n body?: unknown;\n headers: {\n get(name: string): string | null;\n };\n json(): Promise<unknown>;\n text(): Promise<string>;\n arrayBuffer(): Promise<ArrayBuffer>;\n blob(): Promise<Blob>;\n}\n\n/**\n * Fetch function signature - allows for custom fetch implementations.\n * Compatible with both browser fetch and Node.js fetch.\n */\nexport type FetchFunction = (\n url: string,\n init?: FetchRequestInit,\n) => Promise<FetchResponse>;\n\n// =============================================================================\n// Retry Policy\n// =============================================================================\n\n/**\n * Configuration for automatic retry of failed requests.\n */\nexport interface RetryPolicy {\n /** Maximum number of attempts (including initial) */\n maxAttempts: number;\n /** Backoff strategy between retries */\n backoff: \"none\" | \"linear\" | \"exponential\";\n /** Base delay in milliseconds for backoff calculation */\n baseDelayMs: number;\n /** Maximum delay in milliseconds between retries */\n maxDelayMs: number;\n /** Error codes that should trigger a retry */\n retryableErrors: string[];\n}\n\n/**\n * Default retry policy.\n */\nexport const defaultRetryPolicy: RetryPolicy = {\n maxAttempts: 3,\n backoff: \"exponential\",\n baseDelayMs: 1000,\n maxDelayMs: 10000,\n retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT],\n};\n\n// =============================================================================\n// Service Context\n// =============================================================================\n\n/**\n * Event handler function type.\n */\nexport type EventHandler = (data: unknown) => void;\n\n/**\n * SDK telemetry event handler.\n */\nexport type TelemetryEventHandler = (event: string, data: unknown) => void;\n\n/**\n * Default-off telemetry configuration.\n */\nexport type TelemetryConfig =\n | boolean\n | {\n enabled?: boolean;\n onEvent?: TelemetryEventHandler;\n };\n\n/**\n * Service interface - base contract for all services.\n */\nexport interface IService {\n /** Initialize service with context */\n initialize(context: IServiceContext): void;\n\n /** Called when session changes (sign-in, sign-out, refresh) */\n onSessionChange(session: ServiceSession | null): void;\n\n /** Called when SDK signs out - should abort pending operations */\n onSignOut(): void;\n\n /** Service-specific configuration */\n readonly config: Record<string, unknown>;\n}\n\n/**\n * Context provided to services for accessing platform dependencies.\n * The SDK creates this context and passes it to services during initialization.\n */\nexport interface IServiceContext {\n // Session management\n /** Current active session, or null if not authenticated */\n readonly session: ServiceSession | null;\n /** Whether there is an active authenticated session */\n readonly isAuthenticated: boolean;\n\n // Platform dependencies (injected by SDK)\n /** Platform-specific invoke function from WASM binding */\n readonly invoke: InvokeFunction;\n /** Optional multi-resource invoke function */\n readonly invokeAny?: InvokeAnyFunction;\n /** Fetch function (defaults to globalThis.fetch) */\n readonly fetch: FetchFunction;\n /** Available TinyCloud host URLs */\n readonly hosts: string[];\n\n // Cross-service access\n /** Get another registered service by name */\n getService<T extends IService>(name: string): T | undefined;\n\n // Telemetry/Events\n /** Emit a telemetry event */\n emit(event: string, data: unknown): void;\n /** Subscribe to events */\n on(event: string, handler: EventHandler): () => void;\n\n // Lifecycle\n /** Abort signal that fires when SDK signs out */\n readonly abortSignal: AbortSignal;\n\n // Retry policy\n /** Retry policy for failed requests */\n readonly retryPolicy: RetryPolicy;\n}\n\n// =============================================================================\n// Telemetry Events\n// =============================================================================\n\n/**\n * Event emitted before a service request.\n */\nexport interface ServiceRequestEvent {\n service: string;\n action: string;\n span?: string;\n key?: string;\n timestamp: number;\n}\n\n/**\n * Event emitted after a service response.\n */\nexport interface ServiceResponseEvent {\n service: string;\n action: string;\n span?: string;\n ok: boolean;\n duration: number;\n durationMs?: number;\n status?: number;\n}\n\n/**\n * Event emitted on service error.\n */\nexport interface ServiceErrorEvent {\n service: string;\n span?: string;\n error: ServiceError;\n}\n\n/**\n * Event emitted on retry attempt.\n */\nexport interface ServiceRetryEvent {\n service: string;\n attempt: number;\n maxAttempts: number;\n error: ServiceError;\n}\n\n/**\n * Generic named span event for aggregating operation timings.\n */\nexport interface TelemetrySpanEvent {\n span: string;\n ok: boolean;\n durationMs: number;\n service?: string;\n action?: string;\n status?: number;\n error?: ServiceError;\n}\n\n/**\n * Telemetry event names.\n */\nexport const TelemetryEvents = {\n SPAN: \"telemetry.span\",\n SERVICE_REQUEST: \"service.request\",\n SERVICE_RESPONSE: \"service.response\",\n SERVICE_ERROR: \"service.error\",\n SERVICE_RETRY: \"service.retry\",\n SESSION_CHANGED: \"session.changed\",\n SESSION_EXPIRED: \"session.expired\",\n} as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Create a success result.\n */\nexport function ok<T>(data: T): Result<T> {\n return { ok: true, data };\n}\n\n/**\n * Create an error result.\n */\nexport function err<E = ServiceError>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\n/**\n * Create a ServiceError.\n */\nexport function serviceError(\n code: string,\n message: string,\n service: string,\n options?: { cause?: Error; meta?: Record<string, unknown> },\n): ServiceError {\n return {\n code,\n message,\n service,\n cause: options?.cause,\n meta: options?.meta,\n };\n}\n","/**\n * SDK Services - Error Utilities\n *\n * Utilities for creating and handling service errors.\n */\n\nimport { ServiceError, ErrorCodes, err, serviceError } from \"./types\";\n\n/**\n * Create a service error for authentication required.\n */\nexport function authRequiredError(service: string): ServiceError {\n return {\n code: ErrorCodes.AUTH_REQUIRED,\n message: \"Authentication required. Please sign in first.\",\n service,\n };\n}\n\n/**\n * Create a service error for expired authentication.\n */\nexport function authExpiredError(service: string): ServiceError {\n return {\n code: ErrorCodes.AUTH_EXPIRED,\n message: \"Session has expired. Please sign in again.\",\n service,\n };\n}\n\n/**\n * Create a service error for network issues.\n */\nexport function networkError(\n service: string,\n message: string,\n cause?: Error\n): ServiceError {\n return {\n code: ErrorCodes.NETWORK_ERROR,\n message,\n service,\n cause,\n };\n}\n\n/**\n * Create a service error for timeouts.\n */\nexport function timeoutError(service: string): ServiceError {\n return {\n code: ErrorCodes.TIMEOUT,\n message: \"Request timed out.\",\n service,\n };\n}\n\n/**\n * Create a service error for aborted requests.\n */\nexport function abortedError(service: string): ServiceError {\n return {\n code: ErrorCodes.ABORTED,\n message: \"Request was aborted.\",\n service,\n };\n}\n\n/**\n * Create a service error for not found resources.\n */\nexport function notFoundError(\n service: string,\n resource: string\n): ServiceError {\n return {\n code: ErrorCodes.NOT_FOUND,\n message: `Resource not found: ${resource}`,\n service,\n };\n}\n\n/**\n * Create a service error for permission denied.\n */\nexport function permissionDeniedError(\n service: string,\n action: string\n): ServiceError {\n return {\n code: ErrorCodes.PERMISSION_DENIED,\n message: `Permission denied for action: ${action}`,\n service,\n };\n}\n\n/**\n * Parse the server's \"Unauthorized Action: {resource} / {ability}\" pattern.\n */\nexport function parseAuthError(responseText: string): { resource?: string; action?: string } {\n const match = responseText.match(/^Unauthorized Action:\\s*(.+?)\\s*\\/\\s*(tinycloud\\.\\S+)$/m);\n if (match) {\n return { resource: match[1].trim(), action: match[2].trim() };\n }\n return {};\n}\n\n/**\n * Create a service error for unauthorized action (missing capability).\n */\nexport function authUnauthorizedError(\n service: string,\n message: string,\n meta?: Record<string, unknown>\n): ServiceError {\n return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });\n}\n\n/**\n * Create a service error for storage quota exceeded (402 Payment Required).\n */\nexport function storageQuotaExceededError(\n service: string,\n message: string,\n meta?: Record<string, unknown>\n): ServiceError {\n return {\n code: ErrorCodes.STORAGE_QUOTA_EXCEEDED,\n message,\n service,\n meta,\n };\n}\n\n/**\n * Create a service error for storage limit reached (413 Payload Too Large).\n */\nexport function storageLimitReachedError(\n service: string,\n message: string,\n meta?: Record<string, unknown>\n): ServiceError {\n return {\n code: ErrorCodes.STORAGE_LIMIT_REACHED,\n message,\n service,\n meta,\n };\n}\n\n/**\n * Wrap an unknown error in a ServiceError.\n */\nexport function wrapError(\n service: string,\n error: unknown,\n defaultCode: string = ErrorCodes.NETWORK_ERROR\n): ServiceError {\n if (error instanceof Error) {\n // Check for abort errors\n if (error.name === \"AbortError\") {\n return abortedError(service);\n }\n\n // Check for timeout errors (varies by platform)\n if (\n error.name === \"TimeoutError\" ||\n error.message.toLowerCase().includes(\"timeout\")\n ) {\n return timeoutError(service);\n }\n\n return {\n code: defaultCode,\n message: error.message,\n service,\n cause: error,\n };\n }\n\n return {\n code: defaultCode,\n message: String(error),\n service,\n };\n}\n\n/**\n * Create an error Result from a ServiceError.\n */\nexport function errorResult(error: ServiceError) {\n return err(error);\n}\n","/**\n * BaseService - Abstract base class for all TinyCloud services.\n *\n * Provides common functionality:\n * - Context management\n * - Session lifecycle hooks\n * - Abort signal handling\n * - Telemetry emission\n */\n\nimport {\n IService,\n IServiceContext,\n ServiceSession,\n ServiceError,\n TelemetryEvents,\n err,\n Result,\n} from \"../types\";\nimport { authRequiredError, wrapError } from \"../errors\";\n\n/**\n * Abstract base class for TinyCloud services.\n *\n * Services extend this class to get common functionality like\n * context management, session lifecycle, and abort handling.\n *\n * @example\n * ```typescript\n * class MyService extends BaseService implements IMyService {\n * static readonly serviceName = 'myservice';\n *\n * constructor(config: MyServiceConfig = {}) {\n * super();\n * this._config = config;\n * }\n *\n * async doSomething(): Promise<Result<Data>> {\n * if (!this.requireAuth()) {\n * return err(authRequiredError('myservice'));\n * }\n * // ... implementation\n * }\n * }\n * ```\n */\nexport abstract class BaseService implements IService {\n /**\n * Service identifier used for registration.\n * Must be overridden by subclasses.\n */\n static readonly serviceName: string;\n\n /**\n * Service context providing access to platform dependencies.\n * Set during initialize().\n */\n protected context!: IServiceContext;\n\n /**\n * Abort controller for this service's operations.\n * Reset on sign-out.\n */\n protected abortController: AbortController = new AbortController();\n\n /**\n * Service-specific configuration.\n */\n protected _config: Record<string, unknown> = {};\n\n /**\n * Get the service configuration.\n */\n get config(): Record<string, unknown> {\n return this._config;\n }\n\n /**\n * Initialize the service with context.\n * Called by the SDK after instantiation.\n *\n * @param context - The service context\n */\n initialize(context: IServiceContext): void {\n this.context = context;\n }\n\n /**\n * Called when session changes (sign-in, sign-out, refresh).\n * Override in subclasses to handle session changes.\n *\n * @param session - The new session, or null if signed out\n */\n onSessionChange(session: ServiceSession | null): void {\n // Override in subclass if needed\n }\n\n /**\n * Called when SDK signs out.\n * Aborts all pending operations.\n */\n onSignOut(): void {\n this.abortController.abort();\n this.abortController = new AbortController();\n }\n\n /**\n * Get the abort signal for this service.\n * Combines the service-level abort with context-level abort.\n */\n protected get abortSignal(): AbortSignal {\n return this.abortController.signal;\n }\n\n /**\n * Check if the service is authenticated.\n */\n protected get isAuthenticated(): boolean {\n return this.context?.isAuthenticated ?? false;\n }\n\n /**\n * Get the current session.\n * Throws if not authenticated.\n */\n protected get session(): ServiceSession {\n if (!this.context?.session) {\n throw new Error(\"Not authenticated\");\n }\n return this.context.session;\n }\n\n /**\n * Check authentication and return error result if not authenticated.\n * Use this at the start of methods that require authentication.\n *\n * @returns true if authenticated, false otherwise\n */\n protected requireAuth(): boolean {\n return this.isAuthenticated;\n }\n\n /**\n * Emit a telemetry event.\n *\n * @param event - Event name\n * @param data - Event data\n */\n protected emit(event: string, data: unknown): void {\n this.context?.emit(event, data);\n }\n\n /**\n * Emit a service request event.\n *\n * @param action - The action being performed\n * @param key - Optional key/path being accessed\n */\n protected emitRequest(action: string, key?: string): void {\n const service = this.getServiceName();\n this.emit(TelemetryEvents.SERVICE_REQUEST, {\n service,\n action,\n span: this.spanName(action),\n key,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Emit a service response event.\n *\n * @param action - The action that was performed\n * @param ok - Whether the request was successful\n * @param startTime - Start time for duration calculation\n * @param status - Optional HTTP status code\n */\n protected emitResponse(\n action: string,\n ok: boolean,\n startTime: number,\n status?: number\n ): void {\n const service = this.getServiceName();\n const durationMs = Date.now() - startTime;\n const span = this.spanName(action);\n this.emit(TelemetryEvents.SERVICE_RESPONSE, {\n service,\n action,\n span,\n ok,\n duration: durationMs,\n durationMs,\n status,\n });\n this.emit(TelemetryEvents.SPAN, {\n span,\n service,\n action,\n ok,\n durationMs,\n status,\n });\n }\n\n /**\n * Emit a service error event.\n *\n * @param error - The service error\n */\n protected emitError(error: ServiceError, action?: string): void {\n const span = action ? this.spanName(action) : undefined;\n this.emit(TelemetryEvents.SERVICE_ERROR, {\n service: this.getServiceName(),\n ...(span ? { span } : {}),\n error,\n });\n }\n\n /**\n * Get the service name from the static property.\n * Subclasses must define static serviceName.\n */\n protected getServiceName(): string {\n return (this.constructor as typeof BaseService).serviceName;\n }\n\n /**\n * Stable span name used by SDK telemetry sinks.\n */\n protected spanName(action: string): string {\n return `sdk.${this.getServiceName()}.${action}`;\n }\n\n /**\n * Create a combined abort signal from multiple sources.\n *\n * @param signals - Additional abort signals to combine\n * @returns A combined abort signal\n */\n protected combineSignals(...signals: (AbortSignal | undefined)[]): AbortSignal {\n const controller = new AbortController();\n const allSignals = [this.abortSignal, ...signals.filter(Boolean)] as AbortSignal[];\n\n for (const signal of allSignals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n return controller.signal;\n }\n signal.addEventListener(\"abort\", () => controller.abort(signal.reason), {\n once: true,\n });\n }\n\n return controller.signal;\n }\n\n /**\n * Wrap an operation with error handling and telemetry.\n *\n * @param action - The action name for telemetry\n * @param key - Optional key for telemetry\n * @param operation - The operation to execute\n * @returns Result of the operation\n */\n protected async withTelemetry<T>(\n action: string,\n key: string | undefined,\n operation: () => Promise<Result<T>>\n ): Promise<Result<T>> {\n const startTime = Date.now();\n this.emitRequest(action, key);\n\n try {\n const result = await operation();\n\n if (result.ok) {\n this.emitResponse(action, true, startTime);\n } else {\n this.emitResponse(action, false, startTime);\n this.emitError(result.error, action);\n }\n\n return result;\n } catch (error) {\n const serviceError = wrapError(this.getServiceName(), error);\n this.emitResponse(action, false, startTime);\n this.emitError(serviceError, action);\n return err(serviceError);\n }\n }\n}\n","/**\n * DatabaseHandle - Handle for operations on a specific named database.\n *\n * Delegates all operations to the parent SQLService with the database name.\n */\n\nimport type { Result } from \"../types\";\nimport type { IDatabaseHandle } from \"./ISQLService\";\nimport type { SQLService } from \"./SQLService\";\nimport type {\n SqlValue,\n SqlStatement,\n QueryResponse,\n ExecuteResponse,\n BatchResponse,\n QueryOptions,\n ExecuteOptions,\n BatchOptions,\n} from \"./types\";\n\nexport class DatabaseHandle implements IDatabaseHandle {\n private service: SQLService;\n public readonly name: string;\n\n constructor(service: SQLService, name: string) {\n this.service = service;\n this.name = name;\n }\n\n async query<T = Record<string, unknown>>(\n sql: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse<T>>> {\n return this.service.queryOnDb<T>(this.name, sql, params, options);\n }\n\n async execute(\n sql: string,\n params?: SqlValue[],\n options?: ExecuteOptions\n ): Promise<Result<ExecuteResponse>> {\n return this.service.executeOnDb(this.name, sql, params, options);\n }\n\n async batch(\n statements: SqlStatement[],\n options?: BatchOptions\n ): Promise<Result<BatchResponse>> {\n return this.service.batchOnDb(this.name, statements, options);\n }\n\n async executeStatement(\n name: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse | ExecuteResponse>> {\n return this.service.executeStatementOnDb(this.name, name, params, options);\n }\n\n async export(options?: QueryOptions): Promise<Result<Blob>> {\n return this.service.exportDb(this.name, options);\n }\n}\n","/**\n * SQL Service Types\n *\n * Type definitions for the SQL service operations.\n */\n\n/**\n * Configuration for SQLService.\n */\nexport interface SQLServiceConfig {\n /**\n * Default database name.\n * If not set, operations default to \"default\".\n */\n defaultDatabase?: string;\n\n /**\n * Default timeout in milliseconds for SQL operations.\n */\n timeout?: number;\n\n /** Allow additional config properties */\n [key: string]: unknown;\n}\n\n/**\n * Options for SQL query operations.\n */\nexport interface QueryOptions {\n /**\n * Custom abort signal for this operation.\n */\n signal?: AbortSignal;\n}\n\n/**\n * Options for SQL execute operations.\n */\nexport interface ExecuteOptions {\n /**\n * Schema initialization statements (CREATE TABLE IF NOT EXISTS ...).\n * Executed before the main statement on first write.\n */\n schema?: string[];\n\n /**\n * Custom abort signal for this operation.\n */\n signal?: AbortSignal;\n}\n\n/**\n * Options for SQL batch operations.\n */\nexport interface BatchOptions {\n /**\n * Custom abort signal for this operation.\n */\n signal?: AbortSignal;\n}\n\n/**\n * A SQL value: null, number, string, or binary data.\n */\nexport type SqlValue = null | number | string | Uint8Array;\n\n/**\n * A SQL statement with optional parameters.\n */\nexport interface SqlStatement {\n sql: string;\n params?: SqlValue[];\n}\n\n/**\n * Response from SQL query operations.\n */\nexport interface QueryResponse<T = Record<string, unknown>> {\n columns: string[];\n rows: T[][];\n rowCount: number;\n}\n\n/**\n * Response from SQL execute operations.\n */\nexport interface ExecuteResponse {\n changes: number;\n lastInsertRowId: number;\n}\n\n/**\n * Response from SQL batch operations.\n */\nexport interface BatchResponse {\n results: ExecuteResponse[];\n}\n\n/**\n * SQL service action types.\n */\nexport const SQLAction = {\n READ: \"tinycloud.sql/read\",\n WRITE: \"tinycloud.sql/write\",\n ADMIN: \"tinycloud.sql/admin\",\n SELECT: \"tinycloud.sql/select\",\n INSERT: \"tinycloud.sql/insert\",\n UPDATE: \"tinycloud.sql/update\",\n DELETE: \"tinycloud.sql/delete\",\n EXECUTE: \"tinycloud.sql/execute\",\n EXPORT: \"tinycloud.sql/export\",\n ALL: \"tinycloud.sql/*\",\n} as const;\n\nexport type SQLActionType = (typeof SQLAction)[keyof typeof SQLAction];\n","/**\n * SQLService - SQL database service implementation.\n *\n * Platform-agnostic SQL service that works with both web-sdk and node-sdk.\n * Uses dependency injection via IServiceContext for platform dependencies.\n */\n\nimport { BaseService } from \"../base/BaseService\";\nimport {\n Result,\n ok,\n err,\n ErrorCodes,\n serviceError,\n type FetchResponse,\n} from \"../types\";\nimport { authRequiredError, wrapError, parseAuthError } from \"../errors\";\nimport type { ISQLService } from \"./ISQLService\";\nimport type { IDatabaseHandle } from \"./ISQLService\";\nimport { DatabaseHandle } from \"./DatabaseHandle\";\nimport {\n type SQLServiceConfig,\n type QueryOptions,\n type ExecuteOptions,\n type BatchOptions,\n type SqlValue,\n type SqlStatement,\n type QueryResponse,\n type ExecuteResponse,\n type BatchResponse,\n SQLAction,\n} from \"./types\";\n\nexport class SQLService extends BaseService implements ISQLService {\n static readonly serviceName = \"sql\";\n\n declare protected _config: SQLServiceConfig;\n\n constructor(config: SQLServiceConfig = {}) {\n super();\n this._config = config;\n }\n\n get config(): SQLServiceConfig {\n return this._config;\n }\n\n private get defaultDbName(): string {\n return this._config.defaultDatabase ?? \"default\";\n }\n\n private get host(): string {\n return this.context.hosts[0];\n }\n\n /**\n * Get a handle to a named database.\n */\n db(name?: string): IDatabaseHandle {\n return new DatabaseHandle(this, name ?? this.defaultDbName);\n }\n\n /**\n * Shortcut: query the default database.\n */\n async query<T = Record<string, unknown>>(\n sql: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse<T>>> {\n return this.queryOnDb<T>(this.defaultDbName, sql, params, options);\n }\n\n /**\n * Shortcut: execute on the default database.\n */\n async execute(\n sql: string,\n params?: SqlValue[],\n options?: ExecuteOptions\n ): Promise<Result<ExecuteResponse>> {\n return this.executeOnDb(this.defaultDbName, sql, params, options);\n }\n\n /**\n * Shortcut: batch on the default database.\n */\n async batch(\n statements: SqlStatement[],\n options?: BatchOptions\n ): Promise<Result<BatchResponse>> {\n return this.batchOnDb(this.defaultDbName, statements, options);\n }\n\n // === Internal methods called by DatabaseHandle ===\n\n async queryOnDb<T = Record<string, unknown>>(\n dbName: string,\n sql: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse<T>>> {\n return this.withTelemetry(\"query\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const response = await this.invokeSQL(\n dbName,\n this.actionForSql(sql, SQLAction.READ),\n { action: \"query\", sql, params: params ?? [] },\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"query\");\n }\n\n const data = (await response.json()) as QueryResponse<T>;\n return ok(data);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n async executeOnDb(\n dbName: string,\n sql: string,\n params?: SqlValue[],\n options?: ExecuteOptions\n ): Promise<Result<ExecuteResponse>> {\n return this.withTelemetry(\"execute\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const body: Record<string, unknown> = {\n action: \"execute\",\n sql,\n params: params ?? [],\n };\n if (options?.schema) {\n body.schema = options.schema;\n }\n\n const response = await this.invokeSQL(\n dbName,\n this.actionForSql(sql, SQLAction.WRITE),\n body,\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"execute\");\n }\n\n const data = (await response.json()) as ExecuteResponse;\n return ok(data);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n async batchOnDb(\n dbName: string,\n statements: SqlStatement[],\n options?: BatchOptions\n ): Promise<Result<BatchResponse>> {\n return this.withTelemetry(\"batch\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const response = await this.invokeSQL(\n dbName,\n this.actionForSqlBatch(statements),\n { action: \"batch\", statements },\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"batch\");\n }\n\n const data = (await response.json()) as BatchResponse;\n return ok(data);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n async executeStatementOnDb(\n dbName: string,\n name: string,\n params?: SqlValue[],\n options?: QueryOptions\n ): Promise<Result<QueryResponse | ExecuteResponse>> {\n return this.withTelemetry(\"executeStatement\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const response = await this.invokeSQL(\n dbName,\n SQLAction.EXECUTE,\n { action: \"execute_statement\", name, params: params ?? [] },\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"executeStatement\");\n }\n\n const data = (await response.json()) as\n | QueryResponse\n | ExecuteResponse;\n return ok(data);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n async exportDb(\n dbName: string,\n options?: QueryOptions\n ): Promise<Result<Blob>> {\n return this.withTelemetry(\"export\", dbName, async () => {\n if (!this.requireAuth()) {\n return err(authRequiredError(\"sql\"));\n }\n\n try {\n const response = await this.invokeSQL(\n dbName,\n SQLAction.EXPORT,\n { action: \"export\" },\n options?.signal\n );\n\n if (!response.ok) {\n return this.handleErrorResponse(response, \"export\");\n }\n\n // FetchResponse doesn't expose blob(), so access it from the\n // underlying response which is a standard fetch Response at runtime\n const resp = response as any;\n if (typeof resp.blob === \"function\") {\n const blob = await resp.blob();\n return ok(blob as Blob);\n }\n // If blob() is not available, return the raw text as a Blob-like\n const text = await response.text();\n return ok(text as unknown as Blob);\n } catch (error) {\n return err(wrapError(\"sql\", error));\n }\n });\n }\n\n // === Private helpers ===\n\n private async invokeSQL(\n dbName: string,\n action: string,\n body: Record<string, unknown>,\n signal?: AbortSignal\n ): Promise<FetchResponse> {\n const session = this.context.session!;\n const headers = this.context.invoke(session, \"sql\", dbName, action);\n\n return this.context.fetch(`${this.host}/invoke`, {\n method: \"POST\",\n headers: {\n ...(headers as Record<string, string>),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body) as any,\n signal: this.combineSignals(signal),\n });\n }\n\n private actionForSql(sql: string, fallback: string): string {\n return firstSqlToken(sql) === \"pragma\" ? SQLAction.ADMIN : fallback;\n }\n\n private actionForSqlBatch(statements: SqlStatement[]): string {\n return statements.some(\n (statement) => this.actionForSql(statement.sql, SQLAction.WRITE) === SQLAction.ADMIN,\n )\n ? SQLAction.ADMIN\n : SQLAction.WRITE;\n }\n\n private async handleErrorResponse(\n response: FetchResponse,\n operation: string\n ): Promise<Result<never>> {\n const errorText = await response.text();\n\n let errorBody: { error?: string; message?: string; code?: string } = {};\n try {\n errorBody = JSON.parse(errorText);\n } catch {\n // Not JSON\n }\n\n const errorCode = this.mapHttpStatusToErrorCode(\n response.status,\n errorBody.error\n );\n const message =\n errorBody.message ||\n `SQL ${operation} failed: ${response.status} - ${errorText}`;\n\n const meta: Record<string, unknown> = { status: response.status, statusText: response.statusText };\n\n if (response.status === 401) {\n const { resource, action } = parseAuthError(errorText);\n if (action) meta.requiredAction = action;\n if (resource) meta.resource = resource;\n }\n\n return err(\n serviceError(errorCode, message, \"sql\", { meta })\n );\n }\n\n private mapHttpStatusToErrorCode(\n status: number,\n serverError?: string\n ): string {\n switch (status) {\n case 400:\n return ErrorCodes.SQL_ERROR;\n case 401:\n return ErrorCodes.AUTH_UNAUTHORIZED;\n case 403:\n if (serverError === \"sql_readonly_violation\") {\n return ErrorCodes.SQL_READONLY_VIOLATION;\n }\n return ErrorCodes.SQL_PERMISSION_DENIED;\n case 404:\n return ErrorCodes.SQL_DATABASE_NOT_FOUND;\n case 413:\n return ErrorCodes.SQL_RESPONSE_TOO_LARGE;\n case 429:\n return ErrorCodes.SQL_QUOTA_EXCEEDED;\n default:\n return ErrorCodes.NETWORK_ERROR;\n }\n }\n}\n\nfunction firstSqlToken(sql: string): string | undefined {\n let index = 0;\n\n while (index < sql.length) {\n while (index < sql.length && /\\s/.test(sql[index])) {\n index++;\n }\n\n if (sql.startsWith(\"--\", index)) {\n const newline = sql.indexOf(\"\\n\", index + 2);\n if (newline === -1) {\n return undefined;\n }\n index = newline + 1;\n continue;\n }\n\n if (sql.startsWith(\"/*\", index)) {\n const end = sql.indexOf(\"*/\", index + 2);\n if (end === -1) {\n return undefined;\n }\n index = end + 2;\n continue;\n }\n\n break;\n }\n\n const match = /^[A-Za-z_]+/.exec(sql.slice(index));\n return match?.[0].toLowerCase();\n}\n"],"mappings":"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","names":["ok","serviceError"]}
@@ -1,4 +1,4 @@
1
- import { e as Result, c as IService, B as BaseService } from '../BaseService-DZ2hBJeD.cjs';
1
+ import { e as Result, c as IService, B as BaseService } from '../BaseService-BdRvNm2s.cjs';
2
2
 
3
3
  /**
4
4
  * SQL Service Types
@@ -204,6 +204,8 @@ declare class SQLService extends BaseService implements ISQLService {
204
204
  executeStatementOnDb(dbName: string, name: string, params?: SqlValue[], options?: QueryOptions): Promise<Result<QueryResponse | ExecuteResponse>>;
205
205
  exportDb(dbName: string, options?: QueryOptions): Promise<Result<Blob>>;
206
206
  private invokeSQL;
207
+ private actionForSql;
208
+ private actionForSqlBatch;
207
209
  private handleErrorResponse;
208
210
  private mapHttpStatusToErrorCode;
209
211
  }
@@ -1,4 +1,4 @@
1
- import { e as Result, c as IService, B as BaseService } from '../BaseService-DZ2hBJeD.js';
1
+ import { e as Result, c as IService, B as BaseService } from '../BaseService-BdRvNm2s.js';
2
2
 
3
3
  /**
4
4
  * SQL Service Types
@@ -204,6 +204,8 @@ declare class SQLService extends BaseService implements ISQLService {
204
204
  executeStatementOnDb(dbName: string, name: string, params?: SqlValue[], options?: QueryOptions): Promise<Result<QueryResponse | ExecuteResponse>>;
205
205
  exportDb(dbName: string, options?: QueryOptions): Promise<Result<Blob>>;
206
206
  private invokeSQL;
207
+ private actionForSql;
208
+ private actionForSqlBatch;
207
209
  private handleErrorResponse;
208
210
  private mapHttpStatusToErrorCode;
209
211
  }
package/dist/sql/index.js CHANGED
@@ -43,6 +43,7 @@ var defaultRetryPolicy = {
43
43
  retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT]
44
44
  };
45
45
  var TelemetryEvents = {
46
+ SPAN: "telemetry.span",
46
47
  SERVICE_REQUEST: "service.request",
47
48
  SERVICE_RESPONSE: "service.response",
48
49
  SERVICE_ERROR: "service.error",
@@ -209,9 +210,11 @@ var BaseService = class {
209
210
  * @param key - Optional key/path being accessed
210
211
  */
211
212
  emitRequest(action, key) {
213
+ const service = this.getServiceName();
212
214
  this.emit(TelemetryEvents.SERVICE_REQUEST, {
213
- service: this.getServiceName(),
215
+ service,
214
216
  action,
217
+ span: this.spanName(action),
215
218
  key,
216
219
  timestamp: Date.now()
217
220
  });
@@ -225,11 +228,24 @@ var BaseService = class {
225
228
  * @param status - Optional HTTP status code
226
229
  */
227
230
  emitResponse(action, ok2, startTime, status) {
231
+ const service = this.getServiceName();
232
+ const durationMs = Date.now() - startTime;
233
+ const span = this.spanName(action);
228
234
  this.emit(TelemetryEvents.SERVICE_RESPONSE, {
229
- service: this.getServiceName(),
235
+ service,
230
236
  action,
237
+ span,
231
238
  ok: ok2,
232
- duration: Date.now() - startTime,
239
+ duration: durationMs,
240
+ durationMs,
241
+ status
242
+ });
243
+ this.emit(TelemetryEvents.SPAN, {
244
+ span,
245
+ service,
246
+ action,
247
+ ok: ok2,
248
+ durationMs,
233
249
  status
234
250
  });
235
251
  }
@@ -238,9 +254,11 @@ var BaseService = class {
238
254
  *
239
255
  * @param error - The service error
240
256
  */
241
- emitError(error) {
257
+ emitError(error, action) {
258
+ const span = action ? this.spanName(action) : void 0;
242
259
  this.emit(TelemetryEvents.SERVICE_ERROR, {
243
260
  service: this.getServiceName(),
261
+ ...span ? { span } : {},
244
262
  error
245
263
  });
246
264
  }
@@ -251,6 +269,12 @@ var BaseService = class {
251
269
  getServiceName() {
252
270
  return this.constructor.serviceName;
253
271
  }
272
+ /**
273
+ * Stable span name used by SDK telemetry sinks.
274
+ */
275
+ spanName(action) {
276
+ return `sdk.${this.getServiceName()}.${action}`;
277
+ }
254
278
  /**
255
279
  * Create a combined abort signal from multiple sources.
256
280
  *
@@ -288,13 +312,13 @@ var BaseService = class {
288
312
  this.emitResponse(action, true, startTime);
289
313
  } else {
290
314
  this.emitResponse(action, false, startTime);
291
- this.emitError(result.error);
315
+ this.emitError(result.error, action);
292
316
  }
293
317
  return result;
294
318
  } catch (error) {
295
319
  const serviceError2 = wrapError(this.getServiceName(), error);
296
320
  this.emitResponse(action, false, startTime);
297
- this.emitError(serviceError2);
321
+ this.emitError(serviceError2, action);
298
322
  return err(serviceError2);
299
323
  }
300
324
  }
@@ -385,7 +409,7 @@ var SQLService = class extends BaseService {
385
409
  try {
386
410
  const response = await this.invokeSQL(
387
411
  dbName,
388
- SQLAction.READ,
412
+ this.actionForSql(sql, SQLAction.READ),
389
413
  { action: "query", sql, params: params ?? [] },
390
414
  options?.signal
391
415
  );
@@ -415,7 +439,7 @@ var SQLService = class extends BaseService {
415
439
  }
416
440
  const response = await this.invokeSQL(
417
441
  dbName,
418
- SQLAction.WRITE,
442
+ this.actionForSql(sql, SQLAction.WRITE),
419
443
  body,
420
444
  options?.signal
421
445
  );
@@ -437,7 +461,7 @@ var SQLService = class extends BaseService {
437
461
  try {
438
462
  const response = await this.invokeSQL(
439
463
  dbName,
440
- SQLAction.WRITE,
464
+ this.actionForSqlBatch(statements),
441
465
  { action: "batch", statements },
442
466
  options?.signal
443
467
  );
@@ -514,6 +538,14 @@ var SQLService = class extends BaseService {
514
538
  signal: this.combineSignals(signal)
515
539
  });
516
540
  }
541
+ actionForSql(sql, fallback) {
542
+ return firstSqlToken(sql) === "pragma" ? SQLAction.ADMIN : fallback;
543
+ }
544
+ actionForSqlBatch(statements) {
545
+ return statements.some(
546
+ (statement) => this.actionForSql(statement.sql, SQLAction.WRITE) === SQLAction.ADMIN
547
+ ) ? SQLAction.ADMIN : SQLAction.WRITE;
548
+ }
517
549
  async handleErrorResponse(response, operation) {
518
550
  const errorText = await response.text();
519
551
  let errorBody = {};
@@ -559,6 +591,33 @@ var SQLService = class extends BaseService {
559
591
  }
560
592
  };
561
593
  SQLService.serviceName = "sql";
594
+ function firstSqlToken(sql) {
595
+ let index = 0;
596
+ while (index < sql.length) {
597
+ while (index < sql.length && /\s/.test(sql[index])) {
598
+ index++;
599
+ }
600
+ if (sql.startsWith("--", index)) {
601
+ const newline = sql.indexOf("\n", index + 2);
602
+ if (newline === -1) {
603
+ return void 0;
604
+ }
605
+ index = newline + 1;
606
+ continue;
607
+ }
608
+ if (sql.startsWith("/*", index)) {
609
+ const end = sql.indexOf("*/", index + 2);
610
+ if (end === -1) {
611
+ return void 0;
612
+ }
613
+ index = end + 2;
614
+ continue;
615
+ }
616
+ break;
617
+ }
618
+ const match = /^[A-Za-z_]+/.exec(sql.slice(index));
619
+ return match?.[0].toLowerCase();
620
+ }
562
621
  export {
563
622
  DatabaseHandle,
564
623
  SQLAction,