@tinycloud/sdk-services 3.11.0-beta.2 → 3.12.0-beta.10

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.
Files changed (71) hide show
  1. package/dist/{BaseService-Ma2MwJAd.d.ts → BaseService-BRDG69KR.d.ts} +1 -1
  2. package/dist/{BaseService-O0iyuFLk.d.cts → BaseService-CdBkfPGE.d.cts} +1 -1
  3. package/dist/{IHooksService-4QNODVy6.d.cts → IHooksService-C9p3stsB.d.cts} +1 -1
  4. package/dist/{IHooksService-CvTpJbQD.d.ts → IHooksService-D6eyZat1.d.ts} +1 -1
  5. package/dist/{ISQLService-Aly1CBsW.d.ts → ISQLService-B_4KcGA-.d.ts} +1 -1
  6. package/dist/{ISQLService-BOgWzHLT.d.cts → ISQLService-C6nNpcls.d.cts} +1 -1
  7. package/dist/context.cjs +4 -0
  8. package/dist/context.cjs.map +1 -1
  9. package/dist/context.d.cts +1 -1
  10. package/dist/context.d.ts +1 -1
  11. package/dist/context.js +4 -0
  12. package/dist/context.js.map +1 -1
  13. package/dist/encryption/index.cjs +276 -14
  14. package/dist/encryption/index.cjs.map +1 -1
  15. package/dist/encryption/index.d.cts +26 -28
  16. package/dist/encryption/index.d.ts +26 -28
  17. package/dist/encryption/index.js +268 -14
  18. package/dist/encryption/index.js.map +1 -1
  19. package/dist/encryption/named-keys.cjs +225 -14
  20. package/dist/encryption/named-keys.cjs.map +1 -1
  21. package/dist/encryption/named-keys.d.cts +7 -2
  22. package/dist/encryption/named-keys.d.ts +7 -2
  23. package/dist/encryption/named-keys.js +225 -14
  24. package/dist/encryption/named-keys.js.map +1 -1
  25. package/dist/encryption/resource.cjs.map +1 -1
  26. package/dist/encryption/resource.js.map +1 -1
  27. package/dist/feed/index.cjs +4 -0
  28. package/dist/feed/index.cjs.map +1 -1
  29. package/dist/feed/index.d.cts +3 -3
  30. package/dist/feed/index.d.ts +3 -3
  31. package/dist/feed/index.js +4 -0
  32. package/dist/feed/index.js.map +1 -1
  33. package/dist/index.cjs +509 -18
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +14 -12
  36. package/dist/index.d.ts +14 -12
  37. package/dist/index.js +499 -18
  38. package/dist/index.js.map +1 -1
  39. package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
  40. package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
  41. package/dist/kv/index.cjs +34 -0
  42. package/dist/kv/index.cjs.map +1 -1
  43. package/dist/kv/index.d.cts +4 -4
  44. package/dist/kv/index.d.ts +4 -4
  45. package/dist/kv/index.js +34 -0
  46. package/dist/kv/index.js.map +1 -1
  47. package/dist/kv/replication/index.cjs +39 -8
  48. package/dist/kv/replication/index.cjs.map +1 -1
  49. package/dist/kv/replication/index.d.cts +3 -3
  50. package/dist/kv/replication/index.d.ts +3 -3
  51. package/dist/kv/replication/index.js +38 -7
  52. package/dist/kv/replication/index.js.map +1 -1
  53. package/dist/{scope-DEgc9f-W.d.cts → scope-DtcbyRKY.d.cts} +1 -1
  54. package/dist/{scope-BZr0QwxX.d.ts → scope-J20PQP3u.d.ts} +1 -1
  55. package/dist/sql/index.cjs +36 -0
  56. package/dist/sql/index.cjs.map +1 -1
  57. package/dist/sql/index.d.cts +4 -4
  58. package/dist/sql/index.d.ts +4 -4
  59. package/dist/sql/index.js +36 -0
  60. package/dist/sql/index.js.map +1 -1
  61. package/dist/telemetry/requestLog.cjs +4 -0
  62. package/dist/telemetry/requestLog.cjs.map +1 -1
  63. package/dist/telemetry/requestLog.d.cts +1 -1
  64. package/dist/telemetry/requestLog.d.ts +1 -1
  65. package/dist/telemetry/requestLog.js +4 -0
  66. package/dist/telemetry/requestLog.js.map +1 -1
  67. package/dist/{types-D4XbIfFJ.d.cts → types-DfqNz9Nu.d.cts} +135 -6
  68. package/dist/{types-CSd0RYir.d.ts → types-DjAP8dve.d.cts} +4 -0
  69. package/dist/{types-CSd0RYir.d.cts → types-DjAP8dve.d.ts} +4 -0
  70. package/dist/{types-Bf91gup6.d.ts → types-Nvi_RckJ.d.ts} +135 -6
  71. package/package.json +4 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/diagnostics.ts","../src/debug.ts","../src/responseErrors.ts","../src/errors.ts","../src/sleep.ts","../src/context.ts"],"sourcesContent":["/**\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 * A node-supplied permission hint after strict SDK validation. This is\n * deliberately narrower than a general capability: no wildcards, caveats,\n * descriptions, or transport metadata can cross the service boundary.\n */\nexport interface PermissionHint {\n readonly service: \"tinycloud.kv\" | \"tinycloud.encryption\";\n readonly space?: string;\n readonly path: string;\n readonly actions: readonly string[];\n}\n\n/**\n * Storage quota information returned with quota-related errors.\n * @deprecated Read `usedBytes`, `limitBytes` and `account` from the error's\n * `meta`, or use `StorageFullEvent` from `tc.on(\"storage.full\", handler)`.\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 /** HTTP 401: the delegation the invocation cites was revoked. */\n AUTH_DELEGATION_REVOKED: \"AUTH_DELEGATION_REVOKED\",\n /** HTTP 401: a delegation the cited one rests on was revoked. */\n AUTH_DELEGATION_ANCESTOR_REVOKED: \"AUTH_DELEGATION_ANCESTOR_REVOKED\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n TIMEOUT: \"TIMEOUT\",\n ABORTED: \"ABORTED\",\n INVALID_INPUT: \"INVALID_INPUT\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n\n // Node retry signals (tinycloud-node #347). `meta.retryable` says whether a\n // retry is safe; `meta.retryAfterMs` carries the node's Retry-After.\n /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`/`read_audit_busy`: nothing was applied; retry after `meta.retryAfterMs`. */\n NODE_BUSY: \"NODE_BUSY\",\n /** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */\n REQUEST_TIMEOUT: \"REQUEST_TIMEOUT\",\n /** HTTP 500 `outcome_unknown`: a write may have been applied; read back before retrying a non-idempotent operation. */\n OUTCOME_UNKNOWN: \"OUTCOME_UNKNOWN\",\n\n // KV-specific errors\n KV_NOT_FOUND: \"KV_NOT_FOUND\",\n KV_WRITE_FAILED: \"KV_WRITE_FAILED\",\n KV_PRECONDITION_FAILED: \"KV_PRECONDITION_FAILED\",\n KV_CONFLICT: \"KV_CONFLICT\",\n KV_RESPONSE_TOO_LARGE: \"KV_RESPONSE_TOO_LARGE\",\n /** HTTP 410 from the change feed: discard sync state and restart without a cursor; `meta.reason` says why. */\n KV_SYNC_RESET_REQUIRED: \"KV_SYNC_RESET_REQUIRED\",\n /** HTTP 403 from the change feed: the retention grant was refused; `meta.reason` says why. */\n KV_RETENTION_GRANT_REFUSED: \"KV_RETENTION_GRANT_REFUSED\",\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 /** The node answered the usage read without usage numbers (it predates the read). */\n STORAGE_STATUS_UNAVAILABLE: \"STORAGE_STATUS_UNAVAILABLE\",\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 /** Capability caveats that must remain on this invocation attenuation. */\n caveats?: Record<string, unknown>[];\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 redirect?: \"error\";\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 /** Present on standard Responses; the retry reads a clone so the caller's body stays intact. */\n clone?(): FetchResponse;\n /** Set by `ServiceContext.invokeWithRetry` on the response it returns: how many requests it took. */\n retry?: RetryStats;\n}\n\n/** What automatic retry cost one request, so latency can be split from backoff. */\nexport interface RetryStats {\n /** Requests sent, including the first (1 means no retry). */\n attempts: number;\n /** Total milliseconds spent waiting between attempts. */\n backoffMs: number;\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 * Automatic retry of node responses that guarantee nothing was applied\n * (`NODE_BUSY`, `REQUEST_TIMEOUT`). Applied once, in the service context's\n * `invokeWithRetry`, which re-mints the signed invocation per attempt. Backoff\n * is full-jitter: each delay is uniform in `[0, step]`, where\n * `step` is the strategy's delay capped at `maxDelayMs`. A node `Retry-After`\n * replaces the delay. Opt out with `{ maxAttempts: 1 }`.\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 /** Node error codes that trigger a retry. `OUTCOME_UNKNOWN` is never retried. */\n retryableErrors: string[];\n /** Total time budget for all attempts and waits, in milliseconds. Unset: no cap. */\n maxTotalMs?: number;\n}\n\n/**\n * Default retry policy.\n */\nexport const defaultRetryPolicy: RetryPolicy = {\n maxAttempts: 4,\n backoff: \"exponential\",\n baseDelayMs: 250,\n maxDelayMs: 4000,\n retryableErrors: [ErrorCodes.NODE_BUSY, ErrorCodes.REQUEST_TIMEOUT],\n maxTotalMs: 12000,\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 /** Send a signed invocation, retrying a busy node; `build` re-signs per attempt. Absent: sends once. */\n invokeWithRetry?(\n url: string,\n build: () => FetchRequestInit | Promise<FetchRequestInit>,\n ): Promise<FetchResponse>;\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 /** Additional abort signal for a scoped operation, when active. */\n readonly operationAbortSignal?: AbortSignal;\n /** Install an additional signal for the duration of a scoped operation. */\n setOperationAbortSignal?(signal: AbortSignal | undefined): void;\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 REPLICATION_READ: \"replication.read\",\n REPLICATION_WRITE: \"replication.write\",\n REPLICATION_SYNC: \"replication.sync\",\n REPLICATION_STATE: \"replication.state\",\n REPLICATION_DIVERGENCE: \"replication.divergence\",\n REPLICATION_GRANT: \"replication.grant\",\n REPLICATION_LIFECYCLE: \"replication.lifecycle\",\n /** Emitted once, on the first write rejected because storage is full. */\n STORAGE_FULL: \"storage.full\",\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","/** Safe projections for diagnostic sinks. */\n\nconst REDACTED = \"[REDACTED]\";\nconst SAFE_NUMBER_FIELDS = new Set([\n \"duration\",\n \"durationMs\",\n \"endedAt\",\n \"startedAt\",\n \"status\",\n \"timestamp\",\n \"latencyMs\",\n \"stalenessMs\",\n \"count\",\n \"changes\",\n \"deleted\",\n \"fetched\",\n \"pages\",\n \"lagMs\",\n \"pendingCleared\",\n \"partitions\",\n \"remaining\",\n \"expiryMs\",\n]);\nconst SAFE_BOOLEAN_FIELDS = new Set([\"authenticated\", \"ok\", \"persisted\", \"purgePending\"]);\nconst SAFE_REPLICATION_FIELDS: Record<string, readonly string[]> = {\n source: [\"replica\", \"network\", \"none\"],\n reason: [\"hit\", \"absent\", \"deleted\", \"content_missing\", \"coverage_incomplete\", \"not_covered\", \"pending_write\", \"grant_missing\", \"grant_expired\", \"grant_revoked\", \"grant_not_yet_valid\", \"stale\", \"replica_error\", \"runtime_unsupported\", \"replica_unavailable\", \"unsupported_option\", \"network_cursor\", \"cursor_restart\", \"aborted\", \"REPLICA_BEHIND_OWN_WRITES\", \"REPLICA_UNPROVEN_SINCE_START\", \"NETWORK_REQUESTED\", \"replica_gated\", \"sign_out\", \"account_switch\", \"consent_expired\", \"startup\"],\n op: [\"get\", \"list\", \"put\", \"delete\", \"batchPut\"],\n trigger: [\"start\", \"interval\", \"stale_read\", \"manual\", \"peer\"],\n origin: [\"peer\"],\n outcome: [\"found\", \"not_found\", \"error\", \"committed\", \"failed\", \"ambiguous\", \"ok\", \"busy\", \"aborted\"],\n class: [\"offline\", \"authority\", \"storage\", \"node\", \"lifecycle\"],\n state: [\"opened\", \"grant_installed\", \"grant_missing\", \"runtime_unsupported\", \"unavailable\", \"revoked\", \"recreated\", \"purged\", \"purge_failed\", \"pending_store_error\", \"pinned\", \"pending_cleared\", \"closed\", \"idle\", \"ready\", \"gated\"],\n action: [\"minted\", \"renewed\", \"renewal_refused\", \"renewal_deferred\", \"capped\", \"expired\", \"consent_deadline_reached\", \"denied\", \"deny_stale\", \"deny_failed\", \"database_deleted\", \"marker_set\", \"marker_cleared\", \"unguarded\", \"drained\", \"erased\", \"erase_failed\", \"activation_blocked\", \"activated\", \"recovered\"],\n gate: [\"not_established\", \"stamp_stale\", \"principal_mismatch\", \"purge_pending\", \"logout_pending\", \"gate_unavailable\"],\n kind: [\"value\", \"missing_local\", \"extra_local\", \"keys\", \"order\"],\n coverage: [\"empty\", \"bootstrapping\", \"complete\"],\n authority: [\"valid\", \"expired\", \"revoked\", \"not-yet-valid\"],\n code: [\"NETWORK_ERROR\", \"TIMEOUT\", \"ABORTED\", \"REPLICA_BUSY\", \"REPLICA_BEHIND_OWN_WRITES\", \"REPLICA_UNPROVEN_SINCE_START\", \"NETWORK_REQUESTED\", \"INTEGRITY_ERROR\", \"STORAGE_ERROR\", \"REPLICA_CLOSED\", \"RUNTIME_UNSUPPORTED\", \"GRANT_EXPIRED\", \"GRANT_REVOKED\", \"GRANT_NOT_YET_VALID\", \"GRANT_MISSING\", \"GRANT_INVALID\", \"SOURCE_CHANGED\", \"PROTOCOL_ERROR\", \"SCOPE_VIOLATION\", \"NODE_ERROR\", \"CONTENT_MISMATCH\", \"STORAGE_FULL\", \"REPLICA_GATED\", \"RESET_REQUIRED\", \"STORAGE_BUDGET\", \"CONSENT_EXPIRED\"],\n pendingState: [\"in_flight\", \"committed\", \"ambiguous\"],\n strategy: [\"session\", \"minted\", \"installed\"],\n verify: [\"match\", \"diverged\", \"error\", \"aborted\"],\n};\n\nfunction finiteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction isBinaryData(value: object): boolean {\n try {\n return (\n Array.isArray(value) ||\n ArrayBuffer.isView(value) ||\n value instanceof ArrayBuffer\n );\n } catch {\n // A revoked Proxy can throw while checking its shape. It is not diagnostic\n // data and must not prevent the SDK operation that emitted it.\n return true;\n }\n}\n\nfunction read(value: object, key: string): unknown {\n try {\n return (value as Record<string, unknown>)[key];\n } catch {\n return REDACTED;\n }\n}\n\n/** Return only stable, non-sensitive error diagnostics. */\nexport function projectDiagnosticError(error: unknown): Record<string, unknown> {\n if (typeof error !== \"object\" || error === null) return {};\n\n const status = finiteNumber(read(error, \"status\"));\n return status !== undefined && status >= 100 && status <= 599 ? { status } : {};\n}\n\n/**\n * Copy only fixed scalar metrics and URL origins. Diagnostic payloads can be\n * supplied by applications and nodes, so strings, nested values, and unknown\n * fields are never projected to debug or telemetry sinks.\n */\nexport function projectDiagnosticData(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value !== \"object\") return REDACTED;\n\n try {\n if (isBinaryData(value)) return REDACTED;\n\n const projected: Record<string, unknown> = {};\n for (const key of SAFE_NUMBER_FIELDS) {\n const number = finiteNumber(read(value, key));\n if (number !== undefined) projected[key] = number;\n }\n for (const key of SAFE_BOOLEAN_FIELDS) {\n const boolean = read(value, key);\n if (typeof boolean === \"boolean\") projected[key] = boolean;\n }\n for (const [key, allowed] of Object.entries(SAFE_REPLICATION_FIELDS)) {\n const candidate = read(value, key);\n if (typeof candidate === \"string\" && allowed.includes(candidate)) {\n projected[key] = candidate;\n }\n }\n\n\n // URLs, like every other string-bearing value, are never projected. Even\n // an origin can encode user-controlled data in a hostname.\n const url = read(value, \"url\");\n if (url !== undefined) projected.url = REDACTED;\n\n const error = read(value, \"error\");\n if (error !== undefined) projected.error = projectDiagnosticError(error);\n\n return projected;\n } catch {\n // Projection is best-effort only. Hostile Proxy traps and exotic objects\n // must never block telemetry, debug logging, or ordinary subscribers.\n return REDACTED;\n }\n}\n","import { projectDiagnosticData } from \"./diagnostics\";\n\nexport type TinyCloudDebugLevel = \"debug\";\n\nexport interface TinyCloudDebugEvent {\n sequence: number;\n timestamp: number;\n timestampIso: string;\n level: TinyCloudDebugLevel;\n event: string;\n message?: string;\n data?: unknown;\n durationMs?: number;\n startedAt?: number;\n endedAt?: number;\n}\n\nexport interface TinyCloudDebugEnableOptions {\n /**\n * Persist the debug flag in browser localStorage. Defaults to true.\n * Node runtimes intentionally do not persist this setting.\n */\n persist?: boolean;\n}\n\nexport interface TinyCloudDebugTimer {\n stop: (data?: unknown) => TinyCloudDebugEvent | undefined;\n}\n\ntype TinyCloudDebugGlobal = typeof globalThis & {\n TinyCloud_debug?: boolean | string;\n TinyCloudDebug?: TinyCloudDebugLogger;\n window?: unknown;\n enableTinyCloudDebug?: typeof enableTinyCloudDebug;\n disableTinyCloudDebug?: typeof disableTinyCloudDebug;\n getTinyCloudDebugLogs?: typeof getTinyCloudDebugLogs;\n clearTinyCloudDebugLogs?: typeof clearTinyCloudDebugLogs;\n};\n\ninterface TinyCloudDebugStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nconst DEBUG_FLAG = \"TinyCloud_debug\";\nconst MAX_EVENTS = 1000;\n\nfunction getGlobal(): TinyCloudDebugGlobal {\n return globalThis as TinyCloudDebugGlobal;\n}\n\nfunction nowMs(): number {\n const performanceNow = globalThis.performance?.now?.bind(globalThis.performance);\n return typeof performanceNow === \"function\" ? performanceNow() : Date.now();\n}\n\nfunction isTrue(value: unknown): boolean {\n return value === true || value === \"true\" || value === \"1\";\n}\n\nfunction getProcessDebugFlag(): unknown {\n const processLike = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n return processLike?.env?.[DEBUG_FLAG];\n}\n\nfunction isBrowserWindow(): boolean {\n const global = getGlobal();\n return global.window === globalThis;\n}\n\nfunction getLocalStorage(): TinyCloudDebugStorage | undefined {\n if (!isBrowserWindow()) {\n return undefined;\n }\n\n try {\n return (globalThis as typeof globalThis & { localStorage?: TinyCloudDebugStorage }).localStorage;\n } catch {\n return undefined;\n }\n}\n\nfunction getStoredDebugFlag(): unknown {\n const storage = getLocalStorage();\n if (!storage) {\n return undefined;\n }\n\n try {\n return storage.getItem(DEBUG_FLAG);\n } catch {\n return undefined;\n }\n}\n\nfunction setStoredDebugFlag(enabled: boolean): void {\n const storage = getLocalStorage();\n if (!storage) {\n return;\n }\n\n try {\n if (enabled) {\n storage.setItem(DEBUG_FLAG, \"true\");\n } else {\n storage.removeItem(DEBUG_FLAG);\n }\n } catch {\n // localStorage can be unavailable in private browsing or restricted contexts.\n }\n}\n\nfunction shouldStartEnabled(): boolean {\n const global = getGlobal();\n return (\n isTrue(global[DEBUG_FLAG]) ||\n isTrue(getStoredDebugFlag()) ||\n isTrue(getProcessDebugFlag())\n );\n}\n\nexport class TinyCloudDebugLogger {\n private enabled: boolean = shouldStartEnabled();\n private sequence: number = 0;\n private events: TinyCloudDebugEvent[] = [];\n private maxEvents: number = MAX_EVENTS;\n\n isEnabled(): boolean {\n return (\n this.enabled ||\n isTrue(getGlobal()[DEBUG_FLAG]) ||\n isTrue(getProcessDebugFlag())\n );\n }\n\n enable(options: TinyCloudDebugEnableOptions = {}): void {\n this.enabled = true;\n getGlobal()[DEBUG_FLAG] = true;\n\n if (options.persist !== false) {\n setStoredDebugFlag(true);\n }\n\n this.log(\"debug.enabled\", { persisted: options.persist !== false });\n }\n\n disable(options: TinyCloudDebugEnableOptions = {}): void {\n this.log(\"debug.disabled\", { persisted: options.persist !== false });\n this.enabled = false;\n getGlobal()[DEBUG_FLAG] = false;\n\n if (options.persist !== false) {\n setStoredDebugFlag(false);\n }\n }\n\n clear(): void {\n this.events = [];\n }\n\n getLogs(): TinyCloudDebugEvent[] {\n return [...this.events];\n }\n\n log(event: string, data?: unknown, message?: string): TinyCloudDebugEvent | undefined {\n if (!this.isEnabled()) {\n return undefined;\n }\n\n return this.record({\n event,\n data,\n message,\n level: \"debug\",\n timestamp: Date.now(),\n timestampIso: new Date().toISOString(),\n });\n }\n\n startTimer(event: string, data?: unknown): TinyCloudDebugTimer {\n if (!this.isEnabled()) {\n return { stop: () => undefined };\n }\n\n const startedAt = nowMs();\n this.log(`${event}.start`, data);\n\n return {\n stop: (finishData?: unknown) => {\n const endedAt = nowMs();\n return this.record({\n event: `${event}.end`,\n data: finishData,\n durationMs: endedAt - startedAt,\n startedAt,\n endedAt,\n level: \"debug\",\n timestamp: Date.now(),\n timestampIso: new Date().toISOString(),\n });\n },\n };\n }\n\n async timeAsync<T>(\n event: string,\n operation: () => Promise<T>,\n data?: unknown,\n ): Promise<T> {\n if (!this.isEnabled()) {\n return operation();\n }\n\n const timer = this.startTimer(event, data);\n try {\n const result = await operation();\n timer.stop({ ok: true });\n return result;\n } catch (error) {\n timer.stop({ ok: false, error });\n throw error;\n }\n }\n\n time<T>(event: string, operation: () => T, data?: unknown): T {\n if (!this.isEnabled()) {\n return operation();\n }\n\n const timer = this.startTimer(event, data);\n try {\n const result = operation();\n timer.stop({ ok: true });\n return result;\n } catch (error) {\n timer.stop({ ok: false, error });\n throw error;\n }\n }\n\n private record(\n event: Omit<TinyCloudDebugEvent, \"sequence\">,\n ): TinyCloudDebugEvent {\n const debugEvent: TinyCloudDebugEvent = {\n ...event,\n ...(event.data === undefined\n ? {}\n : { data: projectDiagnosticData(event.data) }),\n ...(event.message === undefined ? {} : { message: \"[REDACTED]\" }),\n sequence: ++this.sequence,\n };\n\n this.events.push(debugEvent);\n if (this.events.length > this.maxEvents) {\n this.events.splice(0, this.events.length - this.maxEvents);\n }\n\n try {\n globalThis.console?.debug?.(\"[TinyCloud]\", debugEvent.event, debugEvent);\n } catch {\n // Debug logging should never affect SDK behavior.\n }\n\n return debugEvent;\n }\n}\n\nexport const tinyCloudDebugLogger = new TinyCloudDebugLogger();\n\nexport function enableTinyCloudDebug(\n options?: TinyCloudDebugEnableOptions,\n): TinyCloudDebugLogger {\n tinyCloudDebugLogger.enable(options);\n return tinyCloudDebugLogger;\n}\n\nexport function disableTinyCloudDebug(\n options?: TinyCloudDebugEnableOptions,\n): TinyCloudDebugLogger {\n tinyCloudDebugLogger.disable(options);\n return tinyCloudDebugLogger;\n}\n\nexport function getTinyCloudDebugLogs(): TinyCloudDebugEvent[] {\n return tinyCloudDebugLogger.getLogs();\n}\n\nexport function clearTinyCloudDebugLogs(): void {\n tinyCloudDebugLogger.clear();\n}\n\nexport function installTinyCloudDebugGlobals(): void {\n const global = getGlobal();\n global.TinyCloudDebug = tinyCloudDebugLogger;\n global.enableTinyCloudDebug = enableTinyCloudDebug;\n global.disableTinyCloudDebug = disableTinyCloudDebug;\n global.getTinyCloudDebugLogs = getTinyCloudDebugLogs;\n global.clearTinyCloudDebugLogs = clearTinyCloudDebugLogs;\n}\n\ninstallTinyCloudDebugGlobals();\n","export interface ParsedServiceErrorBody {\n error?: string;\n message?: string;\n code?: string;\n}\n\nexport function parseServiceErrorBody(errorText: string): ParsedServiceErrorBody {\n try {\n return JSON.parse(errorText) as ParsedServiceErrorBody;\n } catch {\n return {};\n }\n}\n\nexport function formatServiceResponseError(\n serviceLabel: string,\n operation: string,\n status: number,\n errorText: string,\n parsed: ParsedServiceErrorBody,\n): string {\n if (parsed.message) {\n return compactMessage(parsed.message);\n }\n\n if (looksLikeHtml(errorText)) {\n if (status === 524 || /524\\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {\n return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;\n }\n return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;\n }\n\n return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;\n}\n\nexport function responseErrorMeta(\n status: number,\n statusText: string,\n errorText: string,\n): Record<string, unknown> {\n const meta: Record<string, unknown> = { status, statusText };\n const snippet = compactMessage(errorText);\n if (snippet) {\n meta.responseSnippet = snippet.slice(0, 300);\n }\n return meta;\n}\n\nfunction looksLikeHtml(text: string): boolean {\n return /<!doctype html/i.test(text) || /<html[\\s>]/i.test(text);\n}\n\nfunction compactMessage(message: string): string {\n const compact = message.replace(/\\s+/g, \" \").trim();\n return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;\n}\n","/**\n * SDK Services - Error Utilities\n *\n * Utilities for creating and handling service errors.\n */\n\nimport { responseErrorMeta } from \"./responseErrors\";\nimport { CAPABILITY_REGISTRY } from \"@tinycloud/bootstrap\";\nimport { parseNetworkId } from \"./encryption/networkId\";\n\nimport {\n type ServiceError,\n ErrorCodes,\n err,\n serviceError,\n type PermissionHint,\n} from \"./types\";\n\n/** Validate one structured node permission hint without accepting raw errors. */\nexport function parsePermissionHint(value: unknown): PermissionHint | undefined {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n const candidate = value as Record<string, unknown>;\n const keys = Object.keys(candidate).sort();\n if (keys.some((key) => ![\"actions\", \"path\", \"service\", \"space\"].includes(key))) return undefined;\n if (candidate.service !== \"tinycloud.kv\" && candidate.service !== \"tinycloud.encryption\") return undefined;\n if (typeof candidate.path !== \"string\" || candidate.path.length === 0 || candidate.path.includes(\"*\")) return undefined;\n if (candidate.service === \"tinycloud.kv\" &&\n (typeof candidate.space !== \"string\" || candidate.space.length === 0 || !candidate.path.startsWith(\"vault/\") || candidate.path.endsWith(\"/\"))) {\n return undefined;\n }\n if (candidate.service === \"tinycloud.encryption\" &&\n (candidate.space !== undefined || !candidate.path.startsWith(\"urn:tinycloud:encryption:\") || candidate.path.endsWith(\":\"))) {\n return undefined;\n }\n if (!Array.isArray(candidate.actions) || candidate.actions.length !== 1 ||\n typeof candidate.actions[0] !== \"string\" || candidate.actions[0].includes(\"*\")) return undefined;\n const expectedAction = candidate.service === \"tinycloud.kv\"\n ? \"tinycloud.kv/get\"\n : \"tinycloud.encryption/decrypt\";\n if (candidate.actions[0] !== expectedAction) return undefined;\n const space = typeof candidate.space === \"string\" ? candidate.space : undefined;\n return {\n service: candidate.service,\n ...(space === undefined ? {} : { space }),\n path: candidate.path,\n actions: [candidate.actions[0]],\n };\n}\n\n/** Extract only a validated structured hint from an SDK-owned error body. */\nexport function parsePermissionHintFromErrorText(text: string): PermissionHint | undefined {\n try {\n const parsed: unknown = JSON.parse(text);\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n const root = parsed as Record<string, unknown>;\n const nested = root.error;\n const nestedRecord = typeof nested === \"object\" && nested !== null\n ? nested as Record<string, unknown>\n : undefined;\n return parsePermissionHint(root.permissionHint) ??\n parsePermissionHint(nestedRecord?.permissionHint);\n } catch {\n return undefined;\n }\n}\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 * Classify a node retry signal (tinycloud-node #347, TC-1054). The node's\n * bodies are plain text led by a stable token:\n * - `503 sql_busy: <stage>: ...` and `503 unavailable_retryable: ...` (and\n * `read_audit_busy`) are `NODE_BUSY`: nothing was applied; retry after `meta.retryAfterMs`.\n * - `503 request_timeout: ...` is `REQUEST_TIMEOUT`: the deadline passed before\n * any write began, so nothing was applied.\n * - `500 outcome_unknown: ...` is `OUTCOME_UNKNOWN`: a write may have been\n * applied; read back before retrying a non-idempotent operation.\n *\n * `meta.retryable` is the safe-to-retry verdict and `meta.retryAfterMs` the\n * node's `Retry-After` (the node sends whole seconds; anything else is\n * ignored). `invokeWithRetry` retries `NODE_BUSY` and `REQUEST_TIMEOUT`;\n * `OUTCOME_UNKNOWN` is never retried. Returns `undefined` for any\n * other response, including the bodyless 503/500 of nodes before 1.21, which\n * callers keep classifying as they always did.\n */\nexport function nodeRetryError(\n service: string,\n message: string,\n response: { status: number; statusText: string; headers: { get(name: string): string | null } },\n errorText: string\n): ServiceError | undefined {\n const body = errorText.trim();\n const [, token, stage] = /^([a-z_]+)(?::\\s*([a-z_]+):)?/.exec(body) ?? [];\n let code: string;\n if (response.status === 503 && (token === \"sql_busy\" || token === \"unavailable_retryable\" || token === \"read_audit_busy\")) {\n code = ErrorCodes.NODE_BUSY;\n } else if (response.status === 503 && token === \"request_timeout\") {\n code = ErrorCodes.REQUEST_TIMEOUT;\n } else if (response.status === 500 && token === \"outcome_unknown\") {\n code = ErrorCodes.OUTCOME_UNKNOWN;\n } else {\n return undefined;\n }\n const retryable = code !== ErrorCodes.OUTCOME_UNKNOWN;\n const retryAfter = response.headers.get(\"retry-after\")?.trim();\n return serviceError(code, message, service, {\n meta: {\n ...responseErrorMeta(response.status, response.statusText, errorText),\n retryable,\n ...(token === \"sql_busy\" && stage ? { stage } : {}),\n ...(retryable && retryAfter && /^\\d{1,9}$/.test(retryAfter) ? { retryAfterMs: Number(retryAfter) * 1000 } : {}),\n },\n });\n}\n\n/**\n * Abort reason for a service request that exceeded its configured timeout.\n *\n * Named `TimeoutError` (like the reason of the platform's\n * `AbortSignal.timeout()`), so it maps to `ErrorCodes.TIMEOUT` and never to\n * `ErrorCodes.ABORTED`, which is reserved for caller and sign-out aborts.\n */\nexport class RequestTimeoutError extends Error {\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(`Request timed out after ${timeoutMs}ms.`);\n this.name = \"TimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Whether an error is a {@link RequestTimeoutError}. Also matches by shape so\n * that a copy of the class from another bundle (ESM/CJS entrypoint) is\n * recognized.\n */\nexport function isRequestTimeoutError(error: unknown): error is RequestTimeoutError {\n return (\n error instanceof RequestTimeoutError ||\n (error instanceof Error &&\n error.name === \"TimeoutError\" &&\n typeof (error as { timeoutMs?: unknown }).timeoutMs === \"number\")\n );\n}\n\n/**\n * Create a service error for timeouts.\n *\n * @param timeoutMs - The configured timeout that elapsed, when known\n * @param cause - The underlying timeout error, when known\n */\nexport function timeoutError(\n service: string,\n timeoutMs?: number,\n cause?: Error\n): ServiceError {\n if (timeoutMs === undefined) {\n return {\n code: ErrorCodes.TIMEOUT,\n message: \"Request timed out.\",\n service,\n ...(cause === undefined ? {} : { cause }),\n };\n }\n return {\n code: ErrorCodes.TIMEOUT,\n message: `Request timed out after ${timeoutMs}ms.`,\n service,\n ...(cause === undefined ? {} : { cause }),\n meta: { timeoutMs },\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 * Authorization verdict read from an error's structure, never its message:\n * - `\"unauthenticated\"`: HTTP 401 — the session is missing or stale.\n * - `\"forbidden\"`: HTTP 403, or `AUTH_UNAUTHORIZED` without a status — the\n * session is valid but lacks the capability.\n * - `\"other\"`: a non-authorization HTTP error status (4xx/5xx).\n */\nexport type AuthorizationVerdict = \"unauthenticated\" | \"forbidden\" | \"other\";\n\nconst grantableActions = new Set(\n CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith(\"/*\"))\n);\n\nconst capabilityServices = new Set(\n CAPABILITY_REGISTRY.map(({ service }) => service.slice(\"tinycloud.\".length))\n);\n\n/**\n * Parse a canonical TinyCloud resource, keeping nested space names intact.\n * The first registered service segment is authoritative: a later matching\n * segment in the path cannot turn a different service into grant advice.\n */\nexport function parseCapabilityResource(\n resource: string,\n service: string\n): { space: string; path: string } | undefined {\n if (\n resource.length === 0 ||\n resource.length > 1024 ||\n !capabilityServices.has(service) ||\n /[\\s\\p{C}*#]/u.test(resource) ||\n resource.includes(\"://\")\n ) return undefined;\n\n const segments = resource.split(\"/\");\n if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return undefined;\n const serviceIndex = segments.findIndex((segment, index) =>\n index > 0 && capabilityServices.has(segment)\n );\n if (serviceIndex < 1 || segments[serviceIndex] !== service) return undefined;\n if (segments.slice(1, serviceIndex).some((segment) =>\n segment.length === 0 || segment === \".\" || segment === \"..\"\n )) return undefined;\n const pathSegments = segments.slice(serviceIndex + 1);\n if (pathSegments.some((segment) => segment === \".\" || segment === \"..\")) return undefined;\n if (service !== \"kv\" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === \"\")) return undefined;\n\n return {\n space: segments.slice(0, serviceIndex).join(\"/\"),\n path: pathSegments.join(\"/\"),\n };\n}\n\n/** A capability resource is a canonical TinyCloud resource or encryption network, never a URL or expression. */\nfunction safeCapabilityResource(value: unknown, service: string, action: string): value is string {\n if (typeof value !== \"string\" || value.length === 0 || value.length > 1024) return false;\n if (service === \"encryption\" && value.startsWith(\"urn:tinycloud:encryption:\")) {\n if (\n !/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) ||\n value.includes(\"//\") ||\n value.split(\"/\").some((segment) => segment === \".\" || segment === \"..\")\n ) return false;\n try {\n parseNetworkId(value);\n return true;\n } catch {\n return false;\n }\n }\n const parsed = parseCapabilityResource(value, service);\n return parsed !== undefined &&\n (!parsed.path.endsWith(\"/\") || (service === \"kv\" && action === \"tinycloud.kv/list\")) &&\n (parsed.path !== \"\" || (service === \"kv\" && action === \"tinycloud.kv/list\"));\n}\n\n/**\n * Return a grantable missing capability only when typed authorization evidence\n * and both safe, service-matched capability fields are present. Status alone,\n * message text, wildcard actions and unrecognized actions cannot grant scope.\n */\nexport function validatedCapabilityOf(\n error: unknown\n): { resource: string; requiredAction: string } | undefined {\n const verdict = authorizationVerdictOf(error);\n if (verdict !== \"unauthenticated\" && verdict !== \"forbidden\") return undefined;\n\n const seen = new Set<unknown>();\n let node: unknown = error;\n for (\n let depth = 0;\n depth < MAX_CAUSE_DEPTH && typeof node === \"object\" && node !== null && !seen.has(node);\n depth += 1\n ) {\n seen.add(node);\n const record = node as Record<string, unknown>;\n const status = errorStatusOf(record);\n const meta = record.meta;\n if (\n (status === 401 || status === 403 || (status === undefined && record.code === ErrorCodes.AUTH_UNAUTHORIZED)) &&\n typeof meta === \"object\" && meta !== null && !Array.isArray(meta)\n ) {\n const metadata = meta as Record<string, unknown>;\n const { resource, requiredAction } = metadata;\n if (typeof requiredAction === \"string\" && grantableActions.has(requiredAction)) {\n const actionService = requiredAction.slice(\"tinycloud.\".length, requiredAction.indexOf(\"/\"));\n const explicitService = record.service === undefined ? metadata.service : record.service;\n if (\n safeCapabilityResource(resource, actionService, requiredAction) &&\n (explicitService === undefined ||\n explicitService === actionService ||\n explicitService === `tinycloud.${actionService}`)\n ) {\n return { resource, requiredAction };\n }\n }\n }\n node = record.cause;\n }\n return undefined;\n}\n\nconst MAX_CAUSE_DEPTH = 8;\n\n/** The first 4xx/5xx among `status`, `statusCode`, `meta.status`. Success and\n * informational statuses (e.g. a 200 `Response` kept as `cause`) are not\n * failures, so they are skipped rather than ending the search. */\nfunction errorStatusOf(node: Record<string, unknown>): number | undefined {\n const meta = node.meta;\n const candidates = [\n node.status,\n node.statusCode,\n typeof meta === \"object\" && meta !== null\n ? (meta as Record<string, unknown>).status\n : undefined,\n ];\n for (const candidate of candidates) {\n if (\n typeof candidate === \"number\" &&\n Number.isInteger(candidate) &&\n candidate >= 400 &&\n candidate <= 599\n ) {\n return candidate;\n }\n }\n return undefined;\n}\n\n/**\n * Classify an error from its typed HTTP error status (`status`, `statusCode`,\n * or `meta.status`, 4xx/5xx only) and `AUTH_UNAUTHORIZED` code, following the\n * `cause` chain outer-first (at most {@link MAX_CAUSE_DEPTH} links, cycles\n * stop the walk) so wrappers that rethrow with `cause` keep the verdict. The\n * outermost error status decides, so an outer 5xx keeps its retry budget even\n * over an inner 401. Returns `undefined` for untyped errors; only then may\n * callers fall back to message heuristics.\n */\nexport function authorizationVerdictOf(\n error: unknown,\n): AuthorizationVerdict | undefined {\n const seen = new Set<unknown>();\n let sawUnauthorizedCode = false;\n let node: unknown = error;\n for (\n let depth = 0;\n depth < MAX_CAUSE_DEPTH && typeof node === \"object\" && node !== null && !seen.has(node);\n depth += 1\n ) {\n seen.add(node);\n const record = node as Record<string, unknown>;\n const status = errorStatusOf(record);\n if (status === 401) return \"unauthenticated\";\n if (status === 403) return \"forbidden\";\n if (status !== undefined) return \"other\";\n if (record.code === ErrorCodes.AUTH_UNAUTHORIZED) sawUnauthorizedCode = true;\n node = record.cause;\n }\n return sawUnauthorizedCode ? \"forbidden\" : undefined;\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/** Shown when a write is rejected because the owner's storage is full. */\nexport const STORAGE_FULL_MESSAGE =\n \"TinyCloud storage is full, so this change was not saved. Reading still works. Free up space or upgrade your plan to save again.\";\n\n/** Shown when a write is larger than the storage the owner has left. */\nexport const STORAGE_WRITE_TOO_LARGE_MESSAGE =\n \"This change is larger than the TinyCloud storage you have left, so it was not saved. Reading still works. Free up space or upgrade your plan to save it.\";\n\n/**\n * Parse the byte counts from a node storage rejection\n * (\"... Used: 155744 bytes, Limit: 0 bytes\"). The limit is the share of the\n * owner's account-wide budget left for this space, so it is 0 when the\n * owner's other spaces already use the whole budget.\n */\nexport function parseStorageQuotaBytes(\n responseText: string\n): { usedBytes: number; limitBytes: number } | undefined {\n const match = responseText.match(/Used:\\s*(\\d+)\\s*bytes,\\s*Limit:\\s*(\\d+)\\s*bytes/i);\n if (!match) return undefined;\n return {\n usedBytes: parseInt(match[1], 10),\n limitBytes: parseInt(match[2], 10),\n };\n}\n\n/**\n * Account-wide storage totals the node attaches to a storage rejection when\n * billing supplied them. `limitBytes` is the plan budget shared by all the\n * owner's spaces, so this is the number to show the user, not the space's.\n */\nexport interface StorageAccountUsage {\n usedBytes: number;\n limitBytes: number;\n /** Plan tier id, e.g. `free` or `paid`. */\n plan?: string;\n}\n\n/** What a node storage rejection says about the space and the account. */\nexport interface StorageRejectionDetails {\n usedBytes?: number;\n limitBytes?: number;\n account?: StorageAccountUsage;\n}\n\nconst STORAGE_REJECTION_BODY_ERRORS: Record<string, true> = {\n storage_quota_exceeded: true,\n storage_limit_reached: true,\n};\n\nfunction isByteCount(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n}\n\nfunction byteCountsOf(value: unknown): { usedBytes: number; limitBytes: number } | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const { usedBytes, limitBytes } = value as Record<string, unknown>;\n return isByteCount(usedBytes) && isByteCount(limitBytes) ? { usedBytes, limitBytes } : undefined;\n}\n\n/**\n * Parse a node storage rejection. The node sends a JSON body\n * (`{error, message, space: {usedBytes, limitBytes}, account?: {usedBytes,\n * limitBytes, plan}}`); older nodes send only the plain-text message. Returns\n * undefined when the body is neither, e.g. a proxy's own 413 page.\n */\nexport function parseStorageRejection(responseText: string): StorageRejectionDetails | undefined {\n let body: unknown;\n try {\n body = JSON.parse(responseText);\n } catch {\n return parseStorageQuotaBytes(responseText);\n }\n if (typeof body !== \"object\" || body === null) return undefined;\n const record = body as Record<string, unknown>;\n const message = typeof record.message === \"string\" ? record.message : \"\";\n const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);\n const isStorageBody = typeof record.error === \"string\" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;\n if (!space && !isStorageBody) {\n return undefined;\n }\n const details: StorageRejectionDetails = { ...space };\n const account = byteCountsOf(record.account);\n if (account) {\n const plan = (record.account as Record<string, unknown>).plan;\n details.account = typeof plan === \"string\" && plan !== \"\" ? { ...account, plan } : account;\n }\n return details;\n}\n\n/**\n * Build the error for a write the node rejected for storage: 402 when storage\n * is already full, 413 when this write is larger than what is left. KV, SQL\n * and DuckDB all report it with the same codes and message so an app can\n * detect \"storage full\" in one place with {@link isStorageFullError}.\n * `meta.usedBytes`/`meta.limitBytes` describe the space; `meta.account`\n * carries the account totals when the node sent them.\n */\nexport function storageRejectionError(\n service: string,\n status: 402 | 413,\n meta: Record<string, unknown>,\n responseText: string\n): ServiceError {\n const quotaMeta = { ...meta, ...parseStorageRejection(responseText) };\n return status === 402\n ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta)\n : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);\n}\n\n/**\n * True when a write failed for storage: the owner's TinyCloud storage is full\n * (`STORAGE_QUOTA_EXCEEDED`), or this write is larger than what is left\n * (`STORAGE_LIMIT_REACHED`). Reads keep working in both cases, so apps should\n * stay usable read-only and point the user at freeing up space or upgrading.\n */\nexport function isStorageFullError(error: { code?: string } | null | undefined): boolean {\n return (\n error?.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED ||\n error?.code === ErrorCodes.STORAGE_LIMIT_REACHED\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 // A configured request timeout elapsed (see BaseService.createRequestSignal)\n if (isRequestTimeoutError(error)) {\n return timeoutError(service, error.timeoutMs, error);\n }\n\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","/** Abortable wait; ref'd unless `unref`. */\nexport const abortableSleep = (ms: number, signal: AbortSignal, unref = false): Promise<void> =>\n new Promise((resolve) => {\n const done = (): void => {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", done);\n resolve();\n };\n const timer = setTimeout(done, ms);\n if (unref) timer.unref?.();\n signal.aborted ? done() : signal.addEventListener(\"abort\", done);\n });\n","/**\n * ServiceContext implementation for TinyCloud SDK Services\n * @module @tinycloud/sdk-services\n */\n\nimport {\n IServiceContext,\n IService,\n ServiceSession,\n RetryPolicy,\n InvokeFunction,\n InvokeAnyFunction,\n FetchFunction,\n FetchRequestInit,\n FetchResponse,\n defaultRetryPolicy,\n EventHandler,\n TelemetryEventHandler,\n TelemetryConfig,\n} from \"./types\";\nimport { tinyCloudDebugLogger } from \"./debug\";\nimport { nodeRetryError } from \"./errors\";\nimport { abortableSleep } from \"./sleep\";\nimport { projectDiagnosticData } from \"./diagnostics\";\n\n/**\n * Configuration options for ServiceContext.\n */\nexport interface ServiceContextConfig {\n /** Function to invoke WASM operations */\n invoke: InvokeFunction;\n /** Optional function to mint a single authorization header for multiple capabilities */\n invokeAny?: InvokeAnyFunction;\n /** Function to make HTTP requests (defaults to globalThis.fetch) */\n fetch?: FetchFunction;\n /** List of TinyCloud host URLs */\n hosts: string[];\n /** Initial session (optional) */\n session?: ServiceSession | null;\n /** Retry policy configuration */\n retryPolicy?: Partial<RetryPolicy>;\n /** Default-off telemetry event delivery. */\n telemetry?: TelemetryConfig;\n}\n\n/**\n * ServiceContext provides platform dependencies and cross-service access to services.\n * This is the primary interface services use to interact with the SDK runtime.\n *\n * @example\n * ```typescript\n * const context = new ServiceContext({\n * invoke: wasmInvoke,\n * hosts: ['https://node.tinycloud.xyz'],\n * retryPolicy: { maxAttempts: 5 },\n * });\n *\n * // Register a service\n * const kvService = new KVService({});\n * context.registerService('kv', kvService);\n * kvService.initialize(context);\n *\n * // Update session when user signs in\n * context.setSession(userSession);\n * ```\n */\nexport class ServiceContext implements IServiceContext {\n private _session: ServiceSession | null = null;\n private _services: Map<string, IService> = new Map();\n private _eventHandlers: Map<string, Set<EventHandler>> = new Map();\n private _abortController: AbortController = new AbortController();\n private _operationAbortSignal?: AbortSignal;\n private _retired = false;\n private readonly _invoke: InvokeFunction;\n private readonly _invokeAny?: InvokeAnyFunction;\n private readonly _fetch: FetchFunction;\n private readonly _hosts: string[];\n private readonly _retryPolicy: RetryPolicy;\n private readonly _telemetryEnabled: boolean;\n private readonly _telemetryHandler?: TelemetryEventHandler;\n\n constructor(config: ServiceContextConfig) {\n this._invoke = this.wrapInvoke(config.invoke);\n this._invokeAny = config.invokeAny\n ? this.wrapInvokeAny(config.invokeAny)\n : undefined;\n this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));\n this._hosts = config.hosts;\n this._session = config.session ?? null;\n this._retryPolicy = {\n ...defaultRetryPolicy,\n ...config.retryPolicy,\n };\n this._telemetryEnabled =\n typeof config.telemetry === \"boolean\"\n ? config.telemetry\n : config.telemetry?.enabled === true;\n this._telemetryHandler =\n typeof config.telemetry === \"object\" ? config.telemetry.onEvent : undefined;\n }\n\n // ============================================================\n // Session Management\n // ============================================================\n\n /**\n * Get the current session.\n */\n get session(): ServiceSession | null {\n return this._retired ? null : this._session;\n }\n\n /**\n * Check if the context has an authenticated session.\n */\n get isAuthenticated(): boolean {\n return !this._retired && this._session !== null;\n }\n\n /**\n * Update the session and notify all registered services.\n *\n * @param session - New session or null to clear\n */\n setSession(session: ServiceSession | null): void {\n this.assertActive();\n this._session = session;\n this.emit('session.changed', { authenticated: session !== null });\n\n // Notify all services of session change\n for (const service of this._services.values()) {\n service.onSessionChange(session);\n }\n }\n\n // ============================================================\n // Platform Dependencies\n // ============================================================\n\n /**\n * Get the invoke function for WASM operations.\n */\n get invoke(): InvokeFunction {\n this.assertActive();\n return this._invoke;\n }\n\n /**\n * Get the multi-resource invoke function when available.\n */\n get invokeAny(): InvokeAnyFunction | undefined {\n this.assertActive();\n return this._invokeAny;\n }\n\n /**\n * Get the fetch function for HTTP requests.\n */\n get fetch(): FetchFunction {\n this.assertActive();\n return this._fetch;\n }\n\n /**\n * Get the list of TinyCloud host URLs.\n */\n get hosts(): string[] {\n this.assertActive();\n return this._hosts;\n }\n\n // ============================================================\n // Service Registry\n // ============================================================\n\n /**\n * Register a service with the context.\n *\n * @param name - Service name (e.g., 'kv')\n * @param service - Service instance\n */\n registerService(name: string, service: IService): void {\n this._services.set(name, service);\n }\n\n /**\n * Unregister a service from the context.\n *\n * @param name - Service name to remove\n */\n unregisterService(name: string): void {\n this._services.delete(name);\n }\n\n /**\n * Get a registered service by name.\n *\n * @param name - Service name\n * @returns The service instance or undefined if not registered\n */\n getService<T extends IService>(name: string): T | undefined {\n return this._services.get(name) as T | undefined;\n }\n\n // ============================================================\n // Event System (Telemetry)\n // ============================================================\n\n /**\n * Emit a telemetry event.\n *\n * @param event - Event name\n * @param data - Event data\n */\n emit(event: string, data: unknown): void {\n tinyCloudDebugLogger.log(event, data);\n\n if (this._telemetryEnabled && this._telemetryHandler) {\n try {\n this._telemetryHandler(event, projectDiagnosticData(data));\n } catch (error) {\n // Don't let telemetry handlers break SDK operations.\n console.error(`Error in telemetry handler for \"${event}\":`, error);\n }\n }\n\n const handlers = this._eventHandlers.get(event);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(data);\n } catch (error) {\n // Don't let event handler errors break the flow\n console.error(`Error in event handler for \"${event}\":`, error);\n }\n }\n }\n }\n\n /**\n * Subscribe to telemetry events.\n *\n * @param event - Event name to subscribe to\n * @param handler - Handler function\n * @returns Unsubscribe function\n */\n on(event: string, handler: EventHandler): () => void {\n if (!this._eventHandlers.has(event)) {\n this._eventHandlers.set(event, new Set());\n }\n this._eventHandlers.get(event)!.add(handler);\n\n // Return unsubscribe function\n return () => {\n const handlers = this._eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n this._eventHandlers.delete(event);\n }\n }\n };\n }\n\n /**\n * Remove all event handlers for an event.\n *\n * @param event - Event name (if omitted, clears all events)\n */\n clearEventHandlers(event?: string): void {\n if (event) {\n this._eventHandlers.delete(event);\n } else {\n this._eventHandlers.clear();\n }\n }\n\n // ============================================================\n // Lifecycle\n // ============================================================\n\n /**\n * Get the abort signal for cancelling operations.\n */\n get abortSignal(): AbortSignal {\n return this._abortController.signal;\n }\n\n get operationAbortSignal(): AbortSignal | undefined {\n return this._operationAbortSignal;\n }\n\n setOperationAbortSignal(signal: AbortSignal | undefined): void {\n this._operationAbortSignal = signal;\n }\n /**\n * Permanently retire this graph after its owner installs a replacement.\n * Unlike `abort()`, retirement never creates a fresh controller, so captured\n * services cannot resume calls under a superseded session.\n */\n retire(): void {\n if (this._retired) return;\n this._retired = true;\n this._session = null;\n this._abortController.abort();\n for (const service of this._services.values()) {\n // User-provided services are part of the retired graph, but their\n // cleanup must never make installing the replacement graph fail.\n try {\n service.onSignOut();\n } catch (error) {\n console.error(\"Error retiring service:\", error);\n }\n }\n }\n\n /**\n * Abort all pending operations and notify services.\n * Creates a new AbortController for future operations.\n */\n abort(): void {\n // A retired graph must never receive a fresh abort signal. Captured\n // services retain this context, so recreating its controller would make a\n // stale graph look live to custom cleanup code.\n if (this._retired) return;\n this._abortController.abort();\n this._abortController = new AbortController();\n\n // Notify all services\n for (const service of this._services.values()) {\n try {\n service.onSignOut();\n } catch (error) {\n console.error(\"Error signing out service:\", error);\n }\n }\n }\n\n /**\n * Sign out - abort operations and clear session.\n */\n signOut(): void {\n if (this._retired) return;\n this.abort();\n this.setSession(null);\n this.emit('session.expired', {});\n }\n\n // ============================================================\n // Retry Policy\n // ============================================================\n\n /**\n * Get the retry policy configuration.\n */\n get retryPolicy(): RetryPolicy {\n return this._retryPolicy;\n }\n\n private wrapInvoke(invoke: InvokeFunction): InvokeFunction {\n return (session, service, path, action, facts) => {\n this.assertActive();\n if (!tinyCloudDebugLogger.isEnabled()) {\n return invoke(session, service, path, action, facts);\n }\n\n const timer = tinyCloudDebugLogger.startTimer(\"sdk.invoke\", {\n service,\n path,\n action,\n facts,\n });\n\n try {\n const result = invoke(session, service, path, action, facts);\n timer.stop({ ok: true, service, path, action });\n return result;\n } catch (error) {\n timer.stop({ ok: false, service, path, action, error });\n throw error;\n }\n };\n }\n\n private assertActive(): void {\n if (this._retired) {\n throw new Error(\"Service graph has been retired: its session was replaced or signed out.\");\n }\n }\n\n private wrapInvokeAny(invokeAny: InvokeAnyFunction): InvokeAnyFunction {\n return (session, entries, facts) => {\n this.assertActive();\n if (!tinyCloudDebugLogger.isEnabled()) {\n return invokeAny(session, entries, facts);\n }\n\n const timer = tinyCloudDebugLogger.startTimer(\"sdk.invokeAny\", {\n entries,\n facts,\n });\n\n try {\n const result = invokeAny(session, entries, facts);\n timer.stop({ ok: true, entries });\n return result;\n } catch (error) {\n timer.stop({ ok: false, entries, error });\n throw error;\n }\n };\n }\n\n /** Retry `NODE_BUSY` / `REQUEST_TIMEOUT`; `build` must re-sign per attempt (the node 409s a repeated invocation). */\n async invokeWithRetry(\n url: string,\n build: () => FetchRequestInit | Promise<FetchRequestInit>,\n ): Promise<FetchResponse> {\n this.assertActive();\n const policy = this._retryPolicy;\n const startedAt = Date.now();\n let backoffMs = 0;\n let signal: AbortSignal | undefined;\n for (let attempt = 1; ; attempt++) {\n const init = await build();\n signal ??= this.combineSignals(init.signal);\n const response = await this._fetch(url, init);\n const done = (): FetchResponse => {\n try { response.retry = { attempts: attempt, backoffMs }; } catch { /* frozen custom response */ }\n return response;\n };\n if (attempt >= policy.maxAttempts || response.status !== 503 || !response.clone) return done();\n const text = await response.clone().text().catch(() => \"\");\n const error = nodeRetryError(\"\", \"\", response, text);\n if (!error || error.meta?.retryable !== true || !policy.retryableErrors.includes(error.code)) {\n return done();\n }\n const remaining = (policy.maxTotalMs ?? Infinity) - (Date.now() - startedAt);\n if (remaining <= 0) return done();\n const retryAfterMs = error.meta.retryAfterMs;\n const step = Math.min(\n policy.maxDelayMs,\n policy.backoff === \"exponential\"\n ? policy.baseDelayMs * 2 ** (attempt - 1)\n : policy.backoff === \"linear\"\n ? policy.baseDelayMs * attempt\n : 0,\n );\n const delay = Math.min(\n typeof retryAfterMs === \"number\" ? retryAfterMs : Math.random() * step,\n remaining,\n );\n (response.body as { cancel?(): Promise<unknown> } | undefined)?.cancel?.()?.catch?.(() => {});\n await abortableSleep(delay, signal);\n if (signal.aborted) throw signal.reason;\n backoffMs += delay;\n }\n }\n\n private wrapFetch(fetchFn: FetchFunction): FetchFunction {\n return async (url, init) => {\n this.assertActive();\n const request = {\n ...init,\n signal: this.combineSignals(init?.signal),\n };\n if (!tinyCloudDebugLogger.isEnabled()) {\n return fetchFn(url, request);\n }\n\n const meta = { url, method: request.method ?? \"GET\" };\n const timer = tinyCloudDebugLogger.startTimer(\"sdk.fetch\", { ...meta, init: request });\n\n try {\n const response = await fetchFn(url, request);\n timer.stop({\n ...meta,\n ok: response.ok,\n status: response.status,\n statusText: response.statusText,\n });\n return response;\n } catch (error) {\n timer.stop({ ...meta, ok: false, error });\n throw error;\n }\n };\n }\n\n private combineSignals(signal?: AbortSignal): AbortSignal {\n if (!signal) return this._abortController.signal;\n // `AbortSignal.any` links the two signals for as long as the composite lives, with no listener on the long-lived\n // signal (TC-2418), so aborting the context (sign-out) still cancels a response body being read. Trade-off: on\n // Node its composites stay reachable while the long-lived signal lives (about 65 B per request on Node 20, 180 B\n // on Node 22; a listener that is never removed retains about 1 KB). Where it is missing (older browsers) the\n // listeners below are unchanged.\n if (typeof AbortSignal.any === \"function\") return AbortSignal.any([signal, this._abortController.signal]);\n const combined = new AbortController();\n const abort = (source: AbortSignal) => combined.abort(source.reason);\n if (signal.aborted) {\n abort(signal);\n } else if (this._abortController.signal.aborted) {\n abort(this._abortController.signal);\n } else {\n signal.addEventListener(\"abort\", () => abort(signal), { once: true });\n this._abortController.signal.addEventListener(\n \"abort\",\n () => abort(this._abortController.signal),\n { once: true },\n );\n }\n return combined.signal;\n }\n}\n\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/types.ts","../src/diagnostics.ts","../src/debug.ts","../src/responseErrors.ts","../src/errors.ts","../src/sleep.ts","../src/context.ts"],"sourcesContent":["/**\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 * A node-supplied permission hint after strict SDK validation. This is\n * deliberately narrower than a general capability: no wildcards, caveats,\n * descriptions, or transport metadata can cross the service boundary.\n */\nexport interface PermissionHint {\n readonly service: \"tinycloud.kv\" | \"tinycloud.encryption\";\n readonly space?: string;\n readonly path: string;\n readonly actions: readonly string[];\n}\n\n/**\n * Storage quota information returned with quota-related errors.\n * @deprecated Read `usedBytes`, `limitBytes` and `account` from the error's\n * `meta`, or use `StorageFullEvent` from `tc.on(\"storage.full\", handler)`.\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 /** HTTP 401: the delegation the invocation cites was revoked. */\n AUTH_DELEGATION_REVOKED: \"AUTH_DELEGATION_REVOKED\",\n /** HTTP 401: a delegation the cited one rests on was revoked. */\n AUTH_DELEGATION_ANCESTOR_REVOKED: \"AUTH_DELEGATION_ANCESTOR_REVOKED\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n NODE_NOT_RESPONDING: \"NODE_NOT_RESPONDING\",\n NODE_IDENTITY_MISMATCH: \"NODE_IDENTITY_MISMATCH\",\n PARTIAL_NODE_WRITE: \"PARTIAL_NODE_WRITE\",\n RESOURCE_NOT_ROUTED: \"RESOURCE_NOT_ROUTED\",\n TIMEOUT: \"TIMEOUT\",\n ABORTED: \"ABORTED\",\n INVALID_INPUT: \"INVALID_INPUT\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n\n // Node retry signals (tinycloud-node #347). `meta.retryable` says whether a\n // retry is safe; `meta.retryAfterMs` carries the node's Retry-After.\n /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`/`read_audit_busy`: nothing was applied; retry after `meta.retryAfterMs`. */\n NODE_BUSY: \"NODE_BUSY\",\n /** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */\n REQUEST_TIMEOUT: \"REQUEST_TIMEOUT\",\n /** HTTP 500 `outcome_unknown`: a write may have been applied; read back before retrying a non-idempotent operation. */\n OUTCOME_UNKNOWN: \"OUTCOME_UNKNOWN\",\n\n // KV-specific errors\n KV_NOT_FOUND: \"KV_NOT_FOUND\",\n KV_WRITE_FAILED: \"KV_WRITE_FAILED\",\n KV_PRECONDITION_FAILED: \"KV_PRECONDITION_FAILED\",\n KV_CONFLICT: \"KV_CONFLICT\",\n KV_RESPONSE_TOO_LARGE: \"KV_RESPONSE_TOO_LARGE\",\n /** HTTP 410 from the change feed: discard sync state and restart without a cursor; `meta.reason` says why. */\n KV_SYNC_RESET_REQUIRED: \"KV_SYNC_RESET_REQUIRED\",\n /** HTTP 403 from the change feed: the retention grant was refused; `meta.reason` says why. */\n KV_RETENTION_GRANT_REFUSED: \"KV_RETENTION_GRANT_REFUSED\",\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 /** The node answered the usage read without usage numbers (it predates the read). */\n STORAGE_STATUS_UNAVAILABLE: \"STORAGE_STATUS_UNAVAILABLE\",\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 /** Capability caveats that must remain on this invocation attenuation. */\n caveats?: Record<string, unknown>[];\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 redirect?: \"error\";\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 /** Present on standard Responses; the retry reads a clone so the caller's body stays intact. */\n clone?(): FetchResponse;\n /** Set by `ServiceContext.invokeWithRetry` on the response it returns: how many requests it took. */\n retry?: RetryStats;\n}\n\n/** What automatic retry cost one request, so latency can be split from backoff. */\nexport interface RetryStats {\n /** Requests sent, including the first (1 means no retry). */\n attempts: number;\n /** Total milliseconds spent waiting between attempts. */\n backoffMs: number;\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 * Automatic retry of node responses that guarantee nothing was applied\n * (`NODE_BUSY`, `REQUEST_TIMEOUT`). Applied once, in the service context's\n * `invokeWithRetry`, which re-mints the signed invocation per attempt. Backoff\n * is full-jitter: each delay is uniform in `[0, step]`, where\n * `step` is the strategy's delay capped at `maxDelayMs`. A node `Retry-After`\n * replaces the delay. Opt out with `{ maxAttempts: 1 }`.\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 /** Node error codes that trigger a retry. `OUTCOME_UNKNOWN` is never retried. */\n retryableErrors: string[];\n /** Total time budget for all attempts and waits, in milliseconds. Unset: no cap. */\n maxTotalMs?: number;\n}\n\n/**\n * Default retry policy.\n */\nexport const defaultRetryPolicy: RetryPolicy = {\n maxAttempts: 4,\n backoff: \"exponential\",\n baseDelayMs: 250,\n maxDelayMs: 4000,\n retryableErrors: [ErrorCodes.NODE_BUSY, ErrorCodes.REQUEST_TIMEOUT],\n maxTotalMs: 12000,\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 /** Send a signed invocation, retrying a busy node; `build` re-signs per attempt. Absent: sends once. */\n invokeWithRetry?(\n url: string,\n build: () => FetchRequestInit | Promise<FetchRequestInit>,\n ): Promise<FetchResponse>;\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 /** Additional abort signal for a scoped operation, when active. */\n readonly operationAbortSignal?: AbortSignal;\n /** Install an additional signal for the duration of a scoped operation. */\n setOperationAbortSignal?(signal: AbortSignal | undefined): void;\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 REPLICATION_READ: \"replication.read\",\n REPLICATION_WRITE: \"replication.write\",\n REPLICATION_SYNC: \"replication.sync\",\n REPLICATION_STATE: \"replication.state\",\n REPLICATION_DIVERGENCE: \"replication.divergence\",\n REPLICATION_GRANT: \"replication.grant\",\n REPLICATION_LIFECYCLE: \"replication.lifecycle\",\n /** Emitted once, on the first write rejected because storage is full. */\n STORAGE_FULL: \"storage.full\",\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","/** Safe projections for diagnostic sinks. */\n\nconst REDACTED = \"[REDACTED]\";\nconst SAFE_NUMBER_FIELDS = new Set([\n \"duration\",\n \"durationMs\",\n \"endedAt\",\n \"startedAt\",\n \"status\",\n \"timestamp\",\n \"latencyMs\",\n \"stalenessMs\",\n \"count\",\n \"changes\",\n \"deleted\",\n \"fetched\",\n \"pages\",\n \"lagMs\",\n \"pendingCleared\",\n \"partitions\",\n \"remaining\",\n \"expiryMs\",\n]);\nconst SAFE_BOOLEAN_FIELDS = new Set([\"authenticated\", \"ok\", \"persisted\", \"purgePending\"]);\nconst SAFE_REPLICATION_FIELDS: Record<string, readonly string[]> = {\n source: [\"replica\", \"network\", \"none\"],\n reason: [\"hit\", \"absent\", \"deleted\", \"content_missing\", \"coverage_incomplete\", \"not_covered\", \"pending_write\", \"grant_missing\", \"grant_expired\", \"grant_revoked\", \"grant_not_yet_valid\", \"stale\", \"replica_error\", \"runtime_unsupported\", \"replica_unavailable\", \"unsupported_option\", \"network_cursor\", \"cursor_restart\", \"aborted\", \"REPLICA_BEHIND_OWN_WRITES\", \"REPLICA_UNPROVEN_SINCE_START\", \"NETWORK_REQUESTED\", \"replica_gated\", \"sign_out\", \"account_switch\", \"consent_expired\", \"startup\"],\n op: [\"get\", \"list\", \"put\", \"delete\", \"batchPut\"],\n trigger: [\"start\", \"interval\", \"stale_read\", \"manual\", \"peer\"],\n origin: [\"peer\"],\n outcome: [\"found\", \"not_found\", \"error\", \"committed\", \"failed\", \"ambiguous\", \"ok\", \"busy\", \"aborted\"],\n class: [\"offline\", \"authority\", \"storage\", \"node\", \"lifecycle\"],\n state: [\"opened\", \"grant_installed\", \"grant_missing\", \"runtime_unsupported\", \"unavailable\", \"revoked\", \"recreated\", \"purged\", \"purge_failed\", \"pending_store_error\", \"pinned\", \"pending_cleared\", \"closed\", \"idle\", \"ready\", \"gated\"],\n action: [\"minted\", \"renewed\", \"renewal_refused\", \"renewal_deferred\", \"capped\", \"expired\", \"consent_deadline_reached\", \"denied\", \"deny_stale\", \"deny_failed\", \"database_deleted\", \"marker_set\", \"marker_cleared\", \"unguarded\", \"drained\", \"erased\", \"erase_failed\", \"activation_blocked\", \"activated\", \"recovered\"],\n gate: [\"not_established\", \"stamp_stale\", \"principal_mismatch\", \"purge_pending\", \"logout_pending\", \"gate_unavailable\"],\n kind: [\"value\", \"missing_local\", \"extra_local\", \"keys\", \"order\"],\n coverage: [\"empty\", \"bootstrapping\", \"complete\"],\n authority: [\"valid\", \"expired\", \"revoked\", \"not-yet-valid\"],\n code: [\"NETWORK_ERROR\", \"TIMEOUT\", \"ABORTED\", \"REPLICA_BUSY\", \"REPLICA_BEHIND_OWN_WRITES\", \"REPLICA_UNPROVEN_SINCE_START\", \"NETWORK_REQUESTED\", \"INTEGRITY_ERROR\", \"STORAGE_ERROR\", \"REPLICA_CLOSED\", \"RUNTIME_UNSUPPORTED\", \"GRANT_EXPIRED\", \"GRANT_REVOKED\", \"GRANT_NOT_YET_VALID\", \"GRANT_MISSING\", \"GRANT_INVALID\", \"SOURCE_CHANGED\", \"PROTOCOL_ERROR\", \"SCOPE_VIOLATION\", \"NODE_ERROR\", \"CONTENT_MISMATCH\", \"STORAGE_FULL\", \"REPLICA_GATED\", \"RESET_REQUIRED\", \"STORAGE_BUDGET\", \"CONSENT_EXPIRED\"],\n pendingState: [\"in_flight\", \"committed\", \"ambiguous\"],\n strategy: [\"session\", \"minted\", \"installed\"],\n verify: [\"match\", \"diverged\", \"error\", \"aborted\"],\n};\n\nfunction finiteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction isBinaryData(value: object): boolean {\n try {\n return (\n Array.isArray(value) ||\n ArrayBuffer.isView(value) ||\n value instanceof ArrayBuffer\n );\n } catch {\n // A revoked Proxy can throw while checking its shape. It is not diagnostic\n // data and must not prevent the SDK operation that emitted it.\n return true;\n }\n}\n\nfunction read(value: object, key: string): unknown {\n try {\n return (value as Record<string, unknown>)[key];\n } catch {\n return REDACTED;\n }\n}\n\n/** Return only stable, non-sensitive error diagnostics. */\nexport function projectDiagnosticError(error: unknown): Record<string, unknown> {\n if (typeof error !== \"object\" || error === null) return {};\n\n const status = finiteNumber(read(error, \"status\"));\n return status !== undefined && status >= 100 && status <= 599 ? { status } : {};\n}\n\n/**\n * Copy only fixed scalar metrics and URL origins. Diagnostic payloads can be\n * supplied by applications and nodes, so strings, nested values, and unknown\n * fields are never projected to debug or telemetry sinks.\n */\nexport function projectDiagnosticData(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value !== \"object\") return REDACTED;\n\n try {\n if (isBinaryData(value)) return REDACTED;\n\n const projected: Record<string, unknown> = {};\n for (const key of SAFE_NUMBER_FIELDS) {\n const number = finiteNumber(read(value, key));\n if (number !== undefined) projected[key] = number;\n }\n for (const key of SAFE_BOOLEAN_FIELDS) {\n const boolean = read(value, key);\n if (typeof boolean === \"boolean\") projected[key] = boolean;\n }\n for (const [key, allowed] of Object.entries(SAFE_REPLICATION_FIELDS)) {\n const candidate = read(value, key);\n if (typeof candidate === \"string\" && allowed.includes(candidate)) {\n projected[key] = candidate;\n }\n }\n\n\n // URLs, like every other string-bearing value, are never projected. Even\n // an origin can encode user-controlled data in a hostname.\n const url = read(value, \"url\");\n if (url !== undefined) projected.url = REDACTED;\n\n const error = read(value, \"error\");\n if (error !== undefined) projected.error = projectDiagnosticError(error);\n\n return projected;\n } catch {\n // Projection is best-effort only. Hostile Proxy traps and exotic objects\n // must never block telemetry, debug logging, or ordinary subscribers.\n return REDACTED;\n }\n}\n","import { projectDiagnosticData } from \"./diagnostics\";\n\nexport type TinyCloudDebugLevel = \"debug\";\n\nexport interface TinyCloudDebugEvent {\n sequence: number;\n timestamp: number;\n timestampIso: string;\n level: TinyCloudDebugLevel;\n event: string;\n message?: string;\n data?: unknown;\n durationMs?: number;\n startedAt?: number;\n endedAt?: number;\n}\n\nexport interface TinyCloudDebugEnableOptions {\n /**\n * Persist the debug flag in browser localStorage. Defaults to true.\n * Node runtimes intentionally do not persist this setting.\n */\n persist?: boolean;\n}\n\nexport interface TinyCloudDebugTimer {\n stop: (data?: unknown) => TinyCloudDebugEvent | undefined;\n}\n\ntype TinyCloudDebugGlobal = typeof globalThis & {\n TinyCloud_debug?: boolean | string;\n TinyCloudDebug?: TinyCloudDebugLogger;\n window?: unknown;\n enableTinyCloudDebug?: typeof enableTinyCloudDebug;\n disableTinyCloudDebug?: typeof disableTinyCloudDebug;\n getTinyCloudDebugLogs?: typeof getTinyCloudDebugLogs;\n clearTinyCloudDebugLogs?: typeof clearTinyCloudDebugLogs;\n};\n\ninterface TinyCloudDebugStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nconst DEBUG_FLAG = \"TinyCloud_debug\";\nconst MAX_EVENTS = 1000;\n\nfunction getGlobal(): TinyCloudDebugGlobal {\n return globalThis as TinyCloudDebugGlobal;\n}\n\nfunction nowMs(): number {\n const performanceNow = globalThis.performance?.now?.bind(globalThis.performance);\n return typeof performanceNow === \"function\" ? performanceNow() : Date.now();\n}\n\nfunction isTrue(value: unknown): boolean {\n return value === true || value === \"true\" || value === \"1\";\n}\n\nfunction getProcessDebugFlag(): unknown {\n const processLike = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n return processLike?.env?.[DEBUG_FLAG];\n}\n\nfunction isBrowserWindow(): boolean {\n const global = getGlobal();\n return global.window === globalThis;\n}\n\nfunction getLocalStorage(): TinyCloudDebugStorage | undefined {\n if (!isBrowserWindow()) {\n return undefined;\n }\n\n try {\n return (globalThis as typeof globalThis & { localStorage?: TinyCloudDebugStorage }).localStorage;\n } catch {\n return undefined;\n }\n}\n\nfunction getStoredDebugFlag(): unknown {\n const storage = getLocalStorage();\n if (!storage) {\n return undefined;\n }\n\n try {\n return storage.getItem(DEBUG_FLAG);\n } catch {\n return undefined;\n }\n}\n\nfunction setStoredDebugFlag(enabled: boolean): void {\n const storage = getLocalStorage();\n if (!storage) {\n return;\n }\n\n try {\n if (enabled) {\n storage.setItem(DEBUG_FLAG, \"true\");\n } else {\n storage.removeItem(DEBUG_FLAG);\n }\n } catch {\n // localStorage can be unavailable in private browsing or restricted contexts.\n }\n}\n\nfunction shouldStartEnabled(): boolean {\n const global = getGlobal();\n return (\n isTrue(global[DEBUG_FLAG]) ||\n isTrue(getStoredDebugFlag()) ||\n isTrue(getProcessDebugFlag())\n );\n}\n\nexport class TinyCloudDebugLogger {\n private enabled: boolean = shouldStartEnabled();\n private sequence: number = 0;\n private events: TinyCloudDebugEvent[] = [];\n private maxEvents: number = MAX_EVENTS;\n\n isEnabled(): boolean {\n return (\n this.enabled ||\n isTrue(getGlobal()[DEBUG_FLAG]) ||\n isTrue(getProcessDebugFlag())\n );\n }\n\n enable(options: TinyCloudDebugEnableOptions = {}): void {\n this.enabled = true;\n getGlobal()[DEBUG_FLAG] = true;\n\n if (options.persist !== false) {\n setStoredDebugFlag(true);\n }\n\n this.log(\"debug.enabled\", { persisted: options.persist !== false });\n }\n\n disable(options: TinyCloudDebugEnableOptions = {}): void {\n this.log(\"debug.disabled\", { persisted: options.persist !== false });\n this.enabled = false;\n getGlobal()[DEBUG_FLAG] = false;\n\n if (options.persist !== false) {\n setStoredDebugFlag(false);\n }\n }\n\n clear(): void {\n this.events = [];\n }\n\n getLogs(): TinyCloudDebugEvent[] {\n return [...this.events];\n }\n\n log(event: string, data?: unknown, message?: string): TinyCloudDebugEvent | undefined {\n if (!this.isEnabled()) {\n return undefined;\n }\n\n return this.record({\n event,\n data,\n message,\n level: \"debug\",\n timestamp: Date.now(),\n timestampIso: new Date().toISOString(),\n });\n }\n\n startTimer(event: string, data?: unknown): TinyCloudDebugTimer {\n if (!this.isEnabled()) {\n return { stop: () => undefined };\n }\n\n const startedAt = nowMs();\n this.log(`${event}.start`, data);\n\n return {\n stop: (finishData?: unknown) => {\n const endedAt = nowMs();\n return this.record({\n event: `${event}.end`,\n data: finishData,\n durationMs: endedAt - startedAt,\n startedAt,\n endedAt,\n level: \"debug\",\n timestamp: Date.now(),\n timestampIso: new Date().toISOString(),\n });\n },\n };\n }\n\n async timeAsync<T>(\n event: string,\n operation: () => Promise<T>,\n data?: unknown,\n ): Promise<T> {\n if (!this.isEnabled()) {\n return operation();\n }\n\n const timer = this.startTimer(event, data);\n try {\n const result = await operation();\n timer.stop({ ok: true });\n return result;\n } catch (error) {\n timer.stop({ ok: false, error });\n throw error;\n }\n }\n\n time<T>(event: string, operation: () => T, data?: unknown): T {\n if (!this.isEnabled()) {\n return operation();\n }\n\n const timer = this.startTimer(event, data);\n try {\n const result = operation();\n timer.stop({ ok: true });\n return result;\n } catch (error) {\n timer.stop({ ok: false, error });\n throw error;\n }\n }\n\n private record(\n event: Omit<TinyCloudDebugEvent, \"sequence\">,\n ): TinyCloudDebugEvent {\n const debugEvent: TinyCloudDebugEvent = {\n ...event,\n ...(event.data === undefined\n ? {}\n : { data: projectDiagnosticData(event.data) }),\n ...(event.message === undefined ? {} : { message: \"[REDACTED]\" }),\n sequence: ++this.sequence,\n };\n\n this.events.push(debugEvent);\n if (this.events.length > this.maxEvents) {\n this.events.splice(0, this.events.length - this.maxEvents);\n }\n\n try {\n globalThis.console?.debug?.(\"[TinyCloud]\", debugEvent.event, debugEvent);\n } catch {\n // Debug logging should never affect SDK behavior.\n }\n\n return debugEvent;\n }\n}\n\nexport const tinyCloudDebugLogger = new TinyCloudDebugLogger();\n\nexport function enableTinyCloudDebug(\n options?: TinyCloudDebugEnableOptions,\n): TinyCloudDebugLogger {\n tinyCloudDebugLogger.enable(options);\n return tinyCloudDebugLogger;\n}\n\nexport function disableTinyCloudDebug(\n options?: TinyCloudDebugEnableOptions,\n): TinyCloudDebugLogger {\n tinyCloudDebugLogger.disable(options);\n return tinyCloudDebugLogger;\n}\n\nexport function getTinyCloudDebugLogs(): TinyCloudDebugEvent[] {\n return tinyCloudDebugLogger.getLogs();\n}\n\nexport function clearTinyCloudDebugLogs(): void {\n tinyCloudDebugLogger.clear();\n}\n\nexport function installTinyCloudDebugGlobals(): void {\n const global = getGlobal();\n global.TinyCloudDebug = tinyCloudDebugLogger;\n global.enableTinyCloudDebug = enableTinyCloudDebug;\n global.disableTinyCloudDebug = disableTinyCloudDebug;\n global.getTinyCloudDebugLogs = getTinyCloudDebugLogs;\n global.clearTinyCloudDebugLogs = clearTinyCloudDebugLogs;\n}\n\ninstallTinyCloudDebugGlobals();\n","export interface ParsedServiceErrorBody {\n error?: string;\n message?: string;\n code?: string;\n}\n\nexport function parseServiceErrorBody(errorText: string): ParsedServiceErrorBody {\n try {\n return JSON.parse(errorText) as ParsedServiceErrorBody;\n } catch {\n return {};\n }\n}\n\nexport function formatServiceResponseError(\n serviceLabel: string,\n operation: string,\n status: number,\n errorText: string,\n parsed: ParsedServiceErrorBody,\n): string {\n if (parsed.message) {\n return compactMessage(parsed.message);\n }\n\n if (looksLikeHtml(errorText)) {\n if (status === 524 || /524\\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {\n return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;\n }\n return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;\n }\n\n return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;\n}\n\nexport function responseErrorMeta(\n status: number,\n statusText: string,\n errorText: string,\n): Record<string, unknown> {\n const meta: Record<string, unknown> = { status, statusText };\n const snippet = compactMessage(errorText);\n if (snippet) {\n meta.responseSnippet = snippet.slice(0, 300);\n }\n return meta;\n}\n\nfunction looksLikeHtml(text: string): boolean {\n return /<!doctype html/i.test(text) || /<html[\\s>]/i.test(text);\n}\n\nfunction compactMessage(message: string): string {\n const compact = message.replace(/\\s+/g, \" \").trim();\n return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;\n}\n","/**\n * SDK Services - Error Utilities\n *\n * Utilities for creating and handling service errors.\n */\n\nimport { responseErrorMeta } from \"./responseErrors\";\nimport { CAPABILITY_REGISTRY } from \"@tinycloud/bootstrap\";\nimport { parseNetworkId } from \"./encryption/networkId\";\nimport { routingFailure } from \"./resourceRouting\";\n\nimport {\n type ServiceError,\n ErrorCodes,\n err,\n serviceError,\n type PermissionHint,\n} from \"./types\";\n\n/** Validate one structured node permission hint without accepting raw errors. */\nexport function parsePermissionHint(value: unknown): PermissionHint | undefined {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n const candidate = value as Record<string, unknown>;\n const keys = Object.keys(candidate).sort();\n if (keys.some((key) => ![\"actions\", \"path\", \"service\", \"space\"].includes(key))) return undefined;\n if (candidate.service !== \"tinycloud.kv\" && candidate.service !== \"tinycloud.encryption\") return undefined;\n if (typeof candidate.path !== \"string\" || candidate.path.length === 0 || candidate.path.includes(\"*\")) return undefined;\n if (candidate.service === \"tinycloud.kv\" &&\n (typeof candidate.space !== \"string\" || candidate.space.length === 0 || !candidate.path.startsWith(\"vault/\") || candidate.path.endsWith(\"/\"))) {\n return undefined;\n }\n if (candidate.service === \"tinycloud.encryption\" &&\n (candidate.space !== undefined || !candidate.path.startsWith(\"urn:tinycloud:encryption:\") || candidate.path.endsWith(\":\"))) {\n return undefined;\n }\n if (!Array.isArray(candidate.actions) || candidate.actions.length !== 1 ||\n typeof candidate.actions[0] !== \"string\" || candidate.actions[0].includes(\"*\")) return undefined;\n const expectedAction = candidate.service === \"tinycloud.kv\"\n ? \"tinycloud.kv/get\"\n : \"tinycloud.encryption/decrypt\";\n if (candidate.actions[0] !== expectedAction) return undefined;\n const space = typeof candidate.space === \"string\" ? candidate.space : undefined;\n return {\n service: candidate.service,\n ...(space === undefined ? {} : { space }),\n path: candidate.path,\n actions: [candidate.actions[0]],\n };\n}\n\n/** Extract only a validated structured hint from an SDK-owned error body. */\nexport function parsePermissionHintFromErrorText(text: string): PermissionHint | undefined {\n try {\n const parsed: unknown = JSON.parse(text);\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n const root = parsed as Record<string, unknown>;\n const nested = root.error;\n const nestedRecord = typeof nested === \"object\" && nested !== null\n ? nested as Record<string, unknown>\n : undefined;\n return parsePermissionHint(root.permissionHint) ??\n parsePermissionHint(nestedRecord?.permissionHint);\n } catch {\n return undefined;\n }\n}\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 * Classify a node retry signal (tinycloud-node #347, TC-1054). The node's\n * bodies are plain text led by a stable token:\n * - `503 sql_busy: <stage>: ...` and `503 unavailable_retryable: ...` (and\n * `read_audit_busy`) are `NODE_BUSY`: nothing was applied; retry after `meta.retryAfterMs`.\n * - `503 request_timeout: ...` is `REQUEST_TIMEOUT`: the deadline passed before\n * any write began, so nothing was applied.\n * - `500 outcome_unknown: ...` is `OUTCOME_UNKNOWN`: a write may have been\n * applied; read back before retrying a non-idempotent operation.\n *\n * `meta.retryable` is the safe-to-retry verdict and `meta.retryAfterMs` the\n * node's `Retry-After` (the node sends whole seconds; anything else is\n * ignored). `invokeWithRetry` retries `NODE_BUSY` and `REQUEST_TIMEOUT`;\n * `OUTCOME_UNKNOWN` is never retried. Returns `undefined` for any\n * other response, including the bodyless 503/500 of nodes before 1.21, which\n * callers keep classifying as they always did.\n */\nexport function nodeRetryError(\n service: string,\n message: string,\n response: { status: number; statusText: string; headers: { get(name: string): string | null } },\n errorText: string\n): ServiceError | undefined {\n const body = errorText.trim();\n const [, token, stage] = /^([a-z_]+)(?::\\s*([a-z_]+):)?/.exec(body) ?? [];\n let code: string;\n if (response.status === 503 && (token === \"sql_busy\" || token === \"unavailable_retryable\" || token === \"read_audit_busy\")) {\n code = ErrorCodes.NODE_BUSY;\n } else if (response.status === 503 && token === \"request_timeout\") {\n code = ErrorCodes.REQUEST_TIMEOUT;\n } else if (response.status === 500 && token === \"outcome_unknown\") {\n code = ErrorCodes.OUTCOME_UNKNOWN;\n } else {\n return undefined;\n }\n const retryable = code !== ErrorCodes.OUTCOME_UNKNOWN;\n const retryAfter = response.headers.get(\"retry-after\")?.trim();\n return serviceError(code, message, service, {\n meta: {\n ...responseErrorMeta(response.status, response.statusText, errorText),\n retryable,\n ...(token === \"sql_busy\" && stage ? { stage } : {}),\n ...(retryable && retryAfter && /^\\d{1,9}$/.test(retryAfter) ? { retryAfterMs: Number(retryAfter) * 1000 } : {}),\n },\n });\n}\n\n/**\n * Abort reason for a service request that exceeded its configured timeout.\n *\n * Named `TimeoutError` (like the reason of the platform's\n * `AbortSignal.timeout()`), so it maps to `ErrorCodes.TIMEOUT` and never to\n * `ErrorCodes.ABORTED`, which is reserved for caller and sign-out aborts.\n */\nexport class RequestTimeoutError extends Error {\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(`Request timed out after ${timeoutMs}ms.`);\n this.name = \"TimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Whether an error is a {@link RequestTimeoutError}. Also matches by shape so\n * that a copy of the class from another bundle (ESM/CJS entrypoint) is\n * recognized.\n */\nexport function isRequestTimeoutError(error: unknown): error is RequestTimeoutError {\n return (\n error instanceof RequestTimeoutError ||\n (error instanceof Error &&\n error.name === \"TimeoutError\" &&\n typeof (error as { timeoutMs?: unknown }).timeoutMs === \"number\")\n );\n}\n\n/**\n * Create a service error for timeouts.\n *\n * @param timeoutMs - The configured timeout that elapsed, when known\n * @param cause - The underlying timeout error, when known\n */\nexport function timeoutError(\n service: string,\n timeoutMs?: number,\n cause?: Error\n): ServiceError {\n if (timeoutMs === undefined) {\n return {\n code: ErrorCodes.TIMEOUT,\n message: \"Request timed out.\",\n service,\n ...(cause === undefined ? {} : { cause }),\n };\n }\n return {\n code: ErrorCodes.TIMEOUT,\n message: `Request timed out after ${timeoutMs}ms.`,\n service,\n ...(cause === undefined ? {} : { cause }),\n meta: { timeoutMs },\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 * Authorization verdict read from an error's structure, never its message:\n * - `\"unauthenticated\"`: HTTP 401 — the session is missing or stale.\n * - `\"forbidden\"`: HTTP 403, or `AUTH_UNAUTHORIZED` without a status — the\n * session is valid but lacks the capability.\n * - `\"other\"`: a non-authorization HTTP error status (4xx/5xx).\n */\nexport type AuthorizationVerdict = \"unauthenticated\" | \"forbidden\" | \"other\";\n\nconst grantableActions = new Set(\n CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith(\"/*\"))\n);\n\nconst capabilityServices = new Set(\n CAPABILITY_REGISTRY.map(({ service }) => service.slice(\"tinycloud.\".length))\n);\n\n/**\n * Parse a canonical TinyCloud resource, keeping nested space names intact.\n * The first registered service segment is authoritative: a later matching\n * segment in the path cannot turn a different service into grant advice.\n */\nexport function parseCapabilityResource(\n resource: string,\n service: string\n): { space: string; path: string } | undefined {\n if (\n resource.length === 0 ||\n resource.length > 1024 ||\n !capabilityServices.has(service) ||\n /[\\s\\p{C}*#]/u.test(resource) ||\n resource.includes(\"://\")\n ) return undefined;\n\n const segments = resource.split(\"/\");\n if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return undefined;\n const serviceIndex = segments.findIndex((segment, index) =>\n index > 0 && capabilityServices.has(segment)\n );\n if (serviceIndex < 1 || segments[serviceIndex] !== service) return undefined;\n if (segments.slice(1, serviceIndex).some((segment) =>\n segment.length === 0 || segment === \".\" || segment === \"..\"\n )) return undefined;\n const pathSegments = segments.slice(serviceIndex + 1);\n if (pathSegments.some((segment) => segment === \".\" || segment === \"..\")) return undefined;\n if (service !== \"kv\" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === \"\")) return undefined;\n\n return {\n space: segments.slice(0, serviceIndex).join(\"/\"),\n path: pathSegments.join(\"/\"),\n };\n}\n\n/** A capability resource is a canonical TinyCloud resource or encryption network, never a URL or expression. */\nfunction safeCapabilityResource(value: unknown, service: string, action: string): value is string {\n if (typeof value !== \"string\" || value.length === 0 || value.length > 1024) return false;\n if (service === \"encryption\" && value.startsWith(\"urn:tinycloud:encryption:\")) {\n if (\n !/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) ||\n value.includes(\"//\") ||\n value.split(\"/\").some((segment) => segment === \".\" || segment === \"..\")\n ) return false;\n try {\n parseNetworkId(value);\n return true;\n } catch {\n return false;\n }\n }\n const parsed = parseCapabilityResource(value, service);\n return parsed !== undefined &&\n (!parsed.path.endsWith(\"/\") || (service === \"kv\" && action === \"tinycloud.kv/list\")) &&\n (parsed.path !== \"\" || (service === \"kv\" && action === \"tinycloud.kv/list\"));\n}\n\n/**\n * Return a grantable missing capability only when typed authorization evidence\n * and both safe, service-matched capability fields are present. Status alone,\n * message text, wildcard actions and unrecognized actions cannot grant scope.\n */\nexport function validatedCapabilityOf(\n error: unknown\n): { resource: string; requiredAction: string } | undefined {\n const verdict = authorizationVerdictOf(error);\n if (verdict !== \"unauthenticated\" && verdict !== \"forbidden\") return undefined;\n\n const seen = new Set<unknown>();\n let node: unknown = error;\n for (\n let depth = 0;\n depth < MAX_CAUSE_DEPTH && typeof node === \"object\" && node !== null && !seen.has(node);\n depth += 1\n ) {\n seen.add(node);\n const record = node as Record<string, unknown>;\n const status = errorStatusOf(record);\n const meta = record.meta;\n if (\n (status === 401 || status === 403 || (status === undefined && record.code === ErrorCodes.AUTH_UNAUTHORIZED)) &&\n typeof meta === \"object\" && meta !== null && !Array.isArray(meta)\n ) {\n const metadata = meta as Record<string, unknown>;\n const { resource, requiredAction } = metadata;\n if (typeof requiredAction === \"string\" && grantableActions.has(requiredAction)) {\n const actionService = requiredAction.slice(\"tinycloud.\".length, requiredAction.indexOf(\"/\"));\n const explicitService = record.service === undefined ? metadata.service : record.service;\n if (\n safeCapabilityResource(resource, actionService, requiredAction) &&\n (explicitService === undefined ||\n explicitService === actionService ||\n explicitService === `tinycloud.${actionService}`)\n ) {\n return { resource, requiredAction };\n }\n }\n }\n node = record.cause;\n }\n return undefined;\n}\n\nconst MAX_CAUSE_DEPTH = 8;\n\n/** The first 4xx/5xx among `status`, `statusCode`, `meta.status`. Success and\n * informational statuses (e.g. a 200 `Response` kept as `cause`) are not\n * failures, so they are skipped rather than ending the search. */\nfunction errorStatusOf(node: Record<string, unknown>): number | undefined {\n const meta = node.meta;\n const candidates = [\n node.status,\n node.statusCode,\n typeof meta === \"object\" && meta !== null\n ? (meta as Record<string, unknown>).status\n : undefined,\n ];\n for (const candidate of candidates) {\n if (\n typeof candidate === \"number\" &&\n Number.isInteger(candidate) &&\n candidate >= 400 &&\n candidate <= 599\n ) {\n return candidate;\n }\n }\n return undefined;\n}\n\n/**\n * Classify an error from its typed HTTP error status (`status`, `statusCode`,\n * or `meta.status`, 4xx/5xx only) and `AUTH_UNAUTHORIZED` code, following the\n * `cause` chain outer-first (at most {@link MAX_CAUSE_DEPTH} links, cycles\n * stop the walk) so wrappers that rethrow with `cause` keep the verdict. The\n * outermost error status decides, so an outer 5xx keeps its retry budget even\n * over an inner 401. Returns `undefined` for untyped errors; only then may\n * callers fall back to message heuristics.\n */\nexport function authorizationVerdictOf(\n error: unknown,\n): AuthorizationVerdict | undefined {\n const seen = new Set<unknown>();\n let sawUnauthorizedCode = false;\n let node: unknown = error;\n for (\n let depth = 0;\n depth < MAX_CAUSE_DEPTH && typeof node === \"object\" && node !== null && !seen.has(node);\n depth += 1\n ) {\n seen.add(node);\n const record = node as Record<string, unknown>;\n const status = errorStatusOf(record);\n if (status === 401) return \"unauthenticated\";\n if (status === 403) return \"forbidden\";\n if (status !== undefined) return \"other\";\n if (record.code === ErrorCodes.AUTH_UNAUTHORIZED) sawUnauthorizedCode = true;\n node = record.cause;\n }\n return sawUnauthorizedCode ? \"forbidden\" : undefined;\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/** Shown when a write is rejected because the owner's storage is full. */\nexport const STORAGE_FULL_MESSAGE =\n \"TinyCloud storage is full, so this change was not saved. Reading still works. Free up space or upgrade your plan to save again.\";\n\n/** Shown when a write is larger than the storage the owner has left. */\nexport const STORAGE_WRITE_TOO_LARGE_MESSAGE =\n \"This change is larger than the TinyCloud storage you have left, so it was not saved. Reading still works. Free up space or upgrade your plan to save it.\";\n\n/**\n * Parse the byte counts from a node storage rejection\n * (\"... Used: 155744 bytes, Limit: 0 bytes\"). The limit is the share of the\n * owner's account-wide budget left for this space, so it is 0 when the\n * owner's other spaces already use the whole budget.\n */\nexport function parseStorageQuotaBytes(\n responseText: string\n): { usedBytes: number; limitBytes: number } | undefined {\n const match = responseText.match(/Used:\\s*(\\d+)\\s*bytes,\\s*Limit:\\s*(\\d+)\\s*bytes/i);\n if (!match) return undefined;\n return {\n usedBytes: parseInt(match[1], 10),\n limitBytes: parseInt(match[2], 10),\n };\n}\n\n/**\n * Account-wide storage totals the node attaches to a storage rejection when\n * billing supplied them. `limitBytes` is the plan budget shared by all the\n * owner's spaces, so this is the number to show the user, not the space's.\n */\nexport interface StorageAccountUsage {\n usedBytes: number;\n limitBytes: number;\n /** Plan tier id, e.g. `free` or `paid`. */\n plan?: string;\n}\n\n/** What a node storage rejection says about the space and the account. */\nexport interface StorageRejectionDetails {\n usedBytes?: number;\n limitBytes?: number;\n account?: StorageAccountUsage;\n}\n\nconst STORAGE_REJECTION_BODY_ERRORS: Record<string, true> = {\n storage_quota_exceeded: true,\n storage_limit_reached: true,\n};\n\nfunction isByteCount(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n}\n\nfunction byteCountsOf(value: unknown): { usedBytes: number; limitBytes: number } | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const { usedBytes, limitBytes } = value as Record<string, unknown>;\n return isByteCount(usedBytes) && isByteCount(limitBytes) ? { usedBytes, limitBytes } : undefined;\n}\n\n/**\n * Parse a node storage rejection. The node sends a JSON body\n * (`{error, message, space: {usedBytes, limitBytes}, account?: {usedBytes,\n * limitBytes, plan}}`); older nodes send only the plain-text message. Returns\n * undefined when the body is neither, e.g. a proxy's own 413 page.\n */\nexport function parseStorageRejection(responseText: string): StorageRejectionDetails | undefined {\n let body: unknown;\n try {\n body = JSON.parse(responseText);\n } catch {\n return parseStorageQuotaBytes(responseText);\n }\n if (typeof body !== \"object\" || body === null) return undefined;\n const record = body as Record<string, unknown>;\n const message = typeof record.message === \"string\" ? record.message : \"\";\n const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);\n const isStorageBody = typeof record.error === \"string\" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;\n if (!space && !isStorageBody) {\n return undefined;\n }\n const details: StorageRejectionDetails = { ...space };\n const account = byteCountsOf(record.account);\n if (account) {\n const plan = (record.account as Record<string, unknown>).plan;\n details.account = typeof plan === \"string\" && plan !== \"\" ? { ...account, plan } : account;\n }\n return details;\n}\n\n/**\n * Build the error for a write the node rejected for storage: 402 when storage\n * is already full, 413 when this write is larger than what is left. KV, SQL\n * and DuckDB all report it with the same codes and message so an app can\n * detect \"storage full\" in one place with {@link isStorageFullError}.\n * `meta.usedBytes`/`meta.limitBytes` describe the space; `meta.account`\n * carries the account totals when the node sent them.\n */\nexport function storageRejectionError(\n service: string,\n status: 402 | 413,\n meta: Record<string, unknown>,\n responseText: string\n): ServiceError {\n const quotaMeta = { ...meta, ...parseStorageRejection(responseText) };\n return status === 402\n ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta)\n : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);\n}\n\n/**\n * True when a write failed for storage: the owner's TinyCloud storage is full\n * (`STORAGE_QUOTA_EXCEEDED`), or this write is larger than what is left\n * (`STORAGE_LIMIT_REACHED`). Reads keep working in both cases, so apps should\n * stay usable read-only and point the user at freeing up space or upgrading.\n */\nexport function isStorageFullError(error: { code?: string } | null | undefined): boolean {\n return (\n error?.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED ||\n error?.code === ErrorCodes.STORAGE_LIMIT_REACHED\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 const routing = routingFailure(error);\n if (routing) return { ...routing, service };\n if (error instanceof Error) {\n // A configured request timeout elapsed (see BaseService.createRequestSignal)\n if (isRequestTimeoutError(error)) {\n return timeoutError(service, error.timeoutMs, error);\n }\n\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","/** Abortable wait; ref'd unless `unref`. */\nexport const abortableSleep = (ms: number, signal: AbortSignal, unref = false): Promise<void> =>\n new Promise((resolve) => {\n const done = (): void => {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", done);\n resolve();\n };\n const timer = setTimeout(done, ms);\n if (unref) timer.unref?.();\n signal.aborted ? done() : signal.addEventListener(\"abort\", done);\n });\n","/**\n * ServiceContext implementation for TinyCloud SDK Services\n * @module @tinycloud/sdk-services\n */\n\nimport {\n IServiceContext,\n IService,\n ServiceSession,\n RetryPolicy,\n InvokeFunction,\n InvokeAnyFunction,\n FetchFunction,\n FetchRequestInit,\n FetchResponse,\n defaultRetryPolicy,\n EventHandler,\n TelemetryEventHandler,\n TelemetryConfig,\n} from \"./types\";\nimport { tinyCloudDebugLogger } from \"./debug\";\nimport { nodeRetryError } from \"./errors\";\nimport { abortableSleep } from \"./sleep\";\nimport { projectDiagnosticData } from \"./diagnostics\";\n\n/**\n * Configuration options for ServiceContext.\n */\nexport interface ServiceContextConfig {\n /** Function to invoke WASM operations */\n invoke: InvokeFunction;\n /** Optional function to mint a single authorization header for multiple capabilities */\n invokeAny?: InvokeAnyFunction;\n /** Function to make HTTP requests (defaults to globalThis.fetch) */\n fetch?: FetchFunction;\n /** List of TinyCloud host URLs */\n hosts: string[];\n /** Initial session (optional) */\n session?: ServiceSession | null;\n /** Retry policy configuration */\n retryPolicy?: Partial<RetryPolicy>;\n /** Default-off telemetry event delivery. */\n telemetry?: TelemetryConfig;\n}\n\n/**\n * ServiceContext provides platform dependencies and cross-service access to services.\n * This is the primary interface services use to interact with the SDK runtime.\n *\n * @example\n * ```typescript\n * const context = new ServiceContext({\n * invoke: wasmInvoke,\n * hosts: ['https://node.tinycloud.xyz'],\n * retryPolicy: { maxAttempts: 5 },\n * });\n *\n * // Register a service\n * const kvService = new KVService({});\n * context.registerService('kv', kvService);\n * kvService.initialize(context);\n *\n * // Update session when user signs in\n * context.setSession(userSession);\n * ```\n */\nexport class ServiceContext implements IServiceContext {\n private _session: ServiceSession | null = null;\n private _services: Map<string, IService> = new Map();\n private _eventHandlers: Map<string, Set<EventHandler>> = new Map();\n private _abortController: AbortController = new AbortController();\n private _operationAbortSignal?: AbortSignal;\n private _retired = false;\n private readonly _invoke: InvokeFunction;\n private readonly _invokeAny?: InvokeAnyFunction;\n private readonly _fetch: FetchFunction;\n private readonly _hosts: string[];\n private readonly _retryPolicy: RetryPolicy;\n private readonly _telemetryEnabled: boolean;\n private readonly _telemetryHandler?: TelemetryEventHandler;\n\n constructor(config: ServiceContextConfig) {\n this._invoke = this.wrapInvoke(config.invoke);\n this._invokeAny = config.invokeAny\n ? this.wrapInvokeAny(config.invokeAny)\n : undefined;\n this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));\n this._hosts = config.hosts;\n this._session = config.session ?? null;\n this._retryPolicy = {\n ...defaultRetryPolicy,\n ...config.retryPolicy,\n };\n this._telemetryEnabled =\n typeof config.telemetry === \"boolean\"\n ? config.telemetry\n : config.telemetry?.enabled === true;\n this._telemetryHandler =\n typeof config.telemetry === \"object\" ? config.telemetry.onEvent : undefined;\n }\n\n // ============================================================\n // Session Management\n // ============================================================\n\n /**\n * Get the current session.\n */\n get session(): ServiceSession | null {\n return this._retired ? null : this._session;\n }\n\n /**\n * Check if the context has an authenticated session.\n */\n get isAuthenticated(): boolean {\n return !this._retired && this._session !== null;\n }\n\n /**\n * Update the session and notify all registered services.\n *\n * @param session - New session or null to clear\n */\n setSession(session: ServiceSession | null): void {\n this.assertActive();\n this._session = session;\n this.emit('session.changed', { authenticated: session !== null });\n\n // Notify all services of session change\n for (const service of this._services.values()) {\n service.onSessionChange(session);\n }\n }\n\n // ============================================================\n // Platform Dependencies\n // ============================================================\n\n /**\n * Get the invoke function for WASM operations.\n */\n get invoke(): InvokeFunction {\n this.assertActive();\n return this._invoke;\n }\n\n /**\n * Get the multi-resource invoke function when available.\n */\n get invokeAny(): InvokeAnyFunction | undefined {\n this.assertActive();\n return this._invokeAny;\n }\n\n /**\n * Get the fetch function for HTTP requests.\n */\n get fetch(): FetchFunction {\n this.assertActive();\n return this._fetch;\n }\n\n /**\n * Get the list of TinyCloud host URLs.\n */\n get hosts(): string[] {\n this.assertActive();\n return this._hosts;\n }\n\n // ============================================================\n // Service Registry\n // ============================================================\n\n /**\n * Register a service with the context.\n *\n * @param name - Service name (e.g., 'kv')\n * @param service - Service instance\n */\n registerService(name: string, service: IService): void {\n this._services.set(name, service);\n }\n\n /**\n * Unregister a service from the context.\n *\n * @param name - Service name to remove\n */\n unregisterService(name: string): void {\n this._services.delete(name);\n }\n\n /**\n * Get a registered service by name.\n *\n * @param name - Service name\n * @returns The service instance or undefined if not registered\n */\n getService<T extends IService>(name: string): T | undefined {\n return this._services.get(name) as T | undefined;\n }\n\n // ============================================================\n // Event System (Telemetry)\n // ============================================================\n\n /**\n * Emit a telemetry event.\n *\n * @param event - Event name\n * @param data - Event data\n */\n emit(event: string, data: unknown): void {\n tinyCloudDebugLogger.log(event, data);\n\n if (this._telemetryEnabled && this._telemetryHandler) {\n try {\n this._telemetryHandler(event, projectDiagnosticData(data));\n } catch (error) {\n // Don't let telemetry handlers break SDK operations.\n console.error(`Error in telemetry handler for \"${event}\":`, error);\n }\n }\n\n const handlers = this._eventHandlers.get(event);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(data);\n } catch (error) {\n // Don't let event handler errors break the flow\n console.error(`Error in event handler for \"${event}\":`, error);\n }\n }\n }\n }\n\n /**\n * Subscribe to telemetry events.\n *\n * @param event - Event name to subscribe to\n * @param handler - Handler function\n * @returns Unsubscribe function\n */\n on(event: string, handler: EventHandler): () => void {\n if (!this._eventHandlers.has(event)) {\n this._eventHandlers.set(event, new Set());\n }\n this._eventHandlers.get(event)!.add(handler);\n\n // Return unsubscribe function\n return () => {\n const handlers = this._eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n this._eventHandlers.delete(event);\n }\n }\n };\n }\n\n /**\n * Remove all event handlers for an event.\n *\n * @param event - Event name (if omitted, clears all events)\n */\n clearEventHandlers(event?: string): void {\n if (event) {\n this._eventHandlers.delete(event);\n } else {\n this._eventHandlers.clear();\n }\n }\n\n // ============================================================\n // Lifecycle\n // ============================================================\n\n /**\n * Get the abort signal for cancelling operations.\n */\n get abortSignal(): AbortSignal {\n return this._abortController.signal;\n }\n\n get operationAbortSignal(): AbortSignal | undefined {\n return this._operationAbortSignal;\n }\n\n setOperationAbortSignal(signal: AbortSignal | undefined): void {\n this._operationAbortSignal = signal;\n }\n /**\n * Permanently retire this graph after its owner installs a replacement.\n * Unlike `abort()`, retirement never creates a fresh controller, so captured\n * services cannot resume calls under a superseded session.\n */\n retire(): void {\n if (this._retired) return;\n this._retired = true;\n this._session = null;\n this._abortController.abort();\n for (const service of this._services.values()) {\n // User-provided services are part of the retired graph, but their\n // cleanup must never make installing the replacement graph fail.\n try {\n service.onSignOut();\n } catch (error) {\n console.error(\"Error retiring service:\", error);\n }\n }\n }\n\n /**\n * Abort all pending operations and notify services.\n * Creates a new AbortController for future operations.\n */\n abort(): void {\n // A retired graph must never receive a fresh abort signal. Captured\n // services retain this context, so recreating its controller would make a\n // stale graph look live to custom cleanup code.\n if (this._retired) return;\n this._abortController.abort();\n this._abortController = new AbortController();\n\n // Notify all services\n for (const service of this._services.values()) {\n try {\n service.onSignOut();\n } catch (error) {\n console.error(\"Error signing out service:\", error);\n }\n }\n }\n\n /**\n * Sign out - abort operations and clear session.\n */\n signOut(): void {\n if (this._retired) return;\n this.abort();\n this.setSession(null);\n this.emit('session.expired', {});\n }\n\n // ============================================================\n // Retry Policy\n // ============================================================\n\n /**\n * Get the retry policy configuration.\n */\n get retryPolicy(): RetryPolicy {\n return this._retryPolicy;\n }\n\n private wrapInvoke(invoke: InvokeFunction): InvokeFunction {\n return (session, service, path, action, facts) => {\n this.assertActive();\n if (!tinyCloudDebugLogger.isEnabled()) {\n return invoke(session, service, path, action, facts);\n }\n\n const timer = tinyCloudDebugLogger.startTimer(\"sdk.invoke\", {\n service,\n path,\n action,\n facts,\n });\n\n try {\n const result = invoke(session, service, path, action, facts);\n timer.stop({ ok: true, service, path, action });\n return result;\n } catch (error) {\n timer.stop({ ok: false, service, path, action, error });\n throw error;\n }\n };\n }\n\n private assertActive(): void {\n if (this._retired) {\n throw new Error(\"Service graph has been retired: its session was replaced or signed out.\");\n }\n }\n\n private wrapInvokeAny(invokeAny: InvokeAnyFunction): InvokeAnyFunction {\n return (session, entries, facts) => {\n this.assertActive();\n if (!tinyCloudDebugLogger.isEnabled()) {\n return invokeAny(session, entries, facts);\n }\n\n const timer = tinyCloudDebugLogger.startTimer(\"sdk.invokeAny\", {\n entries,\n facts,\n });\n\n try {\n const result = invokeAny(session, entries, facts);\n timer.stop({ ok: true, entries });\n return result;\n } catch (error) {\n timer.stop({ ok: false, entries, error });\n throw error;\n }\n };\n }\n\n /** Retry `NODE_BUSY` / `REQUEST_TIMEOUT`; `build` must re-sign per attempt (the node 409s a repeated invocation). */\n async invokeWithRetry(\n url: string,\n build: () => FetchRequestInit | Promise<FetchRequestInit>,\n ): Promise<FetchResponse> {\n this.assertActive();\n const policy = this._retryPolicy;\n const startedAt = Date.now();\n let backoffMs = 0;\n let signal: AbortSignal | undefined;\n for (let attempt = 1; ; attempt++) {\n const init = await build();\n signal ??= this.combineSignals(init.signal);\n const response = await this._fetch(url, init);\n const done = (): FetchResponse => {\n try { response.retry = { attempts: attempt, backoffMs }; } catch { /* frozen custom response */ }\n return response;\n };\n if (attempt >= policy.maxAttempts || response.status !== 503 || !response.clone) return done();\n const text = await response.clone().text().catch(() => \"\");\n const error = nodeRetryError(\"\", \"\", response, text);\n if (!error || error.meta?.retryable !== true || !policy.retryableErrors.includes(error.code)) {\n return done();\n }\n const remaining = (policy.maxTotalMs ?? Infinity) - (Date.now() - startedAt);\n if (remaining <= 0) return done();\n const retryAfterMs = error.meta.retryAfterMs;\n const step = Math.min(\n policy.maxDelayMs,\n policy.backoff === \"exponential\"\n ? policy.baseDelayMs * 2 ** (attempt - 1)\n : policy.backoff === \"linear\"\n ? policy.baseDelayMs * attempt\n : 0,\n );\n const delay = Math.min(\n typeof retryAfterMs === \"number\" ? retryAfterMs : Math.random() * step,\n remaining,\n );\n (response.body as { cancel?(): Promise<unknown> } | undefined)?.cancel?.()?.catch?.(() => {});\n await abortableSleep(delay, signal);\n if (signal.aborted) throw signal.reason;\n backoffMs += delay;\n }\n }\n\n private wrapFetch(fetchFn: FetchFunction): FetchFunction {\n return async (url, init) => {\n this.assertActive();\n const request = {\n ...init,\n signal: this.combineSignals(init?.signal),\n };\n if (!tinyCloudDebugLogger.isEnabled()) {\n return fetchFn(url, request);\n }\n\n const meta = { url, method: request.method ?? \"GET\" };\n const timer = tinyCloudDebugLogger.startTimer(\"sdk.fetch\", { ...meta, init: request });\n\n try {\n const response = await fetchFn(url, request);\n timer.stop({\n ...meta,\n ok: response.ok,\n status: response.status,\n statusText: response.statusText,\n });\n return response;\n } catch (error) {\n timer.stop({ ...meta, ok: false, error });\n throw error;\n }\n };\n }\n\n private combineSignals(signal?: AbortSignal): AbortSignal {\n if (!signal) return this._abortController.signal;\n // `AbortSignal.any` links the two signals for as long as the composite lives, with no listener on the long-lived\n // signal (TC-2418), so aborting the context (sign-out) still cancels a response body being read. Trade-off: on\n // Node its composites stay reachable while the long-lived signal lives (about 65 B per request on Node 20, 180 B\n // on Node 22; a listener that is never removed retains about 1 KB). Where it is missing (older browsers) the\n // listeners below are unchanged.\n if (typeof AbortSignal.any === \"function\") return AbortSignal.any([signal, this._abortController.signal]);\n const combined = new AbortController();\n const abort = (source: AbortSignal) => combined.abort(source.reason);\n if (signal.aborted) {\n abort(signal);\n } else if (this._abortController.signal.aborted) {\n abort(this._abortController.signal);\n } else {\n signal.addEventListener(\"abort\", () => abort(signal), { once: true });\n this._abortController.signal.addEventListener(\n \"abort\",\n () => abort(this._abortController.signal),\n { once: true },\n );\n }\n return combined.signal;\n }\n}\n\n"],"mappings":"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","names":[]}