@dashai/sdk 0.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -13
- package/dist/auth/client.cjs +23 -1
- package/dist/auth/client.cjs.map +1 -1
- package/dist/auth/client.d.cts +30 -5
- package/dist/auth/client.d.ts +30 -5
- package/dist/auth/client.js +23 -1
- package/dist/auth/client.js.map +1 -1
- package/dist/auth/middleware.cjs +11 -1
- package/dist/auth/middleware.cjs.map +1 -1
- package/dist/auth/middleware.js +11 -1
- package/dist/auth/middleware.js.map +1 -1
- package/dist/auth/server.cjs +36 -7
- package/dist/auth/server.cjs.map +1 -1
- package/dist/auth/server.js +36 -7
- package/dist/auth/server.js.map +1 -1
- package/dist/auth/types.cjs.map +1 -1
- package/dist/auth/types.d.cts +13 -0
- package/dist/auth/types.d.ts +13 -0
- package/dist/auth/types.js.map +1 -1
- package/dist/deps/index.cjs +82 -7
- package/dist/deps/index.cjs.map +1 -1
- package/dist/deps/index.d.cts +28 -10
- package/dist/deps/index.d.ts +28 -10
- package/dist/deps/index.js +81 -8
- package/dist/deps/index.js.map +1 -1
- package/dist/index-BsSFz58g.d.cts +431 -0
- package/dist/index-BsSFz58g.d.ts +431 -0
- package/dist/index.cjs +318 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -53
- package/dist/index.d.ts +66 -53
- package/dist/index.js +313 -26
- package/dist/index.js.map +1 -1
- package/dist/query/index.cjs +158 -2
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +7 -7
- package/dist/query/index.d.ts +7 -7
- package/dist/query/index.js +158 -2
- package/dist/query/index.js.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/{types-DXsbCVkb.d.cts → types-CkAfiS4k.d.cts} +106 -38
- package/dist/{types-DXsbCVkb.d.ts → types-CkAfiS4k.d.ts} +106 -38
- package/dist/uses/index.cjs +147 -0
- package/dist/uses/index.cjs.map +1 -0
- package/dist/uses/index.d.cts +1 -0
- package/dist/uses/index.d.ts +1 -0
- package/dist/uses/index.js +142 -0
- package/dist/uses/index.js.map +1 -0
- package/package.json +6 -1
- package/dist/errors-BV75u7b9.d.cts +0 -207
- package/dist/errors-BV75u7b9.d.ts +0 -207
package/dist/query/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/data/errors.ts","../../src/query/ast.ts","../../src/query/builder.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Error model for @dashai/sdk.\n *\n * The backend speaks a structured error envelope:\n *\n * {\n * \"code\": \"ROW_NOT_FOUND\",\n * \"message\": \"Row 123 not found in tasks\",\n * \"retriable\": false,\n * \"context\": { ... }\n * }\n *\n * This module wraps that envelope in a typed `DashwiseError` plus a small\n * set of subclasses for the codes consumers most commonly want to branch\n * on (network errors, auth failures, row-not-found, contract violations).\n *\n * Anything not matched by a specific subclass becomes a plain\n * `DashwiseError` carrying the raw `code`. Branch on `.code` for codes\n * we don't have a class for; use `instanceof` for the common cases.\n */\n\n/**\n * Known error codes. Not exhaustive — the backend emits many codes\n * specific to individual modules / endpoints. Use these constants when\n * you want compile-time safety; otherwise treat `err.code` as a string.\n *\n * The list is curated to the codes a module author is most likely to\n * branch on. Adding a new code here is a non-breaking change.\n */\nexport const DashwiseErrorCode = {\n // Auth / authz\n UNAUTHENTICATED: 'UNAUTHENTICATED',\n FORBIDDEN: 'FORBIDDEN',\n TOKEN_EXPIRED: 'TOKEN_EXPIRED',\n INSTALLATION_NOT_FOUND: 'INSTALLATION_NOT_FOUND',\n INSTALLATION_MODE_UNSUPPORTED: 'INSTALLATION_MODE_UNSUPPORTED',\n // Contract enforcement\n CONTRACT_VIOLATION: 'CONTRACT_VIOLATION',\n FIELD_NOT_READABLE: 'FIELD_NOT_READABLE',\n FILTER_OP_UNKNOWN: 'FILTER_OP_UNKNOWN',\n OPERATION_NOT_ALLOWED: 'OPERATION_NOT_ALLOWED',\n // Cross-module dependencies (Phase 1 of the SDK query plane —\n // see dashwise-devops-docs/plans/modules-sdk-query-v1.md).\n // Surfaced when reading borrowed tables via `client.deps(slug)`.\n DEPENDENCY_NOT_DECLARED: 'DEPENDENCY_NOT_DECLARED',\n TABLE_NOT_IN_CONTRACT: 'TABLE_NOT_IN_CONTRACT',\n PROVIDER_TABLE_MISSING: 'PROVIDER_TABLE_MISSING',\n OPERATION_NOT_ALLOWED_BY_PROVIDER: 'OPERATION_NOT_ALLOWED_BY_PROVIDER',\n // Query-plane joins (Phase 3 — see\n // dashwise-devops-docs/plans/modules-sdk-query-v1-p3-execution.md).\n // Surfaced when `.join('<slug>')` / `.leftJoin('<slug>')` can't\n // resolve the target. Pre-declared in slice 3.1 so slice 3.2's\n // backend translator can emit them through fromBackendEnvelope\n // without an SDK roundtrip.\n LINK_FIELD_NOT_FOUND: 'LINK_FIELD_NOT_FOUND',\n NOT_A_LINK_FIELD: 'NOT_A_LINK_FIELD',\n JOIN_NOT_LINKED: 'JOIN_NOT_LINKED',\n // Cross-module actions (Phase 7 slice 7.3a — see\n // dashwise-devops-docs/plans/modules-sdk-query-v1-p7-cross-module-writes.md).\n // Surfaced when `qb.deps.<dep>.actions.<name>(input)` (generated\n // factory) dispatches against the new backend endpoint\n // `POST :installationId/deps/:providerSlug/actions/:actionName`.\n // Slice 7.2b ships the backend emitter; slice 7.3a (this slice)\n // reserves the codes + maps them to DepActionError in the SDK.\n ACTION_NOT_EXPOSED: 'ACTION_NOT_EXPOSED',\n ACTION_NOT_DECLARED: 'ACTION_NOT_DECLARED',\n ACTION_INPUT_INVALID: 'ACTION_INPUT_INVALID',\n ACTION_OUTPUT_INVALID: 'ACTION_OUTPUT_INVALID',\n ACTION_RATE_LIMITED: 'ACTION_RATE_LIMITED',\n ACTION_EXECUTION_FAILED: 'ACTION_EXECUTION_FAILED',\n // Data plane\n ROW_NOT_FOUND: 'ROW_NOT_FOUND',\n TABLE_NOT_FOUND: 'TABLE_NOT_FOUND',\n VALIDATION_FAILED: 'VALIDATION_FAILED',\n ROW_LIMIT_EXCEEDED: 'ROW_LIMIT_EXCEEDED',\n STORAGE_LIMIT_EXCEEDED: 'STORAGE_LIMIT_EXCEEDED',\n // Outbound / runtime\n OUTBOUND_NOT_ALLOWED: 'OUTBOUND_NOT_ALLOWED',\n OUTBOUND_CONCURRENCY_LIMITED: 'OUTBOUND_CONCURRENCY_LIMITED',\n // Transport\n NETWORK_ERROR: 'NETWORK_ERROR',\n TIMEOUT: 'TIMEOUT',\n // Server\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n} as const;\n\nexport type DashwiseErrorCode = (typeof DashwiseErrorCode)[keyof typeof DashwiseErrorCode];\n\n/**\n * Base class for every error thrown by @dashai/sdk. Carries the\n * structured envelope from the backend (or a synthetic one for\n * client-side failures like network errors / timeouts).\n *\n * Always thrown — never returned as a value. The async client methods\n * `.list()` / `.get()` etc. reject with one of these on failure.\n */\nexport class DashwiseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly retriable: boolean;\n readonly context: Record<string, unknown> | undefined;\n /**\n * Backend-issued trace anchor (Phase 6 slice 6.2, 2026-05-19).\n * Mirrors the `x-request-id` response header. Populated whenever\n * the backend's `RuntimeQueryController` (or any future endpoint\n * that echoes the header) is the origin of this error. `null` when\n * the failure is client-side (NetworkError / TimeoutError — the\n * request never reached the server, so there's no server-issued\n * ID to surface).\n *\n * Use this as a debugging trace anchor when filing a bug against a\n * specific failed query — the matching row in `module_query_log`\n * carries the full backend context (ast_hash, error_code,\n * duration_ms, etc.).\n */\n readonly requestId: string | null;\n\n constructor(\n code: string,\n message: string,\n options: {\n status?: number;\n retriable?: boolean;\n context?: Record<string, unknown>;\n cause?: unknown;\n /**\n * Phase 6 slice 6.2: backend `x-request-id` response header,\n * captured by the transport before throwing. Pass `undefined`\n * (or omit) for client-side errors that never hit the server.\n */\n requestId?: string | null;\n } = {},\n ) {\n super(message);\n this.name = this.constructor.name;\n this.code = code;\n this.status = options.status ?? 0;\n this.retriable = options.retriable ?? false;\n this.context = options.context;\n this.requestId = options.requestId ?? null;\n if (options.cause !== undefined) {\n // Node 18+ and modern browsers support Error.cause natively.\n // Use a property assignment because `super({ cause })` is awkward\n // when the base class only takes a string.\n (this as { cause?: unknown }).cause = options.cause;\n }\n }\n}\n\n/** Network-level failure (DNS, connection refused, TLS, fetch threw). */\nexport class NetworkError extends DashwiseError {\n constructor(message: string, cause?: unknown) {\n super(DashwiseErrorCode.NETWORK_ERROR, message, { retriable: true, cause });\n }\n}\n\n/** Request exceeded the configured timeout (`timeoutMs`). */\nexport class TimeoutError extends DashwiseError {\n constructor(message: string, timeoutMs: number) {\n super(DashwiseErrorCode.TIMEOUT, message, {\n retriable: true,\n context: { timeout_ms: timeoutMs },\n });\n }\n}\n\n/** 401 — token missing/invalid. */\nexport class UnauthenticatedError extends DashwiseError {\n constructor(message = 'Authentication required', requestId: string | null = null) {\n super(DashwiseErrorCode.UNAUTHENTICATED, message, { status: 401, requestId });\n }\n}\n\n/** 403 — caller lacks the role/scope for this operation. */\nexport class ForbiddenError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.FORBIDDEN, message, { status: 403, context, requestId });\n }\n}\n\n/** 404 — `GET /db/<table>/<id>` for a missing or soft-deleted row. */\nexport class RowNotFoundError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.ROW_NOT_FOUND, message, { status: 404, context, requestId });\n }\n}\n\n/**\n * Schema-level rejection: dep contract violated, table not exposed, field\n * not readable, filter op not allowed. Covers the family of \"you asked for\n * something the manifest doesn't permit\" errors.\n */\nexport class ContractViolationError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.CONTRACT_VIOLATION, message, {\n status: 403,\n context,\n requestId,\n });\n }\n}\n\n/** 422-ish — request body failed validation. Inspect `context` for field-level errors. */\nexport class ValidationError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.VALIDATION_FAILED, message, {\n status: 400,\n context,\n requestId,\n });\n }\n}\n\n// ── Cross-module dependency errors ─────────────────────────────────────\n//\n// Surfaced when calling `client.deps(slug).db<Row>(table).<op>(...)`.\n// Each subclass preserves the original backend `code` (unlike the older\n// `ContractViolationError` which collapses several codes onto a single\n// `CONTRACT_VIOLATION` string — that's a known wart we don't replicate\n// in new subclasses). Branch on `instanceof` for the common cases;\n// inspect `err.code` if you need to distinguish the two contract-error\n// codes (`DEPENDENCY_NOT_DECLARED` vs `TABLE_NOT_IN_CONTRACT`).\n\n/**\n * 404 — the caller's `module.json#dependencies` doesn't declare a\n * dependency on the requested provider module, OR the declared dep\n * exists but its `reads[]` block doesn't include the requested table.\n *\n * Either way the fix is on the **caller's** side: update the manifest\n * to declare the dep / add the table to `reads[]`, then re-publish.\n *\n * Covers both `DEPENDENCY_NOT_DECLARED` and `TABLE_NOT_IN_CONTRACT` —\n * inspect `err.code` to distinguish if you need different UX per case.\n */\nexport class DependencyContractError extends DashwiseError {\n constructor(\n code: string,\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(code, message, { status: 404, context, requestId });\n }\n}\n\n/**\n * 404 — the provider's physical table no longer exists. This means the\n * provider published a version that dropped the table while the\n * consumer's installation still pinned the old contract. Recovery is\n * on the **provider's** side (republish with the table) or the\n * workspace admin's (downgrade the provider, or upgrade the consumer\n * to a version whose `dependencies[]` no longer references it).\n */\nexport class ProviderTableMissingError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.PROVIDER_TABLE_MISSING, message, {\n status: 404,\n context,\n requestId,\n });\n }\n}\n\n/**\n * 403 — the provider's `exposes.operations.<verb>.allowed` is explicitly\n * `false` for the requested verb. Distinct from `OPERATION_NOT_ALLOWED`\n * (which is the **owner's** own-table policy) — `OPERATION_NOT_ALLOWED_BY_PROVIDER`\n * is the provider opting out of letting depending modules use a verb.\n *\n * No caller-side fix; either the provider needs to flip the gate, or\n * the consumer needs to find a different way to satisfy the use case.\n */\nexport class OperationNotAllowedByProviderError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.OPERATION_NOT_ALLOWED_BY_PROVIDER, message, {\n status: 403,\n context,\n requestId,\n });\n }\n}\n\n// ── Join errors (Phase 3) ──────────────────────────────────────────────\n//\n// Surfaced when `.join('<slug>')` / `.leftJoin('<slug>')` on the\n// generated `qb` builder can't resolve the target against the\n// manifest. Pre-declared in slice 3.1; raised by the backend\n// translator in slice 3.2. Same one-subclass-with-preserved-code\n// pattern as DependencyContractError so authors can switch on\n// `err.code` for the three sub-cases:\n//\n// LINK_FIELD_NOT_FOUND `.join('xyz')` where `xyz` isn't a field\n// on the source table. Fix: pass an\n// explicit `on` argument, or correct the slug.\n//\n// NOT_A_LINK_FIELD `.join('title')` where `title` exists on\n// the source table but its type isn't\n// `link_row`. Fix: use a real link field,\n// or pass explicit `on`.\n//\n// JOIN_NOT_LINKED `.join('sibling_table')` where the target\n// is a sibling table but no link field\n// bridges them. Fix: pass explicit `on`.\n\n/**\n * 400 — `.join()` / `.leftJoin()` referenced a target the manifest\n * can't resolve. Covers the three sub-cases above; inspect\n * `err.code` to distinguish.\n *\n * Author-side fix in every case: either correct the slug, or pass\n * an explicit `on` argument to bypass link-field inference.\n */\nexport class JoinError extends DashwiseError {\n constructor(\n code: string,\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(code, message, { status: 400, context, requestId });\n }\n}\n\n// ── Cross-module action errors (Phase 7 slice 7.3a, 2026-05-20) ──────\n//\n// Surfaced by the codegen-emitted `qb.deps.<dep>.actions.<name>(input)`\n// factory when the backend's cross-module dispatch\n// (`CrossModuleActionService.dispatch()`) rejects. Six sub-cases all\n// covered by `DepActionError`; inspect `err.code` to discriminate.\n//\n// ACTION_NOT_EXPOSED 403 — provider's manifest doesn't expose\n// the action, OR visibility='private'.\n// Recovery: provider author needs to\n// publish a version that exposes it.\n// ACTION_NOT_DECLARED 403 — consumer's manifest doesn't list\n// the action in dependencies[].actions[].\n// Recovery: add it + republish.\n// ACTION_INPUT_INVALID 400 — input fails Ajv against the\n// provider's declared input schema.\n// Recovery: fix the call site (typical\n// with stale codegen + drift).\n// ACTION_OUTPUT_INVALID 500 — provider returned a value that fails\n// the declared output schema. This is a\n// PROVIDER-author bug; consumer can\n// only file an issue.\n// ACTION_RATE_LIMITED 429 — per-actor or per-caller-installation\n// rate limit exceeded. Retry with\n// backoff after Retry-After (slice 7.4).\n// ACTION_EXECUTION_FAILED 200-envelope — action body threw, timed out,\n// or OOM-ed. Provider-author bug;\n// trace surfaced in err.context.trace.\n\n/**\n * Single subclass for every cross-module action failure. The code\n * (and HTTP status) varies per sub-case; the wrapping class lets\n * consumers `catch (e) { if (e instanceof DepActionError) {...} }`\n * regardless of which sub-case fired.\n */\nexport class DepActionError extends DashwiseError {\n constructor(\n code: string,\n message: string,\n status: number,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(code, message, {\n status,\n retriable: code === DashwiseErrorCode.ACTION_RATE_LIMITED,\n context,\n requestId,\n });\n }\n}\n\n// ── Factory: backend envelope → typed error ────────────────────────────\n\ninterface BackendErrorEnvelope {\n code?: string;\n message?: string;\n retriable?: boolean;\n context?: Record<string, unknown>;\n}\n\n/**\n * Build a typed `DashwiseError` from the backend's response envelope.\n *\n * The mapping picks the most specific subclass for codes we have a class\n * for; everything else becomes a plain `DashwiseError` carrying the raw\n * code. This keeps the type set small while still letting consumers\n * branch on string codes when they want to.\n *\n * Phase 6 slice 6.2 (2026-05-19): accepts an optional `requestId` —\n * the backend's `x-request-id` response header — and threads it onto\n * every produced error so consumers can use it as a debugging trace\n * anchor. Default `null` for backward compat; the transport always\n * passes it through.\n */\nexport function fromBackendEnvelope(\n status: number,\n envelope: BackendErrorEnvelope | null,\n requestId: string | null = null,\n): DashwiseError {\n const code = envelope?.code ?? codeFromStatus(status);\n const message = envelope?.message ?? `HTTP ${status}`;\n const context = envelope?.context;\n\n switch (code) {\n case DashwiseErrorCode.UNAUTHENTICATED:\n case DashwiseErrorCode.TOKEN_EXPIRED:\n return new UnauthenticatedError(message, requestId);\n case DashwiseErrorCode.FORBIDDEN:\n return new ForbiddenError(message, context, requestId);\n case DashwiseErrorCode.ROW_NOT_FOUND:\n return new RowNotFoundError(message, context, requestId);\n case DashwiseErrorCode.CONTRACT_VIOLATION:\n case DashwiseErrorCode.FIELD_NOT_READABLE:\n case DashwiseErrorCode.FILTER_OP_UNKNOWN:\n case DashwiseErrorCode.OPERATION_NOT_ALLOWED:\n return new ContractViolationError(message, context, requestId);\n case DashwiseErrorCode.VALIDATION_FAILED:\n return new ValidationError(message, context, requestId);\n case DashwiseErrorCode.DEPENDENCY_NOT_DECLARED:\n case DashwiseErrorCode.TABLE_NOT_IN_CONTRACT:\n return new DependencyContractError(code, message, context, requestId);\n case DashwiseErrorCode.PROVIDER_TABLE_MISSING:\n return new ProviderTableMissingError(message, context, requestId);\n case DashwiseErrorCode.OPERATION_NOT_ALLOWED_BY_PROVIDER:\n return new OperationNotAllowedByProviderError(message, context, requestId);\n case DashwiseErrorCode.LINK_FIELD_NOT_FOUND:\n case DashwiseErrorCode.NOT_A_LINK_FIELD:\n case DashwiseErrorCode.JOIN_NOT_LINKED:\n return new JoinError(code, message, context, requestId);\n case DashwiseErrorCode.ACTION_NOT_EXPOSED:\n case DashwiseErrorCode.ACTION_NOT_DECLARED:\n return new DepActionError(code, message, 403, context, requestId);\n case DashwiseErrorCode.ACTION_INPUT_INVALID:\n return new DepActionError(code, message, 400, context, requestId);\n case DashwiseErrorCode.ACTION_OUTPUT_INVALID:\n return new DepActionError(code, message, 500, context, requestId);\n case DashwiseErrorCode.ACTION_RATE_LIMITED:\n return new DepActionError(code, message, 429, context, requestId);\n case DashwiseErrorCode.ACTION_EXECUTION_FAILED:\n // ACTION_EXECUTION_FAILED arrives as a 200 envelope from the\n // backend (the host succeeded; the action body crashed). The\n // _callAction helper unwraps the envelope and synthesizes this\n // via the factory call — status 200 reflects \"the transport was\n // fine; the failure is in the application layer\".\n return new DepActionError(code, message, 200, context, requestId);\n default:\n return new DashwiseError(code, message, {\n status,\n retriable: envelope?.retriable ?? false,\n context,\n requestId,\n });\n }\n}\n\nfunction codeFromStatus(status: number): string {\n if (status === 401) return DashwiseErrorCode.UNAUTHENTICATED;\n if (status === 403) return DashwiseErrorCode.FORBIDDEN;\n if (status === 404) return DashwiseErrorCode.ROW_NOT_FOUND;\n if (status >= 500) return DashwiseErrorCode.INTERNAL_ERROR;\n return 'HTTP_ERROR';\n}\n","/**\n * Wire-format Query AST — the JSON shape the SDK serializes and the\n * backend's query plane consumes.\n *\n * The AST is a **public, versioned** contract. Bumping its shape in a\n * way that can't be transparently re-mapped on the backend is a\n * SemVer-significant change for `@dashai/sdk`. The `v` field at the\n * root is the single discriminator the backend uses to decide whether\n * it can handle a given query.\n *\n * Coverage by phase (per `dashwise-devops-docs/plans/modules-sdk-query-v1.md`):\n *\n * - **Phase 2** (this file, today): `v: 1`, `from: { kind: 'owned' }`.\n * Bare table reads. Subsequent slices add `where`, `select`,\n * `orderBy`, `limit`, `offset`, `count`, then aggregations.\n * - **Phase 3**: `joins[]`, `from: { kind: 'view' }`.\n * - **Phase 4**: `from: { kind: 'dep' }` for cross-module reads,\n * `joins[].to` for cross-module joins via the planner.\n *\n * The shape below carries all the optional fields the v1 plan promises\n * (commented per-field with the phase they activate in). Today's\n * translator only honors `v` + `from`; everything else is parsed +\n * rejected with `QUERY_TOO_COMPLEX` until the corresponding phase\n * lands. Pre-declaring the shape lets the SDK builder stub out the\n * fluent API today without forcing an AST version bump every time we\n * activate a field.\n */\n\n// ── Top-level AST ────────────────────────────────────────────────────\n\n/**\n * Schema version. Bumped only on breaking changes. The backend\n * supports `v` and `v+1` during deprecation windows so consumers can\n * upgrade independently of the server. Add new optional fields under\n * the current `v`; that's additive and non-breaking.\n */\nexport type QueryAstVersion = 1;\n\n/**\n * The root AST node. Carries the version discriminator + the table\n * reference + every optional clause.\n *\n * Designed so the backend can switch on `v` first, then validate the\n * rest. Unknown root keys are NOT silently dropped — the validator\n * rejects with `VALIDATION_FAILED` so typos surface loudly.\n */\nexport interface QueryAst {\n /** Schema discriminator. See {@link QueryAstVersion}. */\n v: QueryAstVersion;\n\n /** The primary table for this query. */\n from: TableRef;\n\n /**\n * Optional joins (Phase 3+). When present + the planner can't handle\n * the join (e.g. multi-module before Phase 4), the backend rejects\n * with `QUERY_TOO_COMPLEX`.\n */\n joins?: JoinNode[];\n\n /**\n * Where clause (Phase 2 slice 2). Mirrors the existing `Where<Row>`\n * shape from `@dashai/sdk` (`./data/types.ts`) — recursive AND/OR\n * groups + per-field operator objects. Field references inside `where`\n * can be either bare (`'done'`, resolved against `from`) or qualified\n * (`'tasks.done'`) when joins are present.\n */\n where?: WhereClause;\n\n /**\n * Group-by fields for aggregation queries (Phase 3). Field refs follow\n * the same bare-or-qualified rule as `where`.\n */\n groupBy?: FieldRef[];\n\n /**\n * Aggregations keyed by output column name. Phase 3.\n *\n * { totalDone: { count: '*' }, oldest: { min: 'created_at' } }\n */\n agg?: Record<string, AggExpr>;\n\n /**\n * Columns to select. Default = all fields of `from`. Supports `'*'`\n * shorthand + `'as'` aliases (`'users.email as assignee_email'`).\n * Phase 2 slice 2.\n */\n select?: SelectExpr[];\n\n /** Order-by clauses (Phase 2 slice 2). */\n orderBy?: OrderClause[];\n\n /** Max rows to return. Default 50 (matches owned-table CRUD). */\n limit?: number;\n\n /** Row offset for pagination. Default 0. */\n offset?: number;\n\n /**\n * Phase 3 slice 3.5 — when joins are present, return rows in\n * SQL-style flat shape (`{ 'users.email': value }`) instead of\n * the nested default (`row.users.email`). Ignored without joins.\n *\n * SDK ships the flag in slice 3.3 so authors can write\n * `.flatten()` today; backend wiring lands in slice 3.5. Until\n * then the backend silently ignores it (defaults to nested).\n */\n flatten?: boolean;\n}\n\n// ── Table references ─────────────────────────────────────────────────\n\n/**\n * Discriminated union of every table source. Adding a new kind is\n * additive (consumers using `switch (ref.kind)` must `default` to\n * reject — covered by lint).\n *\n * - `owned`: a table from this module's manifest `owns.tables[]`.\n * - `dep`: a table from another module borrowed via `dependencies[]`\n * (Phase 4 — not yet routable, validator rejects).\n * - `view`: a manifest-declared view (Phase 5 — not yet routable).\n */\nexport type TableRef =\n | { kind: 'owned'; slug: string }\n | { kind: 'dep'; module: string; slug: string }\n | { kind: 'view'; slug: string };\n\n// ── Join nodes ───────────────────────────────────────────────────────\n\n/**\n * Join node — Phase 3+. Includes `on` as an explicit field-pair map\n * because link-field-derived defaults can't be inferred from the AST\n * alone (they live on the manifest). The SDK builder may compute `on`\n * from a link field at compile time and inline it here.\n */\nexport interface JoinNode {\n kind: 'inner' | 'left';\n to: TableRef;\n /**\n * Map of left-side qualified field ref → right-side qualified field\n * ref. E.g. `{ 'tasks.assignee_id': 'users.id' }`.\n */\n on: Record<string, string>;\n /**\n * Alias on result rows. Defaults to `to.slug` (or the link-field\n * slug when the SDK's `.join('<link-slug>')` resolves through a\n * link field). Reserved for multi-join-to-same-target (out of\n * scope for slice 3.2; field is parsed but unused today).\n */\n as?: string;\n}\n\n// ── Where clause ─────────────────────────────────────────────────────\n\n/**\n * Where clause — same shape as the existing `Where<Row>` type but\n * untyped here (the AST is the wire format; the typed builder layer\n * provides per-field narrowing). Top-level keys AND together; `AND`\n * and `OR` arrays group explicitly.\n *\n * Examples:\n * { done: false } // shorthand for { equal: false }\n * { title: { contains: 'urgent' } } // operator object\n * { AND: [{ done: false }, { ... }] } // explicit AND group\n * { OR: [{ priority: 'high' }, ...] } // OR group\n * { 'tasks.done': false } // qualified field ref (after joins)\n */\nexport interface WhereClause {\n AND?: WhereClause[];\n OR?: WhereClause[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [field: string]: any;\n}\n\n// ── Field references ─────────────────────────────────────────────────\n\n/**\n * Field reference — either bare (`'done'`) or qualified\n * (`'tasks.done'`). Qualified refs are only valid when the AST has\n * joins; the validator enforces this.\n */\nexport type FieldRef = string;\n\n// ── Select expressions ───────────────────────────────────────────────\n\n/**\n * Select expression — a field ref, the `'*'` shorthand, or a string\n * with an `'as'` alias.\n *\n * 'id'\n * '*'\n * 'tasks.title'\n * 'users.email as assignee_email'\n *\n * Strict mode (Phase 2 OD2 from the plan): `'*'` selects all fields\n * from `from`, NOT all fields from every joined table. Authors must\n * explicitly opt into joined fields with `'<table>.<field>'`.\n */\nexport type SelectExpr = string;\n\n// ── Order-by ─────────────────────────────────────────────────────────\n\nexport interface OrderClause {\n field: FieldRef;\n dir: 'asc' | 'desc';\n}\n\n// ── Aggregations ─────────────────────────────────────────────────────\n\n/**\n * Aggregation expression. Each function takes either `'*'` (count\n * only) or a specific field ref.\n *\n * { count: '*' }\n * { sum: 'amount' }\n * { avg: 'amount' }\n * { min: 'created_at' }\n * { max: 'updated_at' }\n */\nexport type AggExpr =\n | { count: '*' | FieldRef }\n | { sum: FieldRef }\n | { avg: FieldRef }\n | { min: FieldRef }\n | { max: FieldRef };\n\n// ── Type-narrowing guards ────────────────────────────────────────────\n\n/**\n * Runtime check that an unknown value matches the AST shape at the\n * structural level (not field-level — that's the backend validator's\n * job). Useful for SDK-side fail-fast checks before serializing.\n *\n * Intentionally permissive about optional fields: the AST grew over\n * phases, and older consumers might lack `joins` / `agg` / etc.\n * Strict checks happen at the backend.\n */\nexport function isQueryAst(v: unknown): v is QueryAst {\n if (!v || typeof v !== 'object') return false;\n const a = v as Record<string, unknown>;\n if (a.v !== 1) return false;\n if (!a.from || typeof a.from !== 'object') return false;\n const f = a.from as Record<string, unknown>;\n if (typeof f.kind !== 'string') return false;\n if (f.kind === 'owned' || f.kind === 'view') {\n return typeof f.slug === 'string';\n }\n if (f.kind === 'dep') {\n return typeof (f as { module?: unknown }).module === 'string' && typeof f.slug === 'string';\n }\n return false;\n}\n\n/**\n * Convenience: the current schema version as a literal. Useful when\n * constructing ASTs programmatically — keeps the magic number out of\n * the call site.\n */\nexport const CURRENT_QUERY_AST_VERSION: QueryAstVersion = 1;\n","/**\n * Typed query builder — fluent API that compiles to a {@link QueryAst}\n * and posts it to `POST /api/modules/<installation>/query`.\n *\n * Phase 2 slice 1 (this file): the minimum E2E proof — `qb.from(slug).execute()`\n * round-trips through the backend. Future slices extend with\n * `.where`, `.select`, `.orderBy`, `.limit`, `.offset`, `.count`, then\n * aggregations + joins. The builder's shape is designed to fit those\n * additions without renaming or breaking anything we ship today.\n *\n * Composability: every chainable method returns a new immutable\n * `Query<Row>` — never mutates `this`. That lets authors derive\n * sub-queries from a base:\n *\n * ```ts\n * const base = qb.from('tasks');\n * const all = await base.execute();\n * // (future: const overdue = await base.where({ done: false }).execute())\n * ```\n *\n * The factory accepts a {@link Client} (not the raw transport) so the\n * public surface stays small + the same builder works whether the\n * client was constructed with `createClient` or a test stub that\n * implements the {@link Client} interface.\n *\n * Spec: `dashwise-devops-docs/plans/modules-sdk-query-v1.md` §6.3.\n */\n\nimport { fromBackendEnvelope } from '../data/errors.js';\nimport type { BaseRow, Client, Page, Where } from '../data/types.js';\nimport {\n CURRENT_QUERY_AST_VERSION,\n type AggExpr,\n type FieldRef,\n type OrderClause,\n type QueryAst,\n type TableRef,\n type WhereClause,\n} from './ast.js';\n\n// ── Join options + result envelope (Phase 3) ──────────────────────────\n\n/**\n * Options passed to {@link Query.join} / {@link Query.leftJoin}.\n *\n * `on` is REQUIRED in slice 3.3 — link-field-derived inference lands\n * in a follow-up. Both sides of each pair must be qualified\n * (`<table>.<field>`) so the SDK + backend can disambiguate after\n * joins.\n *\n * `as` is reserved for explicit aliasing when joining the same\n * target multiple times (rare; not in v1).\n */\nexport interface JoinOpts<K extends 'inner' | 'left'> {\n on: Record<string, string>;\n /** Default 'inner' for `.join()`; `.leftJoin()` hardcodes 'left'. */\n kind?: K;\n as?: string;\n}\n\n// ── Aggregation result envelope ──────────────────────────────────────\n\n/**\n * Result envelope for aggregation queries — distinct from\n * {@link Page} because aggregation rows have a different shape (one\n * row per group, with output keys matching the `agg` map keys + the\n * `groupBy` field names) and don't carry pagination metadata in the\n * same way.\n *\n * `rows` shape: `Record<string, unknown>` because the keys are\n * dynamic (mix of group-by field names + aggregation output keys);\n * codegen will later emit a per-query typed shape, but the runtime\n * client stays untyped at the value level.\n *\n * `total` = number of result rows (one per group).\n */\nexport interface QueryResult {\n rows: Array<Record<string, unknown>>;\n total: number;\n}\n\n/**\n * Result envelope for `.explain()` (Phase 5, 2026-05-19). Returned by\n * `POST /api/modules/:installationId/query/explain`. The backend runs\n * Postgres `EXPLAIN (FORMAT JSON)` against the SQL it would emit —\n * WITHOUT executing it (no `ANALYZE`, no rows materialized).\n *\n * Fields:\n * - `plan` — the full Postgres plan JSON tree (opaque object;\n * `plan.Plan` is the root node, `plan.Plan.Plans[]` its children).\n * Useful for visualization tools that want the original tree\n * structure preserved.\n * - `nodes` — depth-first flattened array of plan nodes. Each entry\n * carries `Node Type`, `Total Cost`, `Plan Rows`, and a `depth`\n * annotation (root is depth 0; children depth 1; etc.). Easier to\n * render in a flat table than the recursive tree.\n * - `estimatedCost` — `Total Cost` of the root plan node. Postgres'\n * planner-side cost estimate (not actual runtime). Sum of startup +\n * execution cost in arbitrary cost units.\n * - `estimatedRows` — `Plan Rows` of the root plan node. Planner's\n * estimate of rows the query would produce. May differ from actual\n * once table stats are stale (run `ANALYZE` on the underlying\n * tables to refresh).\n * - `sql` — the generated SQL string the query plane would execute.\n * Surfaced for debugging — useful when the plan doesn't match what\n * you expected and you want to see what was actually compiled.\n * - `paramsCount` — number of bound parameters in the SQL. Cross-check\n * against the WHERE / ON clauses in your AST.\n *\n * MVP support (backend slice 5 `.explain()` MVP):\n * - ✅ Joined queries (any `.join()` / `.leftJoin()` call)\n * - ✅ Joined aggregations (joins + `.agg()` / `.groupBy()`)\n * - ⏳ Bare own-table / bare cross-module dep / views / own-table agg\n * — reject with `EXPLAIN_NOT_SUPPORTED_YET` (typed code; the\n * thrown error carries `context: { fromKind, hasJoins, hasAgg }`\n * so callers can detect + fall back to `.execute()` for the\n * unsupported shapes).\n */\nexport interface ExplainResult {\n plan: unknown;\n nodes: Array<{\n ['Node Type']: string;\n ['Total Cost']?: number;\n ['Plan Rows']?: number;\n depth: number;\n [key: string]: unknown;\n }>;\n estimatedCost: number;\n estimatedRows: number;\n sql: string;\n paramsCount: number;\n}\n\n// ── Slug validation (mirrored from data/client.ts) ───────────────────\n\n/**\n * Owned-table slug pattern. Mirrors the backend manifest validator's\n * `SLUG_REGEX` exactly (snake_case, 2-63 chars). Mirrored here, not\n * imported from the backend, because the SDK is published independently.\n */\nconst TABLE_SLUG_REGEX = /^[a-z][a-z0-9_]*[a-z0-9]$/;\n\n/**\n * Module-slug character set — kebab-case, 2-63 chars, starts with a\n * letter, ends with letter/digit. Mirror of `MODULE_SLUG_REGEX` in\n * `@dashai/sdk/deps` and the backend's manifest validator. Same\n * mirror-vs-import tradeoff: the SDK is published independently.\n */\nconst MODULE_SLUG_REGEX = /^[a-z][a-z0-9-]*[a-z0-9]$/;\n\nfunction assertValidTableSlug(slug: string): void {\n if (typeof slug !== 'string' || !TABLE_SLUG_REGEX.test(slug)) {\n throw new Error(\n `@dashai/sdk/query: invalid table slug \"${slug}\". Slugs must match /^[a-z][a-z0-9_]*[a-z0-9]$/ (snake_case).`,\n );\n }\n}\n\nfunction assertValidModuleSlug(slug: string): void {\n if (typeof slug !== 'string' || !MODULE_SLUG_REGEX.test(slug)) {\n throw new Error(\n `@dashai/sdk/query: invalid module slug \"${slug}\". Module slugs must match /^[a-z][a-z0-9-]*[a-z0-9]$/ (kebab-case).`,\n );\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────\n\n/**\n * Reference to a dependency module's table. Lands with Phase 4 slice\n * 4.1b (the SDK side of the cross-module query plane). The backend\n * routes these through {@link DepsRuntimeService.listRowsForSdk} which\n * enforces the consumer's declared-dep contract + field projection\n * server-side.\n *\n * Authors typically reach this through the codegen-emitted\n * `qb.from(deps.crm.contacts)` shape, but the literal form\n * `qb.from({ kind: 'dep', module: 'crm', slug: 'contacts' })` is also\n * supported for tests + AI-builder authoring.\n */\nexport interface QbDepRef {\n kind: 'dep';\n module: string;\n slug: string;\n}\n\n/**\n * Reference to a view declared in the module's `manifest.owns.views[]`.\n * Lands with Phase 5 slice 5.4b (the SDK side of view querying). The\n * backend's `executeView` resolves the view at runtime — AND-merges\n * the view's static WHERE with the caller's, applies the view's\n * default orderBy when caller omits one, and recurses through\n * `execute()` with a rewritten own-table AST.\n *\n * Authors typically reach this through the codegen-emitted\n * `qb.from(qbViews.active_tasks.ref)` shape (or the more ergonomic\n * `qbViews.active_tasks.where(...).execute()` directly), but the\n * literal form `qb.from({ kind: 'view', slug: 'active_tasks' })`\n * is also supported for tests + AI-builder authoring.\n */\nexport interface QbViewRef {\n kind: 'view';\n slug: string;\n}\n\n/**\n * The factory's return value. Wraps a {@link Client} + exposes a\n * `.from()` method that binds a table source.\n *\n * `from()` accepts three shapes:\n * - a string — own-table slug (default, since Phase 2 slice 1)\n * - a {@link QbDepRef} — cross-module dep ref (Phase 4 slice 4.1b)\n * - a {@link QbViewRef} — view ref (Phase 5 slice 5.4b)\n *\n * A `.raw()` escape hatch is intentionally NOT in v1 per the plan.\n */\nexport interface Qb {\n /** Bind an owned-table source. Returns a chainable {@link Query}. */\n from<Row extends BaseRow = BaseRow>(tableSlug: string): Query<Row>;\n /** Bind a cross-module dep table source. Returns a chainable {@link Query}. */\n from<Row extends BaseRow = BaseRow>(ref: QbDepRef): Query<Row>;\n /** Bind a view source (declared in `manifest.owns.views[]`). */\n from<Row extends BaseRow = BaseRow>(ref: QbViewRef): Query<Row>;\n}\n\n/**\n * Chainable per-query builder. Each instance is **immutable** — every\n * modifier returns a fresh `Query<Row>` with the updated AST.\n *\n * Today's slice exposes `.toAst()` (introspection), `.execute()`\n * (run + return rows), plus four chainable modifiers (`.where`,\n * `.orderBy`, `.limit`, `.offset`). Subsequent slices add `.select`,\n * `.count`, `.groupBy()`/`.agg()`, then `.join()` / `.leftJoin()`.\n *\n * Each chainable method returns a new `Query<Row>` rather than `this`\n * so authors can derive sub-queries from a common base:\n *\n * ```ts\n * const base = qb.from<TaskRow>('tasks');\n * const open = await base.where({ done: false }).execute();\n * const overdue = await base\n * .where({ done: false, due_date: { date_before: today } })\n * .execute();\n * ```\n *\n * The first `base.execute()` returns ALL tasks (no filter); the\n * `where()` calls produce fresh builders that don't mutate `base`.\n */\nexport interface Query<Row extends BaseRow = BaseRow> {\n /**\n * Return the underlying AST. Useful for tests, debugging, future\n * `.explain()` integration. The returned object is a fresh copy —\n * mutating it does NOT affect the builder.\n */\n toAst(): QueryAst;\n\n /**\n * Execute the query. Posts the AST to the backend's\n * `POST /api/modules/<installation>/query` endpoint and resolves\n * with the standard {@link Page} envelope. Rejects with a\n * {@link DashwiseError} on backend errors (`QUERY_AST_VERSION`,\n * `QUERY_TOO_COMPLEX`, etc.).\n */\n execute(): Promise<Page<Row>>;\n\n /**\n * Phase 6 streaming (2026-05-19): stream query results row-by-row\n * via Server-Sent Events. Returns an `AsyncIterable<Row>` — use\n * `for await (const row of q.stream())` to consume.\n *\n * Backend endpoint: `POST /api/modules/<installation>/query/stream`.\n * Each row arrives as one SSE `data:` event; a terminal `complete`\n * event closes the iterator cleanly. Execution-phase backend\n * errors (RBAC, RESULT_TOO_LARGE, etc.) arrive as in-band `error`\n * events and are rethrown as `DashwiseError` from the iterator.\n * Validation-phase errors (malformed AST) come back as standard\n * HTTP 400 BEFORE the stream opens, so they throw immediately\n * when you start iterating (before the first row).\n *\n * **AbortSignal**: pass `opts.signal` to cancel the stream. When\n * the signal fires:\n * - The underlying `fetch()` is cancelled (network read aborts)\n * - The iterator throws an `AbortError` on the next `next()` call\n * - The backend's SSE writer eventually notices the broken\n * pipe and stops emitting events (no leak)\n *\n * **Backpressure**: the iterator is pull-based. Rows are buffered\n * in the SSE chunk decoder until the consumer requests them via\n * the for-await loop. Slow consumers naturally backpressure the\n * network read.\n *\n * **MVP limitation**: in v1 the BACKEND buffers the full result\n * before streaming (true row-by-row Postgres cursoring is a future\n * perf slice). The SDK consumer still gets the streaming UX — rows\n * arrive incrementally on the wire. Once the backend perf slice\n * lands, both ends become streaming end-to-end with no SDK change.\n *\n * @example Basic iteration\n * for await (const task of qb.from<TaskRow>('tasks').stream()) {\n * process(task);\n * }\n *\n * @example Abort midway via AbortController\n * const controller = new AbortController();\n * setTimeout(() => controller.abort(), 5000);\n * try {\n * for await (const row of qb.from('tasks').stream({ signal: controller.signal })) {\n * // ... rows arrive until 5s timeout\n * }\n * } catch (e) {\n * if (e.name === 'AbortError') console.log('Cancelled');\n * }\n */\n stream(opts?: { signal?: AbortSignal }): AsyncIterable<Row>;\n\n /**\n * Return the Postgres execution plan for this query — WITHOUT\n * executing it (Phase 5, 2026-05-19). Posts the AST to\n * `POST /api/modules/<installation>/query/explain`; backend wraps the\n * SQL in `EXPLAIN (FORMAT JSON)` and returns the parsed plan tree.\n *\n * **MVP support matrix** (backend slice 5):\n * - ✅ Joined queries — works (`.join()` / `.leftJoin()`)\n * - ✅ Joined aggregations — works (`.join()...agg()`)\n * - ⏳ Bare own-table / bare dep / view / bare-agg → rejects with\n * `EXPLAIN_NOT_SUPPORTED_YET` (typed code; the error's `context`\n * payload lets you fall back to `.execute()` for unsupported\n * shapes)\n *\n * @example Joined query EXPLAIN (works today)\n * const plan = await qb.from<TaskRow>('tasks')\n * .leftJoin<UserRow>('users', { on: { 'tasks.assignee_id': 'users.id' } })\n * .where({ done: false })\n * .explain();\n * console.log('Cost:', plan.estimatedCost);\n * for (const node of plan.nodes) {\n * console.log(' '.repeat(node.depth) + node['Node Type']);\n * }\n *\n * @example Bare query EXPLAIN (currently throws)\n * try {\n * await qb.from<TaskRow>('tasks').explain();\n * } catch (e) {\n * // e.code === 'EXPLAIN_NOT_SUPPORTED_YET'\n * // Fall back to .execute() — bare-path support lands in a follow-up\n * }\n */\n explain(): Promise<ExplainResult>;\n\n /**\n * Add or replace the where clause. Same recursive `Where<Row>`\n * shape as `client.db().list({ filter })` — top-level keys AND\n * together; explicit `AND` / `OR` arrays group:\n *\n * ```ts\n * qb.from<TaskRow>('tasks').where({ done: false })\n * qb.from<TaskRow>('tasks').where({ title: { contains: 'urgent' } })\n * qb.from<TaskRow>('tasks').where({\n * OR: [{ priority: 'high' }, { due_date: { date_before: today } }]\n * })\n * ```\n *\n * Calling `.where()` twice REPLACES the prior clause (not merge) —\n * authors who want incremental composition should build their\n * where-clause object explicitly. Replace-vs-merge is the same\n * semantic as `client.db().list({ filter: ... })`.\n */\n where(clause: Where<Row>): Query<Row>;\n\n /**\n * Add or replace the order-by clauses. Array of `{ field, dir }`\n * entries; sorts apply in array order (primary first).\n *\n * ```ts\n * qb.from<TaskRow>('tasks').orderBy([\n * { field: 'priority', dir: 'desc' },\n * { field: 'due_date', dir: 'asc' },\n * ])\n * ```\n */\n orderBy(clauses: ReadonlyArray<OrderClause>): Query<Row>;\n\n /**\n * Set the maximum number of rows to return. Server defaults to 50\n * when omitted; max 1000 (`RESULT_TOO_LARGE` for higher values).\n */\n limit(n: number): Query<Row>;\n\n /**\n * Set the row offset for pagination. Defaults to 0.\n */\n offset(n: number): Query<Row>;\n\n /**\n * Convenience: execute the query and return only the total row\n * count (after `where`, before `limit`/`offset`).\n *\n * Equivalent to `(await this.limit(1).execute()).total` but with\n * a clearer call site. The backend's existing list endpoint\n * already computes `total` regardless of `limit`, so this hits\n * the same path as `.execute()` and just extracts the number —\n * no separate `COUNT(*)` round-trip.\n *\n * @example\n * const open = await qb.from<TaskRow>('tasks')\n * .where({ done: false })\n * .count();\n */\n count(): Promise<number>;\n\n /**\n * Add a same-module join (Phase 3 slice 3.3, 2026-05-19). Either\n * INNER (when `opts.kind` is omitted or 'inner') or LEFT (use\n * `.leftJoin()` for the read-friendly alias).\n *\n * `opts.on` is REQUIRED in this slice — link-field-derived `on`\n * inference lands in a follow-up. Pass qualified refs on both\n * sides: `{ 'tasks.assignee_id': 'users.id' }`.\n *\n * Returns a {@link JoinedQuery} — the surface omits write methods\n * (`.create / .update / .delete`) + `.count` / `.agg` / `.groupBy`\n * since the query plane's join path is read-only and not yet\n * aggregation-aware (slice 3.2b-1 limits).\n *\n * Today the validator gates joins+filters+ordering as `QUERY_TOO_COMPLEX`\n * pending slices 3.2b-2 / b-3. The SDK lets you write the call\n * anyway so author muscle memory transfers; the backend will\n * surface a clear \"lands in slice X\" error if combined too early.\n *\n * @example Own-table join\n * await qb.tasks\n * .join<UsersRow>('users', { on: { 'tasks.assignee_id': 'users.id' } })\n * .execute();\n *\n * @example Cross-module join (Phase 4 slice 4.2)\n * await qb.tasks\n * .leftJoin<ContactRow>(\n * { kind: 'dep', module: 'crm', slug: 'contacts' },\n * { on: { 'tasks.contact_id': 'contacts.id' } },\n * )\n * .execute();\n */\n join<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n ): JoinedQuery<Row & Record<string, T>>;\n\n /**\n * Add a LEFT JOIN. Convenience for `.join(target, { kind: 'left', on })`.\n * The joined object on result rows is `T | null` (matches LEFT JOIN's\n * unmatched-row semantics).\n *\n * Phase 4 slice 4.2: accepts {@link QbDepRef} as the target for\n * cross-module joins. The provider's `exposes[].operations.list`\n * must be enabled and the consumer must declare the dep table in\n * `dependencies[].reads[]` — same contract enforcement as the bare\n * dep list path.\n *\n * @example\n * await qb.tasks\n * .leftJoin<UsersRow>('users', { on: { 'tasks.assignee_id': 'users.id' } })\n * .execute();\n */\n leftJoin<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'left'>,\n ): JoinedQuery<Row & Record<string, T | null>>;\n\n /**\n * Narrow the response to only the listed field slugs. The manifest's\n * policy projection is the security floor — `.select()` can narrow\n * further but never widens. Slugs not in the projection (because the\n * manifest hides them, or the slug is a typo) reject server-side with\n * `FIELD_NOT_READABLE`.\n *\n * Pass `['*']` (or omit `.select()` entirely) for the full projection.\n *\n * **Type narrowing not yet implemented:** the returned Query is still\n * typed as `Query<Row>` (full row type). Authors who want narrowed\n * types cast at the call site:\n * ```ts\n * const result = await qb.tasks\n * .select(['id', 'title'])\n * .execute() as unknown as Page<Pick<TasksRow, 'id' | 'title'>>;\n * ```\n * A future TS exercise (Pick-based variadic narrowing) lights up\n * inferred types without the cast.\n *\n * @example\n * await qb.tasks.where({ done: false }).select(['id', 'title']).execute();\n */\n select(fields: ReadonlyArray<string>): Query<Row>;\n\n /**\n * Group rows by one or more fields. Required precursor to `.agg()`\n * when aggregating per-group; without `.groupBy()` the aggregation\n * runs against the whole filtered set and returns one row.\n *\n * Transitions the builder to {@link AggQuery} so subsequent calls\n * have access to `.agg()` + the aggregation-shaped `.execute()`.\n *\n * @example\n * qb.from<TaskRow>('tasks')\n * .where({ done: false })\n * .groupBy(['priority', 'assignee_id'])\n * .agg({ total: { count: '*' } })\n * .execute()\n */\n groupBy(fields: ReadonlyArray<FieldRef>): AggQuery;\n\n /**\n * Set the aggregation expressions. Keys are output column names in\n * the result rows; values are aggregation function specs\n * (`{ count: '*' }`, `{ sum: 'amount' }`, etc.). At least one\n * expression is required; calling `.agg({})` rejects.\n *\n * Without a preceding `.groupBy()`, returns a single row with the\n * aggregations applied to the entire filtered set. With `.groupBy()`,\n * returns one row per group.\n *\n * Transitions the builder to {@link AggQuery} — `.execute()` on\n * the returned builder yields {@link QueryResult} (not `Page<Row>`).\n *\n * @example\n * const stats = await qb.from<TaskRow>('tasks')\n * .where({ done: false })\n * .agg({ total: { count: '*' }, oldest: { min: 'created_at' } })\n * .execute();\n * // stats.rows[0] = { total: 7, oldest: '2026-01-15' }\n */\n agg(expressions: Record<string, AggExpr>): AggQuery;\n}\n\n// ── JoinedQuery (join-mode builder, Phase 3) ──────────────────────────\n\n/**\n * Chainable per-query builder after `.join()` / `.leftJoin()` has\n * been called. Same kind of immutable shape as {@link Query} —\n * every modifier returns a fresh instance.\n *\n * Surface intentionally NARROWER than Query<Row>:\n * - Omits `.create / .update / .delete` — joined queries are\n * read-only (writes always go through `client.db()`).\n * - Omits `.count / .agg / .groupBy` — slice 3.2b-1 doesn't yet\n * handle aggregation over joined tables (Phase 5+ work).\n *\n * Today's available surface:\n * - `.where` — bare-ref + qualified-ref WHERE both work as of\n * Phase 3 slice 3.2b-2-full (SQL-side alias-aware WHERE)\n * - `.orderBy` — bare + qualified-ref sort (slice 3.2b-3)\n * - `.limit`, `.offset` — in-memory pagination over post-WHERE\n * rows, capped at 10k (slice 3.2b-3)\n * - `.join`, `.leftJoin` — stack additional joins up to 2 total\n * in v1 (Phase 4 slice 4.3; validator rejects 3+ with a clear\n * QUERY_TOO_COMPLEX). Cross-module dep refs accepted as targets\n * since slice 4.2.\n * - `.flatten()` — switches to SQL-style flat result rows (slice\n * 3.5; backend wired since slice 3.5)\n * - `.toAst()`, `.execute()` — same as Query<Row>.\n */\nexport interface JoinedQuery<Row extends BaseRow = BaseRow> {\n toAst(): QueryAst;\n /** Resolves with `Page<Row>` shape — rows carry the nested joined data. */\n execute(): Promise<Page<Row>>;\n /**\n * Phase 6 streaming (2026-05-19): stream joined-row results\n * row-by-row via SSE. Same contract as {@link Query.stream} — see\n * its JSDoc for the full doc. Joined queries stream their nested-\n * row shape (each yielded row has the joined object under the\n * alias key, same as `.execute()` returns).\n */\n stream(opts?: { signal?: AbortSignal }): AsyncIterable<Row>;\n /**\n * Return Postgres EXPLAIN (FORMAT JSON) for this joined query —\n * WITHOUT executing it. The joined path is the canonical supported\n * shape for `.explain()` in MVP (Phase 5, 2026-05-19) because the\n * join translator emits clean parameterized SQL the backend can wrap\n * with EXPLAIN directly.\n *\n * Returns the same {@link ExplainResult} shape as `Query.explain`.\n * See `Query.explain` JSDoc for the full call shape + examples.\n */\n explain(): Promise<ExplainResult>;\n where(clause: Where<Row>): JoinedQuery<Row>;\n orderBy(clauses: ReadonlyArray<OrderClause>): JoinedQuery<Row>;\n limit(n: number): JoinedQuery<Row>;\n offset(n: number): JoinedQuery<Row>;\n /**\n * Add another join. Capped at 2 total in v1 (validator).\n * Phase 4 slice 4.2: accepts {@link QbDepRef} as the target for\n * cross-module joins (same overload semantics as the entry join).\n */\n join<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n ): JoinedQuery<Row & Record<string, T>>;\n leftJoin<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'left'>,\n ): JoinedQuery<Row & Record<string, T | null>>;\n /**\n * Switch to flat-row result shape (`{ 'users.email': ... }`)\n * instead of the nested default. Slice 3.5 wires the backend; the\n * SDK exposes it today + sends the AST flag.\n */\n flatten(): JoinedQuery<Record<string, unknown> & BaseRow>;\n}\n\n// ── AggQuery (aggregation-mode builder) ──────────────────────────────\n\n/**\n * Aggregation-mode query. Reachable from {@link Query.agg} or\n * {@link Query.groupBy}. Exposes the same `where` / `limit` / `offset`\n * surface as the non-agg `Query` (you can still filter + limit groups)\n * but `.execute()` returns {@link QueryResult} instead of `Page<Row>`.\n *\n * Doesn't expose `.create` / `.update` / `.delete` (aggregations are\n * read-only by definition) or `.count()` (use `.agg({ n: { count: '*' } })`\n * directly + read `result.rows[0].n`).\n */\nexport interface AggQuery {\n toAst(): QueryAst;\n execute(): Promise<QueryResult>;\n /**\n * Return Postgres EXPLAIN (FORMAT JSON) for the underlying SELECT\n * this aggregation builds. Joined aggregations (where the builder\n * arrived here via `.join().agg()`) work in MVP — the source SELECT\n * dominates cost; the in-memory GROUP BY post-runs above it. Bare\n * own-table aggregations currently reject with `EXPLAIN_NOT_SUPPORTED_YET`\n * (lifts when bare-path support lands; see {@link Query.explain}).\n */\n explain(): Promise<ExplainResult>;\n where(clause: Record<string, unknown>): AggQuery;\n /** Refine the group-by fields (replace, not merge). */\n groupBy(fields: ReadonlyArray<FieldRef>): AggQuery;\n /** Refine the aggregation expressions (replace, not merge). */\n agg(expressions: Record<string, AggExpr>): AggQuery;\n limit(n: number): AggQuery;\n offset(n: number): AggQuery;\n}\n\n/**\n * Build a {@link Qb} bound to `client`. The same `qb` instance can\n * be reused across concurrent calls — every call constructs a fresh\n * Query, and the underlying client is already thread-safe.\n *\n * @example\n * import { createClient } from '@dashai/sdk';\n * import { makeQb } from '@dashai/sdk/query';\n *\n * const client = createClient({ baseUrl, installationId, getToken });\n * const qb = makeQb(client);\n *\n * const page = await qb.from<TaskRow>('tasks').execute();\n * console.log(page.rows);\n *\n * The generated SDK (`@dashai/generated/qb`) will eventually export\n * a typed singleton wrapping the default client — `import { qb } from\n * '@dashai/generated'; qb.tasks.execute()`. That layer lands once\n * codegen is updated; until then, `makeQb(client).from('tasks')` is\n * the path.\n */\nexport function makeQb(client: Client): Qb {\n return {\n // Single runtime function backs all three overloads — TypeScript\n // narrows at the call site, but at runtime we discriminate on the\n // input shape:\n // string → own-table slug\n // { kind: 'dep', ... } → cross-module dep ref (slice 4.1b)\n // { kind: 'view', slug } → view ref (slice 5.4b)\n from<Row extends BaseRow = BaseRow>(\n target: string | QbDepRef | QbViewRef,\n ): Query<Row> {\n let ref: TableRef;\n if (typeof target === 'string') {\n assertValidTableSlug(target);\n ref = { kind: 'owned', slug: target };\n } else if (target && typeof target === 'object' && target.kind === 'dep') {\n // Defensive — TypeScript catches most misuse at compile time,\n // but a runtime check keeps the error message friendly when\n // JavaScript consumers reach for the dep form without TS.\n assertValidModuleSlug(target.module);\n assertValidTableSlug(target.slug);\n ref = { kind: 'dep', module: target.module, slug: target.slug };\n } else if (target && typeof target === 'object' && target.kind === 'view') {\n // View slug shape mirrors table slug — snake_case validator\n // (matches `manifest.owns.views[].slug` regex on the backend).\n assertValidTableSlug(target.slug);\n ref = { kind: 'view', slug: target.slug };\n } else {\n throw new Error(\n `@dashai/sdk/query: qb.from() expects a string table slug, { kind: 'dep', module, slug }, or { kind: 'view', slug }; got ${JSON.stringify(target)}.`,\n );\n }\n const ast: QueryAst = {\n v: CURRENT_QUERY_AST_VERSION,\n from: ref,\n };\n return makeQuery<Row>(client, ast);\n },\n };\n}\n\n/**\n * Internal helper — POST an AST to the backend's `.explain()` endpoint\n * and return the typed envelope. Used by all three builder kinds\n * (Query, JoinedQuery, AggQuery) so the wire shape stays consistent.\n *\n * The endpoint URL is the same controller as `/query` but with the\n * `/explain` suffix; transport handles bearer-token + error mapping\n * identically to `.execute()`.\n *\n * `EXPLAIN_NOT_SUPPORTED_YET` errors propagate unchanged — the SDK\n * doesn't have a typed subclass for them yet (the error code is\n * preserved on the base `DashwiseError` so callers can `.catch(e => e.code === 'EXPLAIN_NOT_SUPPORTED_YET')`).\n */\nasync function postExplain(client: Client, ast: QueryAst): Promise<ExplainResult> {\n return client.request<ExplainResult>('POST', '/query/explain', { ast });\n}\n\n/**\n * Internal helper — open an SSE stream against the backend's\n * `/query/stream` endpoint and yield rows. Used by `Query.stream` +\n * `JoinedQuery.stream`. Returns an `AsyncGenerator<Row>` so the\n * caller can use `for await` directly.\n *\n * Wire contract (mirrors the backend's `POST /query/stream`):\n * - Each row is emitted as one SSE event with the default `message`\n * event name + a `data:` line carrying the JSON row.\n * - Terminal `event: complete\\ndata: {\"total\": N}\\n\\n` event marks\n * the end — generator returns cleanly.\n * - In-band `event: error\\ndata: {\"code\": ..., \"message\": ...}\\n\\n`\n * event is thrown as a typed {@link DashwiseError} from the\n * generator. The error's `requestId` is populated from the\n * `x-request-id` response header for trace anchoring.\n *\n * Pre-stream errors (HTTP non-2xx before any SSE data arrives) are\n * read from the response body, parsed via {@link fromBackendEnvelope},\n * and thrown. Same error mapping as the non-streaming endpoints —\n * `ValidationError`, `ForbiddenError`, etc. all surface correctly.\n *\n * **Cleanup**: when the consumer breaks out of the `for await` loop\n * early (early `break`, exception propagation, etc.), the generator's\n * `return()` is invoked. The `finally` block releases the underlying\n * reader's lock so the fetch's network connection can be reclaimed.\n */\nasync function* streamRowsViaSSE<Row>(\n client: Client,\n ast: QueryAst,\n signal?: AbortSignal,\n): AsyncGenerator<Row, void, void> {\n const res = await client.streamRaw('POST', '/query/stream', { ast }, signal);\n const requestId = res.headers.get('x-request-id');\n\n // Pre-stream error path: backend returned a non-2xx response (most\n // commonly a validation-phase 400 before any SSE headers go out).\n // Body is standard JSON; map via fromBackendEnvelope so the caller\n // gets typed errors identical to `.execute()`'s.\n if (!res.ok) {\n const envelope = await readErrorEnvelope(res);\n throw fromBackendEnvelope(res.status, envelope, requestId);\n }\n\n // Streaming phase. We expect `Content-Type: text/event-stream` here;\n // a non-SSE 2xx response is a backend misconfig (defensive — the\n // /query/stream endpoint always sets the SSE content-type when it\n // reaches the streaming phase).\n const reader = res.body?.getReader();\n if (!reader) {\n throw new Error(\n '@dashai/sdk/query: streaming response has no readable body — backend bug or misconfigured fetch impl.',\n );\n }\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n // Stream closed without a `complete` event. Defensive: if\n // there's anything in the buffer, parse it; otherwise the\n // backend closed unexpectedly.\n if (buffer.trim().length > 0) {\n // Final partial event — parse + emit if it's a message.\n const last = parseSseBlock(buffer);\n if (last && last.event === 'message') {\n yield last.data as Row;\n }\n }\n return;\n }\n buffer += decoder.decode(value, { stream: true });\n // SSE event boundary is `\\n\\n`. Split, keep the last (possibly\n // partial) chunk in the buffer for the next read iteration.\n const parts = buffer.split('\\n\\n');\n buffer = parts.pop() ?? '';\n for (const block of parts) {\n if (!block.trim()) continue;\n const parsed = parseSseBlock(block);\n if (!parsed) continue;\n if (parsed.event === 'message') {\n yield parsed.data as Row;\n } else if (parsed.event === 'complete') {\n // Clean end — done.\n return;\n } else if (parsed.event === 'error') {\n // In-band error — map to typed DashwiseError.\n const err = parsed.data as { code?: string; message?: string };\n throw fromBackendEnvelope(\n 500,\n { code: err.code, message: err.message },\n requestId,\n );\n }\n // Unknown event names are ignored (forward-compat with future\n // SSE event types — e.g., `progress`, `heartbeat`).\n }\n }\n } finally {\n // Release the reader's exclusive lock on the body so the fetch\n // can finalize. Don't await reader.cancel() — that would block\n // generator cleanup on a network round-trip we don't need.\n try {\n reader.releaseLock();\n } catch {\n // releaseLock throws if reader is already closed; safe to ignore.\n }\n }\n}\n\n/**\n * Parse one SSE event block (the text between two `\\n\\n` boundaries)\n * into `{ event, data }`. Default event name is `'message'` per SSE\n * spec. Returns null for empty blocks.\n *\n * The block may contain multiple `data:` lines; SSE spec says they\n * concatenate with `\\n` between. We honor that but in practice the\n * backend emits single-line `data:` payloads.\n */\nfunction parseSseBlock(block: string): { event: string; data: unknown } | null {\n const trimmed = block.trim();\n if (trimmed.length === 0) return null;\n let event = 'message';\n const dataLines: string[] = [];\n for (const line of trimmed.split('\\n')) {\n if (line.startsWith('event:')) {\n event = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n // Per SSE spec, strip ONE leading space after the colon if present.\n const v = line.slice(5);\n dataLines.push(v.startsWith(' ') ? v.slice(1) : v);\n }\n // Other field names (id:, retry:) ignored — we don't use them.\n }\n if (dataLines.length === 0) return null;\n const joined = dataLines.join('\\n');\n let data: unknown;\n try {\n data = JSON.parse(joined);\n } catch {\n data = joined; // pass through as string when not JSON\n }\n return { event, data };\n}\n\n/**\n * Read a pre-stream error response body as the backend's standard\n * `{ code, message, retriable?, context? }` envelope. Tolerates\n * malformed bodies (returns null) since the transport's error\n * pipeline already accepts that case.\n */\nasync function readErrorEnvelope(\n res: Response,\n): Promise<{\n code?: string;\n message?: string;\n retriable?: boolean;\n context?: Record<string, unknown>;\n} | null> {\n try {\n const parsed = await res.json();\n if (parsed && typeof parsed === 'object') {\n return parsed as {\n code?: string;\n message?: string;\n retriable?: boolean;\n context?: Record<string, unknown>;\n };\n }\n return null;\n } catch {\n return null;\n }\n}\n\n// ── Internal: Query implementation ───────────────────────────────────\n\n/**\n * Build a `Query<Row>` instance bound to `client` + carrying `ast`.\n * Internal — consumers reach a Query via `qb.from(slug)`.\n *\n * Kept separate from `makeQb` so future chainable methods (which\n * each produce a new Query) can call back into the same factory\n * without duplicating the dispatch logic.\n */\nfunction makeQuery<Row extends BaseRow>(\n client: Client,\n ast: QueryAst,\n): Query<Row> {\n // Local helper: produce a new Query<Row> with `patch` merged into\n // the existing AST. Every chainable method routes through here so\n // the immutability contract is enforced in one place.\n const fork = (patch: Partial<QueryAst>): Query<Row> =>\n makeQuery<Row>(client, { ...ast, ...patch });\n\n return {\n toAst(): QueryAst {\n // Return a structural clone so callers can introspect/mutate\n // without disturbing the builder's internal state. Cheap —\n // the AST is small JSON.\n return JSON.parse(JSON.stringify(ast)) as QueryAst;\n },\n\n where(clause: Where<Row>): Query<Row> {\n // The SDK's `Where<Row>` is structurally compatible with the\n // AST's looser `WhereClause` (untyped at the AST layer). Cast\n // at the boundary so the typed surface stays sharp.\n return fork({ where: clause as unknown as WhereClause });\n },\n\n orderBy(clauses: ReadonlyArray<OrderClause>): Query<Row> {\n // Defensive copy — the AST stores a fresh array so the caller\n // can mutate their input without retroactively changing the\n // builder's state.\n return fork({ orderBy: [...clauses] });\n },\n\n limit(n: number): Query<Row> {\n // Fail fast on obvious garbage. Backend enforces the upper bound\n // (`RESULT_TOO_LARGE` over 1000) — we only catch the cases that\n // would round-trip to a confusing 400.\n if (!Number.isInteger(n) || n < 1) {\n throw new Error(\n `@dashai/sdk/query: .limit(${n}) must be a positive integer.`,\n );\n }\n return fork({ limit: n });\n },\n\n offset(n: number): Query<Row> {\n if (!Number.isInteger(n) || n < 0) {\n throw new Error(\n `@dashai/sdk/query: .offset(${n}) must be a non-negative integer.`,\n );\n }\n return fork({ offset: n });\n },\n\n select(fields: ReadonlyArray<string>): Query<Row> {\n // Empty array intentionally writes `select: []` to the AST so\n // the backend sees \"user said nothing → no narrowing.\" Matches\n // narrowFieldsFromOpts's contract.\n return fork({ select: [...fields] });\n },\n\n async count(): Promise<number> {\n // Lower limit to 1 row to minimize transport cost — the\n // backend's `total` reflects the post-filter count regardless\n // of limit. Defensive: if the consumer already set a limit we\n // honor theirs (some test harnesses pin a specific page size\n // and assert deterministic behavior across both calls).\n const minimalAst: QueryAst = {\n ...ast,\n limit: ast.limit ?? 1,\n };\n const result = await client.request<Page<Row>>(\n 'POST',\n '/query',\n { ast: minimalAst },\n );\n if (Array.isArray(result)) {\n // Bare-array fallback — total isn't carried; assume the array\n // length (matches what the .execute() coercion does).\n return (result as Row[]).length;\n }\n return result.total;\n },\n\n async execute(): Promise<Page<Row>> {\n // The backend's query controller lives at\n // `POST /api/modules/<installation>/query`. `client.request()`\n // prepends the installation-scoped base URL, applies the\n // bearer token, maps errors to typed DashwiseError subclasses,\n // and handles the 204 case (which this endpoint won't emit\n // but the transport covers it anyway).\n const result = await client.request<Page<Row>>(\n 'POST',\n '/query',\n { ast },\n );\n // Defence in depth: same Page-envelope coercion the owned-table\n // `db().list()` does, in case a misconfigured proxy strips the\n // envelope. Consumers always see `{ rows, total, limit, offset }`.\n if (Array.isArray(result)) {\n return {\n rows: result as Row[],\n total: (result as Row[]).length,\n limit: ast.limit ?? (result as Row[]).length,\n offset: ast.offset ?? 0,\n };\n }\n return result;\n },\n\n async explain(): Promise<ExplainResult> {\n // Phase 5 (2026-05-19): bare own-table reads currently reject\n // server-side with EXPLAIN_NOT_SUPPORTED_YET — the SDK passes\n // the call through anyway so the typed error reaches the caller\n // (the error's `context.fromKind` lets them branch on the\n // unsupported shape if they want). Joined queries (which go\n // through `JoinedQuery.explain`) are the supported MVP path.\n return postExplain(client, ast);\n },\n\n stream(opts?: { signal?: AbortSignal }): AsyncIterable<Row> {\n // Phase 6 streaming (2026-05-19): return an AsyncGenerator\n // that yields rows from the SSE endpoint. The generator is\n // lazy — no network call until the consumer starts iterating.\n return streamRowsViaSSE<Row>(client, ast, opts?.signal);\n },\n\n groupBy(fields: ReadonlyArray<FieldRef>): AggQuery {\n // Transition to aggregation mode. `.groupBy()` alone (without\n // a subsequent `.agg()`) is still a valid intermediate state;\n // the backend rejects with VALIDATION_FAILED when .execute()\n // fires against an AST that has groupBy but no agg.\n return makeAggQuery(client, { ...ast, groupBy: [...fields] });\n },\n\n agg(expressions: Record<string, AggExpr>): AggQuery {\n // Transition to aggregation mode. Shape of `expressions` is\n // validated server-side (Zod owns the agg expression grammar)\n // so the SDK doesn't duplicate the rules.\n return makeAggQuery(client, { ...ast, agg: expressions });\n },\n\n // ── Join entries (Phase 3 slice 3.3) ──────────────────────────\n //\n // .join() / .leftJoin() transition to JoinedQuery whose surface\n // omits write methods + the aggregation suite. The SDK delegates\n // shape validation to the backend's AST validator + the\n // join-translator; today's slice 3.2b-1 cap means combinations\n // with where/orderBy/limit/offset/agg backend-reject with clear\n // \"lands in slice X\" errors.\n\n join<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n ): JoinedQuery<Row & Record<string, T>> {\n return makeJoinedQuery<Row & Record<string, T>>(client, {\n ...ast,\n joins: [...(ast.joins ?? []), buildJoinNode(target, opts, 'inner')],\n });\n },\n\n leftJoin<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'left'>,\n ): JoinedQuery<Row & Record<string, T | null>> {\n return makeJoinedQuery<Row & Record<string, T | null>>(client, {\n ...ast,\n joins: [...(ast.joins ?? []), buildJoinNode(target, opts, 'left')],\n });\n },\n };\n}\n\n// ── JoinedQuery factory (slice 3.3) ─────────────────────────────────\n\n/**\n * Build a `JoinedQuery<Row>` bound to `client` + carrying `ast`.\n * Mirrors `makeQuery` shape but the surface omits write methods +\n * aggregation; `execute()` still returns `Page<Row>`.\n */\nfunction makeJoinedQuery<Row extends BaseRow>(\n client: Client,\n ast: QueryAst,\n): JoinedQuery<Row> {\n const fork = (patch: Partial<QueryAst>): JoinedQuery<Row> =>\n makeJoinedQuery<Row>(client, { ...ast, ...patch });\n\n return {\n toAst(): QueryAst {\n return JSON.parse(JSON.stringify(ast)) as QueryAst;\n },\n\n where(clause: Where<Row>): JoinedQuery<Row> {\n return fork({ where: clause as unknown as WhereClause });\n },\n\n orderBy(clauses: ReadonlyArray<OrderClause>): JoinedQuery<Row> {\n return fork({ orderBy: [...clauses] });\n },\n\n limit(n: number): JoinedQuery<Row> {\n if (!Number.isInteger(n) || n < 1) {\n throw new Error(\n `@dashai/sdk/query: .limit(${n}) must be a positive integer.`,\n );\n }\n return fork({ limit: n });\n },\n\n offset(n: number): JoinedQuery<Row> {\n if (!Number.isInteger(n) || n < 0) {\n throw new Error(\n `@dashai/sdk/query: .offset(${n}) must be a non-negative integer.`,\n );\n }\n return fork({ offset: n });\n },\n\n join<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n ): JoinedQuery<Row & Record<string, T>> {\n return makeJoinedQuery<Row & Record<string, T>>(client, {\n ...ast,\n joins: [...(ast.joins ?? []), buildJoinNode(target, opts, 'inner')],\n });\n },\n\n leftJoin<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'left'>,\n ): JoinedQuery<Row & Record<string, T | null>> {\n return makeJoinedQuery<Row & Record<string, T | null>>(client, {\n ...ast,\n joins: [...(ast.joins ?? []), buildJoinNode(target, opts, 'left')],\n });\n },\n\n flatten(): JoinedQuery<Record<string, unknown> & BaseRow> {\n // SDK sets the AST flag today. Backend wiring lands in slice\n // 3.5; until then the backend silently returns nested rows.\n // Documented in the .flatten() JSDoc.\n return makeJoinedQuery<Record<string, unknown> & BaseRow>(client, {\n ...ast,\n flatten: true,\n });\n },\n\n async execute(): Promise<Page<Row>> {\n const result = await client.request<Page<Row>>(\n 'POST',\n '/query',\n { ast },\n );\n if (Array.isArray(result)) {\n return {\n rows: result as Row[],\n total: (result as Row[]).length,\n limit: ast.limit ?? (result as Row[]).length,\n offset: ast.offset ?? 0,\n };\n }\n return result;\n },\n\n async explain(): Promise<ExplainResult> {\n // Phase 5 (2026-05-19): joined queries are the supported MVP\n // path — the backend's `executeJoin` exposes its translateJoin\n // SQL cleanly so EXPLAIN wraps it directly. Returns the typed\n // {@link ExplainResult} envelope.\n return postExplain(client, ast);\n },\n\n stream(opts?: { signal?: AbortSignal }): AsyncIterable<Row> {\n // Phase 6 streaming: joined queries stream their nested-row\n // shape — backend's executeJoin produces the same shape `.execute()`\n // returns, just delivered row-by-row via SSE.\n return streamRowsViaSSE<Row>(client, ast, opts?.signal);\n },\n };\n}\n\n/**\n * Build a {@link JoinNode} from the SDK's `.join()` / `.leftJoin()`\n * call shape. Defaults `kind` per the caller's method choice when\n * `opts.kind` is absent.\n */\nfunction buildJoinNode(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n defaultKind: 'inner' | 'left',\n): {\n kind: 'inner' | 'left';\n to: TableRef;\n on: Record<string, string>;\n as?: string;\n} {\n // Phase 4 slice 4.2 (2026-05-19): target accepts a dep ref in\n // addition to a bare own-table slug — same overload semantics as\n // `qb.from()`. The backend's `executeJoin` resolves either side\n // through the right chain (own-table or dep contract).\n let to: TableRef;\n let displaySlug: string;\n if (typeof target === 'string') {\n assertValidTableSlug(target);\n to = { kind: 'owned', slug: target };\n displaySlug = target;\n } else if (target && typeof target === 'object' && target.kind === 'dep') {\n assertValidModuleSlug(target.module);\n assertValidTableSlug(target.slug);\n to = { kind: 'dep', module: target.module, slug: target.slug };\n displaySlug = `${target.module}:${target.slug}`;\n } else {\n throw new Error(\n `@dashai/sdk/query: .join() target must be a table slug string or { kind: 'dep', module, slug }; got ${JSON.stringify(target)}.`,\n );\n }\n if (!opts.on || Object.keys(opts.on).length === 0) {\n throw new Error(\n `@dashai/sdk/query: .join('${displaySlug}') requires an \\`on\\` clause with at least one field pair. Link-field-derived \\`on\\` inference lands in a follow-up slice.`,\n );\n }\n return {\n kind: opts.kind ?? defaultKind,\n to,\n on: opts.on,\n ...(opts.as !== undefined ? { as: opts.as } : {}),\n };\n}\n\n/**\n * Build an `AggQuery` bound to `client` + carrying `ast`. Symmetric\n * with `makeQuery` but `.execute()` returns {@link QueryResult} and\n * the surface omits per-row methods (no `.create` / `.update` etc.)\n * since aggregation results aren't individual rows.\n *\n * Internal — consumers reach AggQuery via `.agg()` or `.groupBy()`\n * on a non-agg Query.\n */\nfunction makeAggQuery(client: Client, ast: QueryAst): AggQuery {\n const fork = (patch: Partial<QueryAst>): AggQuery =>\n makeAggQuery(client, { ...ast, ...patch });\n\n return {\n toAst(): QueryAst {\n return JSON.parse(JSON.stringify(ast)) as QueryAst;\n },\n\n where(clause: Record<string, unknown>): AggQuery {\n return fork({ where: clause as WhereClause });\n },\n\n groupBy(fields: ReadonlyArray<FieldRef>): AggQuery {\n return fork({ groupBy: [...fields] });\n },\n\n agg(expressions: Record<string, AggExpr>): AggQuery {\n return fork({ agg: expressions });\n },\n\n limit(n: number): AggQuery {\n if (!Number.isInteger(n) || n < 1) {\n throw new Error(\n `@dashai/sdk/query: .limit(${n}) must be a positive integer.`,\n );\n }\n return fork({ limit: n });\n },\n\n offset(n: number): AggQuery {\n if (!Number.isInteger(n) || n < 0) {\n throw new Error(\n `@dashai/sdk/query: .offset(${n}) must be a non-negative integer.`,\n );\n }\n return fork({ offset: n });\n },\n\n async execute(): Promise<QueryResult> {\n const result = await client.request<QueryResult>(\n 'POST',\n '/query',\n { ast },\n );\n // Defence in depth: bare-array fallback (matches non-agg path).\n if (Array.isArray(result)) {\n return {\n rows: result as Array<Record<string, unknown>>,\n total: (result as unknown[]).length,\n };\n }\n return result;\n },\n\n async explain(): Promise<ExplainResult> {\n // Phase 5 (2026-05-19): joined aggregations work (backend\n // EXPLAINs the source SELECT — the in-memory GROUP BY\n // post-runs above it and isn't visible to Postgres). Bare\n // own-table / cross-mod aggregations reject server-side with\n // EXPLAIN_NOT_SUPPORTED_YET until the bare-path SQL extraction\n // lands. The SDK passes the call through unchanged either way.\n return postExplain(client, ast);\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/data/errors.ts","../../src/query/ast.ts","../../src/query/builder.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Error model for @dashai/sdk.\n *\n * The backend speaks a structured error envelope:\n *\n * {\n * \"code\": \"ROW_NOT_FOUND\",\n * \"message\": \"Row 123 not found in tasks\",\n * \"retriable\": false,\n * \"context\": { ... }\n * }\n *\n * This module wraps that envelope in a typed `DashwiseError` plus a small\n * set of subclasses for the codes consumers most commonly want to branch\n * on (network errors, auth failures, row-not-found, contract violations).\n *\n * Anything not matched by a specific subclass becomes a plain\n * `DashwiseError` carrying the raw `code`. Branch on `.code` for codes\n * we don't have a class for; use `instanceof` for the common cases.\n */\n\n/**\n * Known error codes. Not exhaustive — the backend emits many codes\n * specific to individual modules / endpoints. Use these constants when\n * you want compile-time safety; otherwise treat `err.code` as a string.\n *\n * The list is curated to the codes a module author is most likely to\n * branch on. Adding a new code here is a non-breaking change.\n */\nexport const DashwiseErrorCode = {\n // Auth / authz\n UNAUTHENTICATED: 'UNAUTHENTICATED',\n FORBIDDEN: 'FORBIDDEN',\n TOKEN_EXPIRED: 'TOKEN_EXPIRED',\n INSTALLATION_NOT_FOUND: 'INSTALLATION_NOT_FOUND',\n INSTALLATION_MODE_UNSUPPORTED: 'INSTALLATION_MODE_UNSUPPORTED',\n // Contract enforcement\n CONTRACT_VIOLATION: 'CONTRACT_VIOLATION',\n FIELD_NOT_READABLE: 'FIELD_NOT_READABLE',\n FILTER_OP_UNKNOWN: 'FILTER_OP_UNKNOWN',\n OPERATION_NOT_ALLOWED: 'OPERATION_NOT_ALLOWED',\n // Cross-module dependencies (Phase 1 of the SDK query plane —\n // see dashwise-devops-docs/plans/modules-sdk-query-v1.md).\n // Surfaced when reading borrowed tables via `client.deps(slug)`.\n DEPENDENCY_NOT_DECLARED: 'DEPENDENCY_NOT_DECLARED',\n TABLE_NOT_IN_CONTRACT: 'TABLE_NOT_IN_CONTRACT',\n PROVIDER_TABLE_MISSING: 'PROVIDER_TABLE_MISSING',\n OPERATION_NOT_ALLOWED_BY_PROVIDER: 'OPERATION_NOT_ALLOWED_BY_PROVIDER',\n // Cross-module dependency PARK state (Phase 3 modules-platform-v2 —\n // dependency connect/bind lifecycle). Surfaced at RUNTIME (409) when a\n // module reads a borrowed table (`client.deps(slug)` / `qb.deps.<slug>`)\n // or invokes a dep action whose `module_dependencies` row is currently\n // `status='unbound'` — the dependency is declared but not connected to a\n // provider installation (no provider in the workspace, version mismatch,\n // manually unbound, provider uninstalled, etc.). The consumer app should\n // render a \"connect your provider\" state rather than treating this as a\n // hard failure. `err.context` carries `{ provider, range, reason }`.\n DEP_UNBOUND: 'DEP_UNBOUND',\n // Workspace-table `uses` plane (Phase 4 modules-platform-v2 — the\n // declared/portable path for a module to read+write a WORKSPACE table\n // the workspace already owns, as opposed to `deps` = another module's\n // tables). Surfaced at RUNTIME against `client.uses(slug)` /\n // `qb.uses.<slug>` / the `POST /api/module-api/uses/:slug/...` REST\n // plane. The BINDING is the grant on this plane (for sandbox AND\n // api_key auth alike — a kind-local key does NOT bypass it):\n // - USES_UNBOUND 409 — the `uses` slot is declared but its\n // `module_table_bindings` row isn't `status='bound'` (never bound,\n // ambiguous match, no match, table trashed, ops widened pending\n // re-consent, manually unbound). `err.context` carries\n // `{ uses_slug, reason }`. Recoverable: a workspace admin binds a\n // table (`dashwise bind <uses_slug>`), then the read succeeds.\n // - USES_OP_NOT_ALLOWED 403 — the operation isn't in the binding's\n // enforcement set (manifest `ops` ∩ `consented_ops`). `err.context`\n // carries `{ uses_slug, op }`. Fix: widen the manifest ops +\n // re-consent (re-bind), or the admin re-binds to consent.\n // - CONTRACT_FIELD_MISSING 409 — a field the binding maps no longer\n // exists on the physical table (schema drift). `err.context`\n // carries `{ uses_slug, field }`. The binding is best-effort parked\n // on detection; an admin re-binds to a table that has the field.\n USES_UNBOUND: 'USES_UNBOUND',\n USES_OP_NOT_ALLOWED: 'USES_OP_NOT_ALLOWED',\n CONTRACT_FIELD_MISSING: 'CONTRACT_FIELD_MISSING',\n // `uses`-plane query joins are NOT supported in v1 — a\n // `qb.uses.<slug>.join(...)` / cross-plane join is rejected with a\n // typed 400. (Joins WITH a uses table as a target are v2 territory.)\n USES_JOIN_UNSUPPORTED: 'USES_JOIN_UNSUPPORTED',\n // Query-plane joins (Phase 3 — see\n // dashwise-devops-docs/plans/modules-sdk-query-v1-p3-execution.md).\n // Surfaced when `.join('<slug>')` / `.leftJoin('<slug>')` can't\n // resolve the target. Pre-declared in slice 3.1 so slice 3.2's\n // backend translator can emit them through fromBackendEnvelope\n // without an SDK roundtrip.\n LINK_FIELD_NOT_FOUND: 'LINK_FIELD_NOT_FOUND',\n NOT_A_LINK_FIELD: 'NOT_A_LINK_FIELD',\n JOIN_NOT_LINKED: 'JOIN_NOT_LINKED',\n // Cross-module actions (Phase 7 slice 7.3a — see\n // dashwise-devops-docs/plans/modules-sdk-query-v1-p7-cross-module-writes.md).\n // Surfaced when `qb.deps.<dep>.actions.<name>(input)` (generated\n // factory) dispatches against the backend endpoint\n // `POST /api/module-api/deps/:providerSlug/actions/:actionName`.\n // Slice 7.2b ships the backend emitter; slice 7.3a (this slice)\n // reserves the codes + maps them to DepActionError in the SDK.\n ACTION_NOT_EXPOSED: 'ACTION_NOT_EXPOSED',\n ACTION_NOT_DECLARED: 'ACTION_NOT_DECLARED',\n ACTION_INPUT_INVALID: 'ACTION_INPUT_INVALID',\n ACTION_OUTPUT_INVALID: 'ACTION_OUTPUT_INVALID',\n ACTION_RATE_LIMITED: 'ACTION_RATE_LIMITED',\n ACTION_EXECUTION_FAILED: 'ACTION_EXECUTION_FAILED',\n // Data plane\n ROW_NOT_FOUND: 'ROW_NOT_FOUND',\n TABLE_NOT_FOUND: 'TABLE_NOT_FOUND',\n VALIDATION_FAILED: 'VALIDATION_FAILED',\n ROW_LIMIT_EXCEEDED: 'ROW_LIMIT_EXCEEDED',\n STORAGE_LIMIT_EXCEEDED: 'STORAGE_LIMIT_EXCEEDED',\n // Outbound / runtime\n OUTBOUND_NOT_ALLOWED: 'OUTBOUND_NOT_ALLOWED',\n OUTBOUND_CONCURRENCY_LIMITED: 'OUTBOUND_CONCURRENCY_LIMITED',\n // Transport\n NETWORK_ERROR: 'NETWORK_ERROR',\n TIMEOUT: 'TIMEOUT',\n // Server\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n} as const;\n\nexport type DashwiseErrorCode = (typeof DashwiseErrorCode)[keyof typeof DashwiseErrorCode];\n\n/**\n * Base class for every error thrown by @dashai/sdk. Carries the\n * structured envelope from the backend (or a synthetic one for\n * client-side failures like network errors / timeouts).\n *\n * Always thrown — never returned as a value. The async client methods\n * `.list()` / `.get()` etc. reject with one of these on failure.\n */\nexport class DashwiseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly retriable: boolean;\n readonly context: Record<string, unknown> | undefined;\n /**\n * Backend-issued trace anchor (Phase 6 slice 6.2, 2026-05-19).\n * Mirrors the `x-request-id` response header. Populated whenever\n * the backend's `RuntimeQueryController` (or any future endpoint\n * that echoes the header) is the origin of this error. `null` when\n * the failure is client-side (NetworkError / TimeoutError — the\n * request never reached the server, so there's no server-issued\n * ID to surface).\n *\n * Use this as a debugging trace anchor when filing a bug against a\n * specific failed query — the matching row in `module_query_log`\n * carries the full backend context (ast_hash, error_code,\n * duration_ms, etc.).\n */\n readonly requestId: string | null;\n\n constructor(\n code: string,\n message: string,\n options: {\n status?: number;\n retriable?: boolean;\n context?: Record<string, unknown>;\n cause?: unknown;\n /**\n * Phase 6 slice 6.2: backend `x-request-id` response header,\n * captured by the transport before throwing. Pass `undefined`\n * (or omit) for client-side errors that never hit the server.\n */\n requestId?: string | null;\n } = {},\n ) {\n super(message);\n this.name = this.constructor.name;\n this.code = code;\n this.status = options.status ?? 0;\n this.retriable = options.retriable ?? false;\n this.context = options.context;\n this.requestId = options.requestId ?? null;\n if (options.cause !== undefined) {\n // Node 18+ and modern browsers support Error.cause natively.\n // Use a property assignment because `super({ cause })` is awkward\n // when the base class only takes a string.\n (this as { cause?: unknown }).cause = options.cause;\n }\n }\n}\n\n/** Network-level failure (DNS, connection refused, TLS, fetch threw). */\nexport class NetworkError extends DashwiseError {\n constructor(message: string, cause?: unknown) {\n super(DashwiseErrorCode.NETWORK_ERROR, message, { retriable: true, cause });\n }\n}\n\n/** Request exceeded the configured timeout (`timeoutMs`). */\nexport class TimeoutError extends DashwiseError {\n constructor(message: string, timeoutMs: number) {\n super(DashwiseErrorCode.TIMEOUT, message, {\n retriable: true,\n context: { timeout_ms: timeoutMs },\n });\n }\n}\n\n/** 401 — token missing/invalid. */\nexport class UnauthenticatedError extends DashwiseError {\n constructor(message = 'Authentication required', requestId: string | null = null) {\n super(DashwiseErrorCode.UNAUTHENTICATED, message, { status: 401, requestId });\n }\n}\n\n/** 403 — caller lacks the role/scope for this operation. */\nexport class ForbiddenError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.FORBIDDEN, message, { status: 403, context, requestId });\n }\n}\n\n/** 404 — `GET /db/<table>/<id>` for a missing or soft-deleted row. */\nexport class RowNotFoundError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.ROW_NOT_FOUND, message, { status: 404, context, requestId });\n }\n}\n\n/**\n * Schema-level rejection: dep contract violated, table not exposed, field\n * not readable, filter op not allowed. Covers the family of \"you asked for\n * something the manifest doesn't permit\" errors.\n */\nexport class ContractViolationError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.CONTRACT_VIOLATION, message, {\n status: 403,\n context,\n requestId,\n });\n }\n}\n\n/** 422-ish — request body failed validation. Inspect `context` for field-level errors. */\nexport class ValidationError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.VALIDATION_FAILED, message, {\n status: 400,\n context,\n requestId,\n });\n }\n}\n\n// ── Cross-module dependency errors ─────────────────────────────────────\n//\n// Surfaced when calling `client.deps(slug).db<Row>(table).<op>(...)`.\n// Each subclass preserves the original backend `code` (unlike the older\n// `ContractViolationError` which collapses several codes onto a single\n// `CONTRACT_VIOLATION` string — that's a known wart we don't replicate\n// in new subclasses). Branch on `instanceof` for the common cases;\n// inspect `err.code` if you need to distinguish the two contract-error\n// codes (`DEPENDENCY_NOT_DECLARED` vs `TABLE_NOT_IN_CONTRACT`).\n\n/**\n * 404 — the caller's `module.json#dependencies` doesn't declare a\n * dependency on the requested provider module, OR the declared dep\n * exists but its `reads[]` block doesn't include the requested table.\n *\n * Either way the fix is on the **caller's** side: update the manifest\n * to declare the dep / add the table to `reads[]`, then re-publish.\n *\n * Covers both `DEPENDENCY_NOT_DECLARED` and `TABLE_NOT_IN_CONTRACT` —\n * inspect `err.code` to distinguish if you need different UX per case.\n */\nexport class DependencyContractError extends DashwiseError {\n constructor(\n code: string,\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(code, message, { status: 404, context, requestId });\n }\n}\n\n/**\n * 404 — the provider's physical table no longer exists. This means the\n * provider published a version that dropped the table while the\n * consumer's installation still pinned the old contract. Recovery is\n * on the **provider's** side (republish with the table) or the\n * workspace admin's (downgrade the provider, or upgrade the consumer\n * to a version whose `dependencies[]` no longer references it).\n */\nexport class ProviderTableMissingError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.PROVIDER_TABLE_MISSING, message, {\n status: 404,\n context,\n requestId,\n });\n }\n}\n\n/**\n * 403 — the provider's `exposes.operations.<verb>.allowed` is explicitly\n * `false` for the requested verb. Distinct from `OPERATION_NOT_ALLOWED`\n * (which is the **owner's** own-table policy) — `OPERATION_NOT_ALLOWED_BY_PROVIDER`\n * is the provider opting out of letting depending modules use a verb.\n *\n * No caller-side fix; either the provider needs to flip the gate, or\n * the consumer needs to find a different way to satisfy the use case.\n */\nexport class OperationNotAllowedByProviderError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.OPERATION_NOT_ALLOWED_BY_PROVIDER, message, {\n status: 403,\n context,\n requestId,\n });\n }\n}\n\n/**\n * The vocabulary of `unbound_reason` values a parked dependency can\n * carry, surfaced in {@link DepUnboundError.reason}. Mirrors the\n * backend's `module_dependencies.unbound_reason` enum (Phase 3\n * modules-platform-v2). Not exhaustive by design — treat `.reason` as a\n * string and branch on these constants when you want compile-time\n * safety. Adding a new reason is a non-breaking change.\n *\n * Resolve/bind-time reasons (why the dep never bound):\n * - `MISSING_DEPENDENCY` no install of the provider module in the workspace\n * - `DEPENDENCY_VERSION_MISMATCH` an install exists but its version is outside the declared range\n * - `PROVIDER_VERSION_RECORD_MISSING` the provider's version row is gone (data-integrity edge)\n * - `EXPOSES_MISSING` the provider no longer exposes a table/field the dep reads\n * - `EXPOSES_PRIVATE` the exposed surface is `private` to the consumer\n * - `ACTION_NOT_EXPOSED` a declared dep action isn't exposed by the provider (Rule #11)\n * - `PROVIDER_NOT_PRODUCTION` kind hygiene: a production consumer can only bind a production provider\n *\n * Lifecycle reasons (the dep bound once, then got parked):\n * - `MANUALLY_UNBOUND` a workspace admin ran `deps unbind` / the unbind endpoint\n * - `PROVIDER_UPGRADE_BROKE_CONTRACT` a provider upgrade (with `force`) broke this consumer's contract\n * - `PROVIDER_UNINSTALLED` the provider was uninstalled with `park_consumers`\n */\nexport const DepUnboundReason = {\n MISSING_DEPENDENCY: 'MISSING_DEPENDENCY',\n DEPENDENCY_VERSION_MISMATCH: 'DEPENDENCY_VERSION_MISMATCH',\n PROVIDER_VERSION_RECORD_MISSING: 'PROVIDER_VERSION_RECORD_MISSING',\n EXPOSES_MISSING: 'EXPOSES_MISSING',\n EXPOSES_PRIVATE: 'EXPOSES_PRIVATE',\n ACTION_NOT_EXPOSED: 'ACTION_NOT_EXPOSED',\n PROVIDER_NOT_PRODUCTION: 'PROVIDER_NOT_PRODUCTION',\n MANUALLY_UNBOUND: 'MANUALLY_UNBOUND',\n PROVIDER_UPGRADE_BROKE_CONTRACT: 'PROVIDER_UPGRADE_BROKE_CONTRACT',\n PROVIDER_UNINSTALLED: 'PROVIDER_UNINSTALLED',\n} as const;\n\nexport type DepUnboundReason =\n (typeof DepUnboundReason)[keyof typeof DepUnboundReason];\n\n/**\n * 409 — a runtime read (borrowed-table `list` / `get`, query-plane\n * `qb.deps.<slug>.<table>`, or a cross-module action) targeted a\n * dependency whose `module_dependencies` row is `status='unbound'`\n * (Phase 3 modules-platform-v2 dependency connect lifecycle).\n *\n * \"Unbound\" means the dependency is *declared* in the consumer's\n * manifest but not *connected* to a provider installation. It happens\n * when the resolver PARKED the dep at install/apply time (no provider\n * in the workspace, version mismatch, exposes/action coverage gap) or\n * when the dependency was later parked (manually unbound, provider\n * upgrade broke the contract, provider uninstalled).\n *\n * This is a *recoverable* state, not a bug in the consumer. Catch it and\n * render a \"connect your provider\" prompt — a workspace admin resolves it\n * by installing/binding the provider (`dashwise deps bind <slug>` or the\n * `POST /api/installations/:id/dependencies/:providerSlug/bind` endpoint).\n * Once bound, the same read succeeds with no code change.\n *\n * Convenience accessors ({@link DepUnboundError.provider},\n * {@link DepUnboundError.range}, {@link DepUnboundError.reason}) read the\n * backend's `context: { provider, range, reason }` so callers don't have\n * to reach into `err.context` by hand.\n *\n * @example\n * ```ts\n * import { DepUnboundError } from '@dashai/sdk';\n *\n * try {\n * const contacts = await client.deps('crm').db<ContactRow>('contacts').list();\n * // …or the typed builder: await qb.deps.crm.contacts.execute();\n * } catch (err) {\n * if (err instanceof DepUnboundError) {\n * // Declared but not connected — show a connect-your-provider CTA.\n * return <ConnectProvider provider={err.provider} range={err.range} reason={err.reason} />;\n * }\n * throw err;\n * }\n * ```\n */\nexport class DepUnboundError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.DEP_UNBOUND, message, {\n status: 409,\n context,\n requestId,\n });\n }\n\n /**\n * The provider module slug the parked dependency points at (kebab-case,\n * e.g. `crm`). Read from `context.provider`; `undefined` if the backend\n * omitted it.\n */\n get provider(): string | undefined {\n const v = this.context?.provider;\n return typeof v === 'string' ? v : undefined;\n }\n\n /**\n * The SemVer range the consumer's manifest declared for this dependency\n * (e.g. `^1.2.0`). Read from `context.range`; `undefined` if omitted.\n */\n get range(): string | undefined {\n const v = this.context?.range;\n return typeof v === 'string' ? v : undefined;\n }\n\n /**\n * Why the dependency is unbound — one of {@link DepUnboundReason} (but\n * typed as `string` for forward-compat with reasons the backend may add).\n * Read from `context.reason`; `undefined` if omitted.\n */\n get reason(): string | undefined {\n const v = this.context?.reason;\n return typeof v === 'string' ? v : undefined;\n }\n}\n\n// ── Workspace-table `uses` errors (Phase 4 modules-platform-v2) ────────\n//\n// The `uses` plane lets a module read+write a WORKSPACE table the\n// workspace already owns — the declared, portable counterpart to the\n// ad-hoc numeric-tableId Data Hub bridge. A module declares an ABSTRACT\n// contract in `module.json#uses[].tables[]` (slug + fields + abstract\n// types + ops); at install/bind time that slot is BOUND to a physical\n// workspace table via a `field_map` (declared field → physical field id).\n// The binding IS the grant: even a kind-local `dwk_` key can't touch a\n// uses table until its slot is bound.\n//\n// Surfaced against `client.uses(slug).db<Row>(table)` /\n// `qb.uses.<slug>.<table>` / the REST plane at\n// `POST /api/module-api/uses/:usesSlug/rows/...`. Same\n// preserve-the-code, convenience-accessor pattern as DepUnboundError.\n\n/**\n * The vocabulary of `reason` values a parked `uses` binding can carry,\n * surfaced in {@link UsesUnboundError.reason}. Mirrors the backend's\n * `module_table_bindings` unbound_reason enum for this plane\n * (Phase 4 modules-platform-v2). Not exhaustive by design — treat\n * `.reason` as a string; branch on these constants for compile-time\n * safety. Adding a new reason is non-breaking.\n *\n * - `NOT_BOUND_YET` the slot has never been bound (declared\n * but no workspace table connected yet)\n * - `AMBIGUOUS_MATCH` auto-match found >1 candidate table; a\n * human must pick one (`dashwise bind`)\n * - `NO_MATCH` auto-match found no candidate table\n * - `TABLE_DELETED` the bound table was trashed (or a mapped\n * field drifted away) → the row parked\n * - `OPS_WIDENED_PENDING_CONSENT` the manifest widened `ops` beyond what\n * was consented at bind time; re-bind to\n * refresh consent\n * - `MANUALLY_UNBOUND` a workspace admin ran `dashwise unbind`\n */\nexport const UsesUnboundReason = {\n NOT_BOUND_YET: 'NOT_BOUND_YET',\n AMBIGUOUS_MATCH: 'AMBIGUOUS_MATCH',\n NO_MATCH: 'NO_MATCH',\n TABLE_DELETED: 'TABLE_DELETED',\n OPS_WIDENED_PENDING_CONSENT: 'OPS_WIDENED_PENDING_CONSENT',\n MANUALLY_UNBOUND: 'MANUALLY_UNBOUND',\n} as const;\n\nexport type UsesUnboundReason =\n (typeof UsesUnboundReason)[keyof typeof UsesUnboundReason];\n\n/**\n * 409 — a runtime read/write against a `uses` table whose\n * `module_table_bindings` row is not `status='bound'` (Phase 4\n * modules-platform-v2 workspace-table binding lifecycle).\n *\n * \"Unbound\" means the workspace-table slot is *declared* in the module's\n * manifest `uses` block but not *connected* to a physical workspace\n * table. It happens when install-time auto-match parked the slot (no\n * candidate / ambiguous candidates / a write contract that never\n * auto-binds), the bound table was later trashed, the manifest widened\n * `ops` beyond consent, or an admin manually unbound it.\n *\n * A *recoverable* state, not a bug. Catch it and render a \"connect a\n * table\" prompt — a workspace admin resolves it by binding a workspace\n * table to the slot (`dashwise bind <uses_slug>` / the\n * `PUT /api/installations/:id/bindings/:usesSlug` endpoint). Once bound,\n * the same call succeeds with no code change.\n *\n * Convenience accessors read the backend's `context: { uses_slug, reason }`.\n *\n * @example\n * ```ts\n * import { UsesUnboundError } from '@dashai/sdk';\n *\n * try {\n * const staff = await client.uses('employees').db<EmployeeRow>('employees').list();\n * // …or the typed builder: await qb.uses.employees.execute();\n * } catch (err) {\n * if (err instanceof UsesUnboundError) {\n * return <BindWorkspaceTable slot={err.usesSlug} reason={err.reason} />;\n * }\n * throw err;\n * }\n * ```\n */\nexport class UsesUnboundError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.USES_UNBOUND, message, {\n status: 409,\n context,\n requestId,\n });\n }\n\n /**\n * The manifest `uses[].tables[].slug` of the unbound slot (kebab-case,\n * e.g. `employees`). Read from `context.uses_slug`; `undefined` if the\n * backend omitted it.\n */\n get usesSlug(): string | undefined {\n const v = this.context?.uses_slug;\n return typeof v === 'string' ? v : undefined;\n }\n\n /**\n * Why the slot is unbound — one of {@link UsesUnboundReason} (typed as\n * `string` for forward-compat with reasons the backend may add). Read\n * from `context.reason`; `undefined` if omitted.\n */\n get reason(): string | undefined {\n const v = this.context?.reason;\n return typeof v === 'string' ? v : undefined;\n }\n}\n\n/**\n * 403 — a `uses`-plane operation that isn't in the binding's enforced\n * operation set (the manifest's declared `ops` INTERSECT the admin's\n * `consented_ops` at bind time). E.g. the manifest declares\n * `ops: ['read','update']` but the admin consented only to `read` when\n * binding, so an `update` is rejected.\n *\n * Fix: either narrow the call to a consented op, or widen the manifest\n * `ops` + have an admin re-bind (which refreshes `consented_ops`).\n *\n * `err.op` / `err.usesSlug` read the backend's `context: { uses_slug, op }`.\n */\nexport class UsesOpNotAllowedError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.USES_OP_NOT_ALLOWED, message, {\n status: 403,\n context,\n requestId,\n });\n }\n\n /** The `uses` slot slug the rejected op targeted. Read from `context.uses_slug`. */\n get usesSlug(): string | undefined {\n const v = this.context?.uses_slug;\n return typeof v === 'string' ? v : undefined;\n }\n\n /**\n * The operation that was rejected — one of `create` / `read` /\n * `update` / `delete`. Read from `context.op`; `undefined` if omitted.\n */\n get op(): string | undefined {\n const v = this.context?.op;\n return typeof v === 'string' ? v : undefined;\n }\n}\n\n/**\n * 409 — a field the `uses` binding's `field_map` points at no longer\n * exists on the physical workspace table (schema drift: the table owner\n * deleted or renamed the column after the bind). The backend best-effort\n * parks the binding (reason `TABLE_DELETED`/field-drift) on detection.\n *\n * Recovery is the workspace admin's: re-bind the slot to a table that\n * still has the field, or re-map the field (`dashwise bind <uses_slug>\n * --map <usesField>=<physicalField>`).\n *\n * `err.field` / `err.usesSlug` read the backend's\n * `context: { uses_slug, field }`.\n */\nexport class ContractFieldMissingError extends DashwiseError {\n constructor(\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(DashwiseErrorCode.CONTRACT_FIELD_MISSING, message, {\n status: 409,\n context,\n requestId,\n });\n }\n\n /** The `uses` slot slug whose contract broke. Read from `context.uses_slug`. */\n get usesSlug(): string | undefined {\n const v = this.context?.uses_slug;\n return typeof v === 'string' ? v : undefined;\n }\n\n /**\n * The declared field slug (the `uses` side name) whose mapped physical\n * field is gone. Read from `context.field`; `undefined` if omitted.\n */\n get field(): string | undefined {\n const v = this.context?.field;\n return typeof v === 'string' ? v : undefined;\n }\n}\n\n// ── Join errors (Phase 3) ──────────────────────────────────────────────\n//\n// Surfaced when `.join('<slug>')` / `.leftJoin('<slug>')` on the\n// generated `qb` builder can't resolve the target against the\n// manifest. Pre-declared in slice 3.1; raised by the backend\n// translator in slice 3.2. Same one-subclass-with-preserved-code\n// pattern as DependencyContractError so authors can switch on\n// `err.code` for the three sub-cases:\n//\n// LINK_FIELD_NOT_FOUND `.join('xyz')` where `xyz` isn't a field\n// on the source table. Fix: pass an\n// explicit `on` argument, or correct the slug.\n//\n// NOT_A_LINK_FIELD `.join('title')` where `title` exists on\n// the source table but its type isn't\n// `link_row`. Fix: use a real link field,\n// or pass explicit `on`.\n//\n// JOIN_NOT_LINKED `.join('sibling_table')` where the target\n// is a sibling table but no link field\n// bridges them. Fix: pass explicit `on`.\n\n/**\n * 400 — `.join()` / `.leftJoin()` referenced a target the manifest\n * can't resolve. Covers the three sub-cases above; inspect\n * `err.code` to distinguish.\n *\n * Author-side fix in every case: either correct the slug, or pass\n * an explicit `on` argument to bypass link-field inference.\n */\nexport class JoinError extends DashwiseError {\n constructor(\n code: string,\n message: string,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(code, message, { status: 400, context, requestId });\n }\n}\n\n// ── Cross-module action errors (Phase 7 slice 7.3a, 2026-05-20) ──────\n//\n// Surfaced by the codegen-emitted `qb.deps.<dep>.actions.<name>(input)`\n// factory when the backend's cross-module dispatch\n// (`CrossModuleActionService.dispatch()`) rejects. Six sub-cases all\n// covered by `DepActionError`; inspect `err.code` to discriminate.\n//\n// ACTION_NOT_EXPOSED 403 — provider's manifest doesn't expose\n// the action, OR visibility='private'.\n// Recovery: provider author needs to\n// publish a version that exposes it.\n// ACTION_NOT_DECLARED 403 — consumer's manifest doesn't list\n// the action in dependencies[].actions[].\n// Recovery: add it + republish.\n// ACTION_INPUT_INVALID 400 — input fails Ajv against the\n// provider's declared input schema.\n// Recovery: fix the call site (typical\n// with stale codegen + drift).\n// ACTION_OUTPUT_INVALID 500 — provider returned a value that fails\n// the declared output schema. This is a\n// PROVIDER-author bug; consumer can\n// only file an issue.\n// ACTION_RATE_LIMITED 429 — per-actor or per-caller-installation\n// rate limit exceeded. Retry with\n// backoff after Retry-After (slice 7.4).\n// ACTION_EXECUTION_FAILED 200-envelope — action body threw, timed out,\n// or OOM-ed. Provider-author bug;\n// trace surfaced in err.context.trace.\n\n/**\n * Single subclass for every cross-module action failure. The code\n * (and HTTP status) varies per sub-case; the wrapping class lets\n * consumers `catch (e) { if (e instanceof DepActionError) {...} }`\n * regardless of which sub-case fired.\n */\nexport class DepActionError extends DashwiseError {\n constructor(\n code: string,\n message: string,\n status: number,\n context?: Record<string, unknown>,\n requestId: string | null = null,\n ) {\n super(code, message, {\n status,\n retriable: code === DashwiseErrorCode.ACTION_RATE_LIMITED,\n context,\n requestId,\n });\n }\n}\n\n// ── Factory: backend envelope → typed error ────────────────────────────\n\ninterface BackendErrorEnvelope {\n code?: string;\n message?: string;\n retriable?: boolean;\n context?: Record<string, unknown>;\n}\n\n/**\n * Build a typed `DashwiseError` from the backend's response envelope.\n *\n * The mapping picks the most specific subclass for codes we have a class\n * for; everything else becomes a plain `DashwiseError` carrying the raw\n * code. This keeps the type set small while still letting consumers\n * branch on string codes when they want to.\n *\n * Phase 6 slice 6.2 (2026-05-19): accepts an optional `requestId` —\n * the backend's `x-request-id` response header — and threads it onto\n * every produced error so consumers can use it as a debugging trace\n * anchor. Default `null` for backward compat; the transport always\n * passes it through.\n */\nexport function fromBackendEnvelope(\n status: number,\n envelope: BackendErrorEnvelope | null,\n requestId: string | null = null,\n): DashwiseError {\n const code = envelope?.code ?? codeFromStatus(status);\n const message = envelope?.message ?? `HTTP ${status}`;\n const context = envelope?.context;\n\n switch (code) {\n case DashwiseErrorCode.UNAUTHENTICATED:\n case DashwiseErrorCode.TOKEN_EXPIRED:\n return new UnauthenticatedError(message, requestId);\n case DashwiseErrorCode.FORBIDDEN:\n return new ForbiddenError(message, context, requestId);\n case DashwiseErrorCode.ROW_NOT_FOUND:\n return new RowNotFoundError(message, context, requestId);\n case DashwiseErrorCode.CONTRACT_VIOLATION:\n case DashwiseErrorCode.FIELD_NOT_READABLE:\n case DashwiseErrorCode.FILTER_OP_UNKNOWN:\n case DashwiseErrorCode.OPERATION_NOT_ALLOWED:\n return new ContractViolationError(message, context, requestId);\n case DashwiseErrorCode.VALIDATION_FAILED:\n return new ValidationError(message, context, requestId);\n case DashwiseErrorCode.DEPENDENCY_NOT_DECLARED:\n case DashwiseErrorCode.TABLE_NOT_IN_CONTRACT:\n return new DependencyContractError(code, message, context, requestId);\n case DashwiseErrorCode.PROVIDER_TABLE_MISSING:\n return new ProviderTableMissingError(message, context, requestId);\n case DashwiseErrorCode.OPERATION_NOT_ALLOWED_BY_PROVIDER:\n return new OperationNotAllowedByProviderError(message, context, requestId);\n case DashwiseErrorCode.DEP_UNBOUND:\n return new DepUnboundError(message, context, requestId);\n case DashwiseErrorCode.USES_UNBOUND:\n return new UsesUnboundError(message, context, requestId);\n case DashwiseErrorCode.USES_OP_NOT_ALLOWED:\n return new UsesOpNotAllowedError(message, context, requestId);\n case DashwiseErrorCode.CONTRACT_FIELD_MISSING:\n return new ContractFieldMissingError(message, context, requestId);\n case DashwiseErrorCode.LINK_FIELD_NOT_FOUND:\n case DashwiseErrorCode.NOT_A_LINK_FIELD:\n case DashwiseErrorCode.JOIN_NOT_LINKED:\n // `uses`-plane joins are unsupported in v1; the backend emits a 400\n // with this code. Map to JoinError so it lands in the same\n // \"your join can't be resolved\" family (code preserved for `err.code`).\n case DashwiseErrorCode.USES_JOIN_UNSUPPORTED:\n return new JoinError(code, message, context, requestId);\n case DashwiseErrorCode.ACTION_NOT_EXPOSED:\n case DashwiseErrorCode.ACTION_NOT_DECLARED:\n return new DepActionError(code, message, 403, context, requestId);\n case DashwiseErrorCode.ACTION_INPUT_INVALID:\n return new DepActionError(code, message, 400, context, requestId);\n case DashwiseErrorCode.ACTION_OUTPUT_INVALID:\n return new DepActionError(code, message, 500, context, requestId);\n case DashwiseErrorCode.ACTION_RATE_LIMITED:\n return new DepActionError(code, message, 429, context, requestId);\n case DashwiseErrorCode.ACTION_EXECUTION_FAILED:\n // ACTION_EXECUTION_FAILED arrives as a 200 envelope from the\n // backend (the host succeeded; the action body crashed). The\n // _callAction helper unwraps the envelope and synthesizes this\n // via the factory call — status 200 reflects \"the transport was\n // fine; the failure is in the application layer\".\n return new DepActionError(code, message, 200, context, requestId);\n default:\n return new DashwiseError(code, message, {\n status,\n retriable: envelope?.retriable ?? false,\n context,\n requestId,\n });\n }\n}\n\nfunction codeFromStatus(status: number): string {\n if (status === 401) return DashwiseErrorCode.UNAUTHENTICATED;\n if (status === 403) return DashwiseErrorCode.FORBIDDEN;\n if (status === 404) return DashwiseErrorCode.ROW_NOT_FOUND;\n if (status >= 500) return DashwiseErrorCode.INTERNAL_ERROR;\n return 'HTTP_ERROR';\n}\n","/**\n * Wire-format Query AST — the JSON shape the SDK serializes and the\n * backend's query plane consumes.\n *\n * The AST is a **public, versioned** contract. Bumping its shape in a\n * way that can't be transparently re-mapped on the backend is a\n * SemVer-significant change for `@dashai/sdk`. The `v` field at the\n * root is the single discriminator the backend uses to decide whether\n * it can handle a given query.\n *\n * Coverage by phase (per `dashwise-devops-docs/plans/modules-sdk-query-v1.md`):\n *\n * - **Phase 2** (this file, today): `v: 1`, `from: { kind: 'owned' }`.\n * Bare table reads. Subsequent slices add `where`, `select`,\n * `orderBy`, `limit`, `offset`, `count`, then aggregations.\n * - **Phase 3**: `joins[]`, `from: { kind: 'view' }`.\n * - **Phase 4**: `from: { kind: 'dep' }` for cross-module reads,\n * `joins[].to` for cross-module joins via the planner.\n *\n * The shape below carries all the optional fields the v1 plan promises\n * (commented per-field with the phase they activate in). Today's\n * translator only honors `v` + `from`; everything else is parsed +\n * rejected with `QUERY_TOO_COMPLEX` until the corresponding phase\n * lands. Pre-declaring the shape lets the SDK builder stub out the\n * fluent API today without forcing an AST version bump every time we\n * activate a field.\n */\n\n// ── Top-level AST ────────────────────────────────────────────────────\n\n/**\n * Schema version. Bumped only on breaking changes. The backend\n * supports `v` and `v+1` during deprecation windows so consumers can\n * upgrade independently of the server. Add new optional fields under\n * the current `v`; that's additive and non-breaking.\n */\nexport type QueryAstVersion = 1;\n\n/**\n * The root AST node. Carries the version discriminator + the table\n * reference + every optional clause.\n *\n * Designed so the backend can switch on `v` first, then validate the\n * rest. Unknown root keys are NOT silently dropped — the validator\n * rejects with `VALIDATION_FAILED` so typos surface loudly.\n */\nexport interface QueryAst {\n /** Schema discriminator. See {@link QueryAstVersion}. */\n v: QueryAstVersion;\n\n /** The primary table for this query. */\n from: TableRef;\n\n /**\n * Optional joins (Phase 3+). When present + the planner can't handle\n * the join (e.g. multi-module before Phase 4), the backend rejects\n * with `QUERY_TOO_COMPLEX`.\n */\n joins?: JoinNode[];\n\n /**\n * Where clause (Phase 2 slice 2). Mirrors the existing `Where<Row>`\n * shape from `@dashai/sdk` (`./data/types.ts`) — recursive AND/OR\n * groups + per-field operator objects. Field references inside `where`\n * can be either bare (`'done'`, resolved against `from`) or qualified\n * (`'tasks.done'`) when joins are present.\n */\n where?: WhereClause;\n\n /**\n * Group-by fields for aggregation queries (Phase 3). Field refs follow\n * the same bare-or-qualified rule as `where`.\n */\n groupBy?: FieldRef[];\n\n /**\n * Aggregations keyed by output column name. Phase 3.\n *\n * { totalDone: { count: '*' }, oldest: { min: 'created_at' } }\n */\n agg?: Record<string, AggExpr>;\n\n /**\n * Columns to select. Default = all fields of `from`. Supports `'*'`\n * shorthand + `'as'` aliases (`'users.email as assignee_email'`).\n * Phase 2 slice 2.\n */\n select?: SelectExpr[];\n\n /** Order-by clauses (Phase 2 slice 2). */\n orderBy?: OrderClause[];\n\n /** Max rows to return. Default 50 (matches owned-table CRUD). */\n limit?: number;\n\n /** Row offset for pagination. Default 0. */\n offset?: number;\n\n /**\n * Phase 3 slice 3.5 — when joins are present, return rows in\n * SQL-style flat shape (`{ 'users.email': value }`) instead of\n * the nested default (`row.users.email`). Ignored without joins.\n *\n * SDK ships the flag in slice 3.3 so authors can write\n * `.flatten()` today; backend wiring lands in slice 3.5. Until\n * then the backend silently ignores it (defaults to nested).\n */\n flatten?: boolean;\n}\n\n// ── Table references ─────────────────────────────────────────────────\n\n/**\n * Discriminated union of every table source. Adding a new kind is\n * additive (consumers using `switch (ref.kind)` must `default` to\n * reject — covered by lint).\n *\n * - `owned`: a table from this module's manifest `owns.tables[]`.\n * - `dep`: a table from another module borrowed via `dependencies[]`\n * (Phase 4 — not yet routable, validator rejects).\n * - `view`: a manifest-declared view (Phase 5 — not yet routable).\n */\nexport type TableRef =\n | { kind: 'owned'; slug: string }\n | { kind: 'dep'; module: string; slug: string }\n | { kind: 'view'; slug: string };\n\n// ── Join nodes ───────────────────────────────────────────────────────\n\n/**\n * Join node — Phase 3+. Includes `on` as an explicit field-pair map\n * because link-field-derived defaults can't be inferred from the AST\n * alone (they live on the manifest). The SDK builder may compute `on`\n * from a link field at compile time and inline it here.\n */\nexport interface JoinNode {\n kind: 'inner' | 'left';\n to: TableRef;\n /**\n * Map of left-side qualified field ref → right-side qualified field\n * ref. E.g. `{ 'tasks.assignee_id': 'users.id' }`.\n */\n on: Record<string, string>;\n /**\n * Alias on result rows. Defaults to `to.slug` (or the link-field\n * slug when the SDK's `.join('<link-slug>')` resolves through a\n * link field). Reserved for multi-join-to-same-target (out of\n * scope for slice 3.2; field is parsed but unused today).\n */\n as?: string;\n}\n\n// ── Where clause ─────────────────────────────────────────────────────\n\n/**\n * Where clause — same shape as the existing `Where<Row>` type but\n * untyped here (the AST is the wire format; the typed builder layer\n * provides per-field narrowing). Top-level keys AND together; `AND`\n * and `OR` arrays group explicitly.\n *\n * Examples:\n * { done: false } // shorthand for { equal: false }\n * { title: { contains: 'urgent' } } // operator object\n * { AND: [{ done: false }, { ... }] } // explicit AND group\n * { OR: [{ priority: 'high' }, ...] } // OR group\n * { 'tasks.done': false } // qualified field ref (after joins)\n */\nexport interface WhereClause {\n AND?: WhereClause[];\n OR?: WhereClause[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [field: string]: any;\n}\n\n// ── Field references ─────────────────────────────────────────────────\n\n/**\n * Field reference — either bare (`'done'`) or qualified\n * (`'tasks.done'`). Qualified refs are only valid when the AST has\n * joins; the validator enforces this.\n */\nexport type FieldRef = string;\n\n// ── Select expressions ───────────────────────────────────────────────\n\n/**\n * Select expression — a field ref, the `'*'` shorthand, or a string\n * with an `'as'` alias.\n *\n * 'id'\n * '*'\n * 'tasks.title'\n * 'users.email as assignee_email'\n *\n * Strict mode (Phase 2 OD2 from the plan): `'*'` selects all fields\n * from `from`, NOT all fields from every joined table. Authors must\n * explicitly opt into joined fields with `'<table>.<field>'`.\n */\nexport type SelectExpr = string;\n\n// ── Order-by ─────────────────────────────────────────────────────────\n\nexport interface OrderClause {\n field: FieldRef;\n dir: 'asc' | 'desc';\n}\n\n// ── Aggregations ─────────────────────────────────────────────────────\n\n/**\n * Aggregation expression. Each function takes either `'*'` (count\n * only) or a specific field ref.\n *\n * { count: '*' }\n * { sum: 'amount' }\n * { avg: 'amount' }\n * { min: 'created_at' }\n * { max: 'updated_at' }\n */\nexport type AggExpr =\n | { count: '*' | FieldRef }\n | { sum: FieldRef }\n | { avg: FieldRef }\n | { min: FieldRef }\n | { max: FieldRef };\n\n// ── Type-narrowing guards ────────────────────────────────────────────\n\n/**\n * Runtime check that an unknown value matches the AST shape at the\n * structural level (not field-level — that's the backend validator's\n * job). Useful for SDK-side fail-fast checks before serializing.\n *\n * Intentionally permissive about optional fields: the AST grew over\n * phases, and older consumers might lack `joins` / `agg` / etc.\n * Strict checks happen at the backend.\n */\nexport function isQueryAst(v: unknown): v is QueryAst {\n if (!v || typeof v !== 'object') return false;\n const a = v as Record<string, unknown>;\n if (a.v !== 1) return false;\n if (!a.from || typeof a.from !== 'object') return false;\n const f = a.from as Record<string, unknown>;\n if (typeof f.kind !== 'string') return false;\n if (f.kind === 'owned' || f.kind === 'view') {\n return typeof f.slug === 'string';\n }\n if (f.kind === 'dep') {\n return typeof (f as { module?: unknown }).module === 'string' && typeof f.slug === 'string';\n }\n return false;\n}\n\n/**\n * Convenience: the current schema version as a literal. Useful when\n * constructing ASTs programmatically — keeps the magic number out of\n * the call site.\n */\nexport const CURRENT_QUERY_AST_VERSION: QueryAstVersion = 1;\n","/**\n * Typed query builder — fluent API that compiles to a {@link QueryAst}\n * and posts it to `POST /api/module-api/query`.\n *\n * Phase 2 slice 1 (this file): the minimum E2E proof — `qb.from(slug).execute()`\n * round-trips through the backend. Future slices extend with\n * `.where`, `.select`, `.orderBy`, `.limit`, `.offset`, `.count`, then\n * aggregations + joins. The builder's shape is designed to fit those\n * additions without renaming or breaking anything we ship today.\n *\n * Composability: every chainable method returns a new immutable\n * `Query<Row>` — never mutates `this`. That lets authors derive\n * sub-queries from a base:\n *\n * ```ts\n * const base = qb.from('tasks');\n * const all = await base.execute();\n * // (future: const overdue = await base.where({ done: false }).execute())\n * ```\n *\n * The factory accepts a {@link Client} (not the raw transport) so the\n * public surface stays small + the same builder works whether the\n * client was constructed with `createClient` or a test stub that\n * implements the {@link Client} interface.\n *\n * Spec: `dashwise-devops-docs/plans/modules-sdk-query-v1.md` §6.3.\n */\n\nimport { fromBackendEnvelope } from '../data/errors.js';\nimport type { BaseRow, Client, Page, Where } from '../data/types.js';\nimport {\n CURRENT_QUERY_AST_VERSION,\n type AggExpr,\n type FieldRef,\n type OrderClause,\n type QueryAst,\n type TableRef,\n type WhereClause,\n} from './ast.js';\n\n// ── Join options + result envelope (Phase 3) ──────────────────────────\n\n/**\n * Options passed to {@link Query.join} / {@link Query.leftJoin}.\n *\n * `on` is REQUIRED in slice 3.3 — link-field-derived inference lands\n * in a follow-up. Both sides of each pair must be qualified\n * (`<table>.<field>`) so the SDK + backend can disambiguate after\n * joins.\n *\n * `as` is reserved for explicit aliasing when joining the same\n * target multiple times (rare; not in v1).\n */\nexport interface JoinOpts<K extends 'inner' | 'left'> {\n on: Record<string, string>;\n /** Default 'inner' for `.join()`; `.leftJoin()` hardcodes 'left'. */\n kind?: K;\n as?: string;\n}\n\n// ── Aggregation result envelope ──────────────────────────────────────\n\n/**\n * Result envelope for aggregation queries — distinct from\n * {@link Page} because aggregation rows have a different shape (one\n * row per group, with output keys matching the `agg` map keys + the\n * `groupBy` field names) and don't carry pagination metadata in the\n * same way.\n *\n * `rows` shape: `Record<string, unknown>` because the keys are\n * dynamic (mix of group-by field names + aggregation output keys);\n * codegen will later emit a per-query typed shape, but the runtime\n * client stays untyped at the value level.\n *\n * `total` = number of result rows (one per group).\n */\nexport interface QueryResult {\n rows: Array<Record<string, unknown>>;\n total: number;\n}\n\n/**\n * Result envelope for `.explain()` (Phase 5, 2026-05-19). Returned by\n * `POST /api/module-api/query/explain`. The backend runs\n * Postgres `EXPLAIN (FORMAT JSON)` against the SQL it would emit —\n * WITHOUT executing it (no `ANALYZE`, no rows materialized).\n *\n * Fields:\n * - `plan` — the full Postgres plan JSON tree (opaque object;\n * `plan.Plan` is the root node, `plan.Plan.Plans[]` its children).\n * Useful for visualization tools that want the original tree\n * structure preserved.\n * - `nodes` — depth-first flattened array of plan nodes. Each entry\n * carries `Node Type`, `Total Cost`, `Plan Rows`, and a `depth`\n * annotation (root is depth 0; children depth 1; etc.). Easier to\n * render in a flat table than the recursive tree.\n * - `estimatedCost` — `Total Cost` of the root plan node. Postgres'\n * planner-side cost estimate (not actual runtime). Sum of startup +\n * execution cost in arbitrary cost units.\n * - `estimatedRows` — `Plan Rows` of the root plan node. Planner's\n * estimate of rows the query would produce. May differ from actual\n * once table stats are stale (run `ANALYZE` on the underlying\n * tables to refresh).\n * - `sql` — the generated SQL string the query plane would execute.\n * Surfaced for debugging — useful when the plan doesn't match what\n * you expected and you want to see what was actually compiled.\n * - `paramsCount` — number of bound parameters in the SQL. Cross-check\n * against the WHERE / ON clauses in your AST.\n *\n * MVP support (backend slice 5 `.explain()` MVP):\n * - ✅ Joined queries (any `.join()` / `.leftJoin()` call)\n * - ✅ Joined aggregations (joins + `.agg()` / `.groupBy()`)\n * - ⏳ Bare own-table / bare cross-module dep / views / own-table agg\n * — reject with `EXPLAIN_NOT_SUPPORTED_YET` (typed code; the\n * thrown error carries `context: { fromKind, hasJoins, hasAgg }`\n * so callers can detect + fall back to `.execute()` for the\n * unsupported shapes).\n */\nexport interface ExplainResult {\n plan: unknown;\n nodes: Array<{\n ['Node Type']: string;\n ['Total Cost']?: number;\n ['Plan Rows']?: number;\n depth: number;\n [key: string]: unknown;\n }>;\n estimatedCost: number;\n estimatedRows: number;\n sql: string;\n paramsCount: number;\n}\n\n// ── Slug validation (mirrored from data/client.ts) ───────────────────\n\n/**\n * Owned-table slug pattern. Mirrors the backend manifest validator's\n * `SLUG_REGEX` exactly (snake_case, 2-63 chars). Mirrored here, not\n * imported from the backend, because the SDK is published independently.\n */\nconst TABLE_SLUG_REGEX = /^[a-z][a-z0-9_]*[a-z0-9]$/;\n\n/**\n * Module-slug character set — kebab-case, 2-63 chars, starts with a\n * letter, ends with letter/digit. Mirror of `MODULE_SLUG_REGEX` in\n * `@dashai/sdk/deps` and the backend's manifest validator. Same\n * mirror-vs-import tradeoff: the SDK is published independently.\n */\nconst MODULE_SLUG_REGEX = /^[a-z][a-z0-9-]*[a-z0-9]$/;\n\nfunction assertValidTableSlug(slug: string): void {\n if (typeof slug !== 'string' || !TABLE_SLUG_REGEX.test(slug)) {\n throw new Error(\n `@dashai/sdk/query: invalid table slug \"${slug}\". Slugs must match /^[a-z][a-z0-9_]*[a-z0-9]$/ (snake_case).`,\n );\n }\n}\n\nfunction assertValidModuleSlug(slug: string): void {\n if (typeof slug !== 'string' || !MODULE_SLUG_REGEX.test(slug)) {\n throw new Error(\n `@dashai/sdk/query: invalid module slug \"${slug}\". Module slugs must match /^[a-z][a-z0-9-]*[a-z0-9]$/ (kebab-case).`,\n );\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────\n\n/**\n * Reference to a dependency module's table. Lands with Phase 4 slice\n * 4.1b (the SDK side of the cross-module query plane). The backend\n * routes these through {@link DepsRuntimeService.listRowsForSdk} which\n * enforces the consumer's declared-dep contract + field projection\n * server-side.\n *\n * Authors typically reach this through the codegen-emitted\n * `qb.from(deps.crm.contacts)` shape, but the literal form\n * `qb.from({ kind: 'dep', module: 'crm', slug: 'contacts' })` is also\n * supported for tests + AI-builder authoring.\n */\nexport interface QbDepRef {\n kind: 'dep';\n module: string;\n slug: string;\n}\n\n/**\n * Reference to a view declared in the module's `manifest.owns.views[]`.\n * Lands with Phase 5 slice 5.4b (the SDK side of view querying). The\n * backend's `executeView` resolves the view at runtime — AND-merges\n * the view's static WHERE with the caller's, applies the view's\n * default orderBy when caller omits one, and recurses through\n * `execute()` with a rewritten own-table AST.\n *\n * Authors typically reach this through the codegen-emitted\n * `qb.from(qbViews.active_tasks.ref)` shape (or the more ergonomic\n * `qbViews.active_tasks.where(...).execute()` directly), but the\n * literal form `qb.from({ kind: 'view', slug: 'active_tasks' })`\n * is also supported for tests + AI-builder authoring.\n */\nexport interface QbViewRef {\n kind: 'view';\n slug: string;\n}\n\n/**\n * The factory's return value. Wraps a {@link Client} + exposes a\n * `.from()` method that binds a table source.\n *\n * `from()` accepts three shapes:\n * - a string — own-table slug (default, since Phase 2 slice 1)\n * - a {@link QbDepRef} — cross-module dep ref (Phase 4 slice 4.1b)\n * - a {@link QbViewRef} — view ref (Phase 5 slice 5.4b)\n *\n * A `.raw()` escape hatch is intentionally NOT in v1 per the plan.\n */\nexport interface Qb {\n /** Bind an owned-table source. Returns a chainable {@link Query}. */\n from<Row extends BaseRow = BaseRow>(tableSlug: string): Query<Row>;\n /** Bind a cross-module dep table source. Returns a chainable {@link Query}. */\n from<Row extends BaseRow = BaseRow>(ref: QbDepRef): Query<Row>;\n /** Bind a view source (declared in `manifest.owns.views[]`). */\n from<Row extends BaseRow = BaseRow>(ref: QbViewRef): Query<Row>;\n}\n\n/**\n * Chainable per-query builder. Each instance is **immutable** — every\n * modifier returns a fresh `Query<Row>` with the updated AST.\n *\n * Today's slice exposes `.toAst()` (introspection), `.execute()`\n * (run + return rows), plus four chainable modifiers (`.where`,\n * `.orderBy`, `.limit`, `.offset`). Subsequent slices add `.select`,\n * `.count`, `.groupBy()`/`.agg()`, then `.join()` / `.leftJoin()`.\n *\n * Each chainable method returns a new `Query<Row>` rather than `this`\n * so authors can derive sub-queries from a common base:\n *\n * ```ts\n * const base = qb.from<TaskRow>('tasks');\n * const open = await base.where({ done: false }).execute();\n * const overdue = await base\n * .where({ done: false, due_date: { date_before: today } })\n * .execute();\n * ```\n *\n * The first `base.execute()` returns ALL tasks (no filter); the\n * `where()` calls produce fresh builders that don't mutate `base`.\n */\nexport interface Query<Row extends BaseRow = BaseRow> {\n /**\n * Return the underlying AST. Useful for tests, debugging, future\n * `.explain()` integration. The returned object is a fresh copy —\n * mutating it does NOT affect the builder.\n */\n toAst(): QueryAst;\n\n /**\n * Execute the query. Posts the AST to the backend's\n * `POST /api/module-api/query` endpoint and resolves\n * with the standard {@link Page} envelope. Rejects with a\n * {@link DashwiseError} on backend errors (`QUERY_AST_VERSION`,\n * `QUERY_TOO_COMPLEX`, etc.).\n */\n execute(): Promise<Page<Row>>;\n\n /**\n * Phase 6 streaming (2026-05-19): stream query results row-by-row\n * via Server-Sent Events. Returns an `AsyncIterable<Row>` — use\n * `for await (const row of q.stream())` to consume.\n *\n * Backend endpoint: `POST /api/module-api/query/stream`.\n * Each row arrives as one SSE `data:` event; a terminal `complete`\n * event closes the iterator cleanly. Execution-phase backend\n * errors (RBAC, RESULT_TOO_LARGE, etc.) arrive as in-band `error`\n * events and are rethrown as `DashwiseError` from the iterator.\n * Validation-phase errors (malformed AST) come back as standard\n * HTTP 400 BEFORE the stream opens, so they throw immediately\n * when you start iterating (before the first row).\n *\n * **AbortSignal**: pass `opts.signal` to cancel the stream. When\n * the signal fires:\n * - The underlying `fetch()` is cancelled (network read aborts)\n * - The iterator throws an `AbortError` on the next `next()` call\n * - The backend's SSE writer eventually notices the broken\n * pipe and stops emitting events (no leak)\n *\n * **Backpressure**: the iterator is pull-based. Rows are buffered\n * in the SSE chunk decoder until the consumer requests them via\n * the for-await loop. Slow consumers naturally backpressure the\n * network read.\n *\n * **MVP limitation**: in v1 the BACKEND buffers the full result\n * before streaming (true row-by-row Postgres cursoring is a future\n * perf slice). The SDK consumer still gets the streaming UX — rows\n * arrive incrementally on the wire. Once the backend perf slice\n * lands, both ends become streaming end-to-end with no SDK change.\n *\n * @example Basic iteration\n * for await (const task of qb.from<TaskRow>('tasks').stream()) {\n * process(task);\n * }\n *\n * @example Abort midway via AbortController\n * const controller = new AbortController();\n * setTimeout(() => controller.abort(), 5000);\n * try {\n * for await (const row of qb.from('tasks').stream({ signal: controller.signal })) {\n * // ... rows arrive until 5s timeout\n * }\n * } catch (e) {\n * if (e.name === 'AbortError') console.log('Cancelled');\n * }\n */\n stream(opts?: { signal?: AbortSignal }): AsyncIterable<Row>;\n\n /**\n * Return the Postgres execution plan for this query — WITHOUT\n * executing it (Phase 5, 2026-05-19). Posts the AST to\n * `POST /api/module-api/query/explain`; backend wraps the\n * SQL in `EXPLAIN (FORMAT JSON)` and returns the parsed plan tree.\n *\n * **MVP support matrix** (backend slice 5):\n * - ✅ Joined queries — works (`.join()` / `.leftJoin()`)\n * - ✅ Joined aggregations — works (`.join()...agg()`)\n * - ⏳ Bare own-table / bare dep / view / bare-agg → rejects with\n * `EXPLAIN_NOT_SUPPORTED_YET` (typed code; the error's `context`\n * payload lets you fall back to `.execute()` for unsupported\n * shapes)\n *\n * @example Joined query EXPLAIN (works today)\n * const plan = await qb.from<TaskRow>('tasks')\n * .leftJoin<UserRow>('users', { on: { 'tasks.assignee_id': 'users.id' } })\n * .where({ done: false })\n * .explain();\n * console.log('Cost:', plan.estimatedCost);\n * for (const node of plan.nodes) {\n * console.log(' '.repeat(node.depth) + node['Node Type']);\n * }\n *\n * @example Bare query EXPLAIN (currently throws)\n * try {\n * await qb.from<TaskRow>('tasks').explain();\n * } catch (e) {\n * // e.code === 'EXPLAIN_NOT_SUPPORTED_YET'\n * // Fall back to .execute() — bare-path support lands in a follow-up\n * }\n */\n explain(): Promise<ExplainResult>;\n\n /**\n * Add or replace the where clause. Same recursive `Where<Row>`\n * shape as `client.db().list({ filter })` — top-level keys AND\n * together; explicit `AND` / `OR` arrays group:\n *\n * ```ts\n * qb.from<TaskRow>('tasks').where({ done: false })\n * qb.from<TaskRow>('tasks').where({ title: { contains: 'urgent' } })\n * qb.from<TaskRow>('tasks').where({\n * OR: [{ priority: 'high' }, { due_date: { date_before: today } }]\n * })\n * ```\n *\n * Calling `.where()` twice REPLACES the prior clause (not merge) —\n * authors who want incremental composition should build their\n * where-clause object explicitly. Replace-vs-merge is the same\n * semantic as `client.db().list({ filter: ... })`.\n */\n where(clause: Where<Row>): Query<Row>;\n\n /**\n * Add or replace the order-by clauses. Array of `{ field, dir }`\n * entries; sorts apply in array order (primary first).\n *\n * ```ts\n * qb.from<TaskRow>('tasks').orderBy([\n * { field: 'priority', dir: 'desc' },\n * { field: 'due_date', dir: 'asc' },\n * ])\n * ```\n */\n orderBy(clauses: ReadonlyArray<OrderClause>): Query<Row>;\n\n /**\n * Set the maximum number of rows to return. Server defaults to 50\n * when omitted; max 1000 (`RESULT_TOO_LARGE` for higher values).\n */\n limit(n: number): Query<Row>;\n\n /**\n * Set the row offset for pagination. Defaults to 0.\n */\n offset(n: number): Query<Row>;\n\n /**\n * Convenience: execute the query and return only the total row\n * count (after `where`, before `limit`/`offset`).\n *\n * Equivalent to `(await this.limit(1).execute()).total` but with\n * a clearer call site. The backend's existing list endpoint\n * already computes `total` regardless of `limit`, so this hits\n * the same path as `.execute()` and just extracts the number —\n * no separate `COUNT(*)` round-trip.\n *\n * @example\n * const open = await qb.from<TaskRow>('tasks')\n * .where({ done: false })\n * .count();\n */\n count(): Promise<number>;\n\n /**\n * Add a same-module join (Phase 3 slice 3.3, 2026-05-19). Either\n * INNER (when `opts.kind` is omitted or 'inner') or LEFT (use\n * `.leftJoin()` for the read-friendly alias).\n *\n * `opts.on` is REQUIRED in this slice — link-field-derived `on`\n * inference lands in a follow-up. Pass qualified refs on both\n * sides: `{ 'tasks.assignee_id': 'users.id' }`.\n *\n * Returns a {@link JoinedQuery} — the surface omits write methods\n * (`.create / .update / .delete`) + `.count` / `.agg` / `.groupBy`\n * since the query plane's join path is read-only and not yet\n * aggregation-aware (slice 3.2b-1 limits).\n *\n * Today the validator gates joins+filters+ordering as `QUERY_TOO_COMPLEX`\n * pending slices 3.2b-2 / b-3. The SDK lets you write the call\n * anyway so author muscle memory transfers; the backend will\n * surface a clear \"lands in slice X\" error if combined too early.\n *\n * @example Own-table join\n * await qb.tasks\n * .join<UsersRow>('users', { on: { 'tasks.assignee_id': 'users.id' } })\n * .execute();\n *\n * @example Cross-module join (Phase 4 slice 4.2)\n * await qb.tasks\n * .leftJoin<ContactRow>(\n * { kind: 'dep', module: 'crm', slug: 'contacts' },\n * { on: { 'tasks.contact_id': 'contacts.id' } },\n * )\n * .execute();\n */\n join<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n ): JoinedQuery<Row & Record<string, T>>;\n\n /**\n * Add a LEFT JOIN. Convenience for `.join(target, { kind: 'left', on })`.\n * The joined object on result rows is `T | null` (matches LEFT JOIN's\n * unmatched-row semantics).\n *\n * Phase 4 slice 4.2: accepts {@link QbDepRef} as the target for\n * cross-module joins. The provider's `exposes[].operations.list`\n * must be enabled and the consumer must declare the dep table in\n * `dependencies[].reads[]` — same contract enforcement as the bare\n * dep list path.\n *\n * @example\n * await qb.tasks\n * .leftJoin<UsersRow>('users', { on: { 'tasks.assignee_id': 'users.id' } })\n * .execute();\n */\n leftJoin<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'left'>,\n ): JoinedQuery<Row & Record<string, T | null>>;\n\n /**\n * Narrow the response to only the listed field slugs. The manifest's\n * policy projection is the security floor — `.select()` can narrow\n * further but never widens. Slugs not in the projection (because the\n * manifest hides them, or the slug is a typo) reject server-side with\n * `FIELD_NOT_READABLE`.\n *\n * Pass `['*']` (or omit `.select()` entirely) for the full projection.\n *\n * **Type narrowing not yet implemented:** the returned Query is still\n * typed as `Query<Row>` (full row type). Authors who want narrowed\n * types cast at the call site:\n * ```ts\n * const result = await qb.tasks\n * .select(['id', 'title'])\n * .execute() as unknown as Page<Pick<TasksRow, 'id' | 'title'>>;\n * ```\n * A future TS exercise (Pick-based variadic narrowing) lights up\n * inferred types without the cast.\n *\n * @example\n * await qb.tasks.where({ done: false }).select(['id', 'title']).execute();\n */\n select(fields: ReadonlyArray<string>): Query<Row>;\n\n /**\n * Group rows by one or more fields. Required precursor to `.agg()`\n * when aggregating per-group; without `.groupBy()` the aggregation\n * runs against the whole filtered set and returns one row.\n *\n * Transitions the builder to {@link AggQuery} so subsequent calls\n * have access to `.agg()` + the aggregation-shaped `.execute()`.\n *\n * @example\n * qb.from<TaskRow>('tasks')\n * .where({ done: false })\n * .groupBy(['priority', 'assignee_id'])\n * .agg({ total: { count: '*' } })\n * .execute()\n */\n groupBy(fields: ReadonlyArray<FieldRef>): AggQuery;\n\n /**\n * Set the aggregation expressions. Keys are output column names in\n * the result rows; values are aggregation function specs\n * (`{ count: '*' }`, `{ sum: 'amount' }`, etc.). At least one\n * expression is required; calling `.agg({})` rejects.\n *\n * Without a preceding `.groupBy()`, returns a single row with the\n * aggregations applied to the entire filtered set. With `.groupBy()`,\n * returns one row per group.\n *\n * Transitions the builder to {@link AggQuery} — `.execute()` on\n * the returned builder yields {@link QueryResult} (not `Page<Row>`).\n *\n * @example\n * const stats = await qb.from<TaskRow>('tasks')\n * .where({ done: false })\n * .agg({ total: { count: '*' }, oldest: { min: 'created_at' } })\n * .execute();\n * // stats.rows[0] = { total: 7, oldest: '2026-01-15' }\n */\n agg(expressions: Record<string, AggExpr>): AggQuery;\n}\n\n// ── JoinedQuery (join-mode builder, Phase 3) ──────────────────────────\n\n/**\n * Chainable per-query builder after `.join()` / `.leftJoin()` has\n * been called. Same kind of immutable shape as {@link Query} —\n * every modifier returns a fresh instance.\n *\n * Surface intentionally NARROWER than Query<Row>:\n * - Omits `.create / .update / .delete` — joined queries are\n * read-only (writes always go through `client.db()`).\n * - Omits `.count / .agg / .groupBy` — slice 3.2b-1 doesn't yet\n * handle aggregation over joined tables (Phase 5+ work).\n *\n * Today's available surface:\n * - `.where` — bare-ref + qualified-ref WHERE both work as of\n * Phase 3 slice 3.2b-2-full (SQL-side alias-aware WHERE)\n * - `.orderBy` — bare + qualified-ref sort (slice 3.2b-3)\n * - `.limit`, `.offset` — in-memory pagination over post-WHERE\n * rows, capped at 10k (slice 3.2b-3)\n * - `.join`, `.leftJoin` — stack additional joins up to 2 total\n * in v1 (Phase 4 slice 4.3; validator rejects 3+ with a clear\n * QUERY_TOO_COMPLEX). Cross-module dep refs accepted as targets\n * since slice 4.2.\n * - `.flatten()` — switches to SQL-style flat result rows (slice\n * 3.5; backend wired since slice 3.5)\n * - `.toAst()`, `.execute()` — same as Query<Row>.\n */\nexport interface JoinedQuery<Row extends BaseRow = BaseRow> {\n toAst(): QueryAst;\n /** Resolves with `Page<Row>` shape — rows carry the nested joined data. */\n execute(): Promise<Page<Row>>;\n /**\n * Phase 6 streaming (2026-05-19): stream joined-row results\n * row-by-row via SSE. Same contract as {@link Query.stream} — see\n * its JSDoc for the full doc. Joined queries stream their nested-\n * row shape (each yielded row has the joined object under the\n * alias key, same as `.execute()` returns).\n */\n stream(opts?: { signal?: AbortSignal }): AsyncIterable<Row>;\n /**\n * Return Postgres EXPLAIN (FORMAT JSON) for this joined query —\n * WITHOUT executing it. The joined path is the canonical supported\n * shape for `.explain()` in MVP (Phase 5, 2026-05-19) because the\n * join translator emits clean parameterized SQL the backend can wrap\n * with EXPLAIN directly.\n *\n * Returns the same {@link ExplainResult} shape as `Query.explain`.\n * See `Query.explain` JSDoc for the full call shape + examples.\n */\n explain(): Promise<ExplainResult>;\n where(clause: Where<Row>): JoinedQuery<Row>;\n orderBy(clauses: ReadonlyArray<OrderClause>): JoinedQuery<Row>;\n limit(n: number): JoinedQuery<Row>;\n offset(n: number): JoinedQuery<Row>;\n /**\n * Add another join. Capped at 2 total in v1 (validator).\n * Phase 4 slice 4.2: accepts {@link QbDepRef} as the target for\n * cross-module joins (same overload semantics as the entry join).\n */\n join<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n ): JoinedQuery<Row & Record<string, T>>;\n leftJoin<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'left'>,\n ): JoinedQuery<Row & Record<string, T | null>>;\n /**\n * Switch to flat-row result shape (`{ 'users.email': ... }`)\n * instead of the nested default. Slice 3.5 wires the backend; the\n * SDK exposes it today + sends the AST flag.\n */\n flatten(): JoinedQuery<Record<string, unknown> & BaseRow>;\n}\n\n// ── AggQuery (aggregation-mode builder) ──────────────────────────────\n\n/**\n * Aggregation-mode query. Reachable from {@link Query.agg} or\n * {@link Query.groupBy}. Exposes the same `where` / `limit` / `offset`\n * surface as the non-agg `Query` (you can still filter + limit groups)\n * but `.execute()` returns {@link QueryResult} instead of `Page<Row>`.\n *\n * Doesn't expose `.create` / `.update` / `.delete` (aggregations are\n * read-only by definition) or `.count()` (use `.agg({ n: { count: '*' } })`\n * directly + read `result.rows[0].n`).\n */\nexport interface AggQuery {\n toAst(): QueryAst;\n execute(): Promise<QueryResult>;\n /**\n * Return Postgres EXPLAIN (FORMAT JSON) for the underlying SELECT\n * this aggregation builds. Joined aggregations (where the builder\n * arrived here via `.join().agg()`) work in MVP — the source SELECT\n * dominates cost; the in-memory GROUP BY post-runs above it. Bare\n * own-table aggregations currently reject with `EXPLAIN_NOT_SUPPORTED_YET`\n * (lifts when bare-path support lands; see {@link Query.explain}).\n */\n explain(): Promise<ExplainResult>;\n where(clause: Record<string, unknown>): AggQuery;\n /** Refine the group-by fields (replace, not merge). */\n groupBy(fields: ReadonlyArray<FieldRef>): AggQuery;\n /** Refine the aggregation expressions (replace, not merge). */\n agg(expressions: Record<string, AggExpr>): AggQuery;\n limit(n: number): AggQuery;\n offset(n: number): AggQuery;\n}\n\n/**\n * Build a {@link Qb} bound to `client`. The same `qb` instance can\n * be reused across concurrent calls — every call constructs a fresh\n * Query, and the underlying client is already thread-safe.\n *\n * @example\n * import { createClient } from '@dashai/sdk';\n * import { makeQb } from '@dashai/sdk/query';\n *\n * const client = createClient({ baseUrl, apiKey });\n * const qb = makeQb(client);\n *\n * const page = await qb.from<TaskRow>('tasks').execute();\n * console.log(page.rows);\n *\n * The generated SDK (`@dashai/generated/qb`) will eventually export\n * a typed singleton wrapping the default client — `import { qb } from\n * '@dashai/generated'; qb.tasks.execute()`. That layer lands once\n * codegen is updated; until then, `makeQb(client).from('tasks')` is\n * the path.\n */\nexport function makeQb(client: Client): Qb {\n return {\n // Single runtime function backs all three overloads — TypeScript\n // narrows at the call site, but at runtime we discriminate on the\n // input shape:\n // string → own-table slug\n // { kind: 'dep', ... } → cross-module dep ref (slice 4.1b)\n // { kind: 'view', slug } → view ref (slice 5.4b)\n from<Row extends BaseRow = BaseRow>(\n target: string | QbDepRef | QbViewRef,\n ): Query<Row> {\n let ref: TableRef;\n if (typeof target === 'string') {\n assertValidTableSlug(target);\n ref = { kind: 'owned', slug: target };\n } else if (target && typeof target === 'object' && target.kind === 'dep') {\n // Defensive — TypeScript catches most misuse at compile time,\n // but a runtime check keeps the error message friendly when\n // JavaScript consumers reach for the dep form without TS.\n assertValidModuleSlug(target.module);\n assertValidTableSlug(target.slug);\n ref = { kind: 'dep', module: target.module, slug: target.slug };\n } else if (target && typeof target === 'object' && target.kind === 'view') {\n // View slug shape mirrors table slug — snake_case validator\n // (matches `manifest.owns.views[].slug` regex on the backend).\n assertValidTableSlug(target.slug);\n ref = { kind: 'view', slug: target.slug };\n } else {\n throw new Error(\n `@dashai/sdk/query: qb.from() expects a string table slug, { kind: 'dep', module, slug }, or { kind: 'view', slug }; got ${JSON.stringify(target)}.`,\n );\n }\n const ast: QueryAst = {\n v: CURRENT_QUERY_AST_VERSION,\n from: ref,\n };\n return makeQuery<Row>(client, ast);\n },\n };\n}\n\n/**\n * Internal helper — POST an AST to the backend's `.explain()` endpoint\n * and return the typed envelope. Used by all three builder kinds\n * (Query, JoinedQuery, AggQuery) so the wire shape stays consistent.\n *\n * The endpoint URL is the same controller as `/query` but with the\n * `/explain` suffix; transport handles bearer-token + error mapping\n * identically to `.execute()`.\n *\n * `EXPLAIN_NOT_SUPPORTED_YET` errors propagate unchanged — the SDK\n * doesn't have a typed subclass for them yet (the error code is\n * preserved on the base `DashwiseError` so callers can `.catch(e => e.code === 'EXPLAIN_NOT_SUPPORTED_YET')`).\n */\nasync function postExplain(client: Client, ast: QueryAst): Promise<ExplainResult> {\n return client.request<ExplainResult>('POST', '/query/explain', { ast });\n}\n\n/**\n * Internal helper — open an SSE stream against the backend's\n * `/query/stream` endpoint and yield rows. Used by `Query.stream` +\n * `JoinedQuery.stream`. Returns an `AsyncGenerator<Row>` so the\n * caller can use `for await` directly.\n *\n * Wire contract (mirrors the backend's `POST /query/stream`):\n * - Each row is emitted as one SSE event with the default `message`\n * event name + a `data:` line carrying the JSON row.\n * - Terminal `event: complete\\ndata: {\"total\": N}\\n\\n` event marks\n * the end — generator returns cleanly.\n * - In-band `event: error\\ndata: {\"code\": ..., \"message\": ...}\\n\\n`\n * event is thrown as a typed {@link DashwiseError} from the\n * generator. The error's `requestId` is populated from the\n * `x-request-id` response header for trace anchoring.\n *\n * Pre-stream errors (HTTP non-2xx before any SSE data arrives) are\n * read from the response body, parsed via {@link fromBackendEnvelope},\n * and thrown. Same error mapping as the non-streaming endpoints —\n * `ValidationError`, `ForbiddenError`, etc. all surface correctly.\n *\n * **Cleanup**: when the consumer breaks out of the `for await` loop\n * early (early `break`, exception propagation, etc.), the generator's\n * `return()` is invoked. The `finally` block releases the underlying\n * reader's lock so the fetch's network connection can be reclaimed.\n */\nasync function* streamRowsViaSSE<Row>(\n client: Client,\n ast: QueryAst,\n signal?: AbortSignal,\n): AsyncGenerator<Row, void, void> {\n const res = await client.streamRaw('POST', '/query/stream', { ast }, signal);\n const requestId = res.headers.get('x-request-id');\n\n // Pre-stream error path: backend returned a non-2xx response (most\n // commonly a validation-phase 400 before any SSE headers go out).\n // Body is standard JSON; map via fromBackendEnvelope so the caller\n // gets typed errors identical to `.execute()`'s.\n if (!res.ok) {\n const envelope = await readErrorEnvelope(res);\n throw fromBackendEnvelope(res.status, envelope, requestId);\n }\n\n // Streaming phase. We expect `Content-Type: text/event-stream` here;\n // a non-SSE 2xx response is a backend misconfig (defensive — the\n // /query/stream endpoint always sets the SSE content-type when it\n // reaches the streaming phase).\n const reader = res.body?.getReader();\n if (!reader) {\n throw new Error(\n '@dashai/sdk/query: streaming response has no readable body — backend bug or misconfigured fetch impl.',\n );\n }\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n // Stream closed without a `complete` event. Defensive: if\n // there's anything in the buffer, parse it; otherwise the\n // backend closed unexpectedly.\n if (buffer.trim().length > 0) {\n // Final partial event — parse + emit if it's a message.\n const last = parseSseBlock(buffer);\n if (last && last.event === 'message') {\n yield last.data as Row;\n }\n }\n return;\n }\n buffer += decoder.decode(value, { stream: true });\n // SSE event boundary is `\\n\\n`. Split, keep the last (possibly\n // partial) chunk in the buffer for the next read iteration.\n const parts = buffer.split('\\n\\n');\n buffer = parts.pop() ?? '';\n for (const block of parts) {\n if (!block.trim()) continue;\n const parsed = parseSseBlock(block);\n if (!parsed) continue;\n if (parsed.event === 'message') {\n yield parsed.data as Row;\n } else if (parsed.event === 'complete') {\n // Clean end — done.\n return;\n } else if (parsed.event === 'error') {\n // In-band error — map to typed DashwiseError.\n const err = parsed.data as { code?: string; message?: string };\n throw fromBackendEnvelope(\n 500,\n { code: err.code, message: err.message },\n requestId,\n );\n }\n // Unknown event names are ignored (forward-compat with future\n // SSE event types — e.g., `progress`, `heartbeat`).\n }\n }\n } finally {\n // Release the reader's exclusive lock on the body so the fetch\n // can finalize. Don't await reader.cancel() — that would block\n // generator cleanup on a network round-trip we don't need.\n try {\n reader.releaseLock();\n } catch {\n // releaseLock throws if reader is already closed; safe to ignore.\n }\n }\n}\n\n/**\n * Parse one SSE event block (the text between two `\\n\\n` boundaries)\n * into `{ event, data }`. Default event name is `'message'` per SSE\n * spec. Returns null for empty blocks.\n *\n * The block may contain multiple `data:` lines; SSE spec says they\n * concatenate with `\\n` between. We honor that but in practice the\n * backend emits single-line `data:` payloads.\n */\nfunction parseSseBlock(block: string): { event: string; data: unknown } | null {\n const trimmed = block.trim();\n if (trimmed.length === 0) return null;\n let event = 'message';\n const dataLines: string[] = [];\n for (const line of trimmed.split('\\n')) {\n if (line.startsWith('event:')) {\n event = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n // Per SSE spec, strip ONE leading space after the colon if present.\n const v = line.slice(5);\n dataLines.push(v.startsWith(' ') ? v.slice(1) : v);\n }\n // Other field names (id:, retry:) ignored — we don't use them.\n }\n if (dataLines.length === 0) return null;\n const joined = dataLines.join('\\n');\n let data: unknown;\n try {\n data = JSON.parse(joined);\n } catch {\n data = joined; // pass through as string when not JSON\n }\n return { event, data };\n}\n\n/**\n * Read a pre-stream error response body as the backend's standard\n * `{ code, message, retriable?, context? }` envelope. Tolerates\n * malformed bodies (returns null) since the transport's error\n * pipeline already accepts that case.\n */\nasync function readErrorEnvelope(\n res: Response,\n): Promise<{\n code?: string;\n message?: string;\n retriable?: boolean;\n context?: Record<string, unknown>;\n} | null> {\n try {\n const parsed = await res.json();\n if (parsed && typeof parsed === 'object') {\n return parsed as {\n code?: string;\n message?: string;\n retriable?: boolean;\n context?: Record<string, unknown>;\n };\n }\n return null;\n } catch {\n return null;\n }\n}\n\n// ── Internal: Query implementation ───────────────────────────────────\n\n/**\n * Build a `Query<Row>` instance bound to `client` + carrying `ast`.\n * Internal — consumers reach a Query via `qb.from(slug)`.\n *\n * Kept separate from `makeQb` so future chainable methods (which\n * each produce a new Query) can call back into the same factory\n * without duplicating the dispatch logic.\n */\nfunction makeQuery<Row extends BaseRow>(\n client: Client,\n ast: QueryAst,\n): Query<Row> {\n // Local helper: produce a new Query<Row> with `patch` merged into\n // the existing AST. Every chainable method routes through here so\n // the immutability contract is enforced in one place.\n const fork = (patch: Partial<QueryAst>): Query<Row> =>\n makeQuery<Row>(client, { ...ast, ...patch });\n\n return {\n toAst(): QueryAst {\n // Return a structural clone so callers can introspect/mutate\n // without disturbing the builder's internal state. Cheap —\n // the AST is small JSON.\n return JSON.parse(JSON.stringify(ast)) as QueryAst;\n },\n\n where(clause: Where<Row>): Query<Row> {\n // The SDK's `Where<Row>` is structurally compatible with the\n // AST's looser `WhereClause` (untyped at the AST layer). Cast\n // at the boundary so the typed surface stays sharp.\n return fork({ where: clause as unknown as WhereClause });\n },\n\n orderBy(clauses: ReadonlyArray<OrderClause>): Query<Row> {\n // Defensive copy — the AST stores a fresh array so the caller\n // can mutate their input without retroactively changing the\n // builder's state.\n return fork({ orderBy: [...clauses] });\n },\n\n limit(n: number): Query<Row> {\n // Fail fast on obvious garbage. Backend enforces the upper bound\n // (`RESULT_TOO_LARGE` over 1000) — we only catch the cases that\n // would round-trip to a confusing 400.\n if (!Number.isInteger(n) || n < 1) {\n throw new Error(\n `@dashai/sdk/query: .limit(${n}) must be a positive integer.`,\n );\n }\n return fork({ limit: n });\n },\n\n offset(n: number): Query<Row> {\n if (!Number.isInteger(n) || n < 0) {\n throw new Error(\n `@dashai/sdk/query: .offset(${n}) must be a non-negative integer.`,\n );\n }\n return fork({ offset: n });\n },\n\n select(fields: ReadonlyArray<string>): Query<Row> {\n // Empty array intentionally writes `select: []` to the AST so\n // the backend sees \"user said nothing → no narrowing.\" Matches\n // narrowFieldsFromOpts's contract.\n return fork({ select: [...fields] });\n },\n\n async count(): Promise<number> {\n // Lower limit to 1 row to minimize transport cost — the\n // backend's `total` reflects the post-filter count regardless\n // of limit. Defensive: if the consumer already set a limit we\n // honor theirs (some test harnesses pin a specific page size\n // and assert deterministic behavior across both calls).\n const minimalAst: QueryAst = {\n ...ast,\n limit: ast.limit ?? 1,\n };\n const result = await client.request<Page<Row>>(\n 'POST',\n '/query',\n { ast: minimalAst },\n );\n if (Array.isArray(result)) {\n // Bare-array fallback — total isn't carried; assume the array\n // length (matches what the .execute() coercion does).\n return (result as Row[]).length;\n }\n return result.total;\n },\n\n async execute(): Promise<Page<Row>> {\n // The backend's query controller lives at\n // `POST /api/module-api/query`. `client.request()`\n // prepends the installation-scoped base URL, applies the\n // bearer token, maps errors to typed DashwiseError subclasses,\n // and handles the 204 case (which this endpoint won't emit\n // but the transport covers it anyway).\n const result = await client.request<Page<Row>>(\n 'POST',\n '/query',\n { ast },\n );\n // Defence in depth: same Page-envelope coercion the owned-table\n // `db().list()` does, in case a misconfigured proxy strips the\n // envelope. Consumers always see `{ rows, total, limit, offset }`.\n if (Array.isArray(result)) {\n return {\n rows: result as Row[],\n total: (result as Row[]).length,\n limit: ast.limit ?? (result as Row[]).length,\n offset: ast.offset ?? 0,\n };\n }\n return result;\n },\n\n async explain(): Promise<ExplainResult> {\n // Phase 5 (2026-05-19): bare own-table reads currently reject\n // server-side with EXPLAIN_NOT_SUPPORTED_YET — the SDK passes\n // the call through anyway so the typed error reaches the caller\n // (the error's `context.fromKind` lets them branch on the\n // unsupported shape if they want). Joined queries (which go\n // through `JoinedQuery.explain`) are the supported MVP path.\n return postExplain(client, ast);\n },\n\n stream(opts?: { signal?: AbortSignal }): AsyncIterable<Row> {\n // Phase 6 streaming (2026-05-19): return an AsyncGenerator\n // that yields rows from the SSE endpoint. The generator is\n // lazy — no network call until the consumer starts iterating.\n return streamRowsViaSSE<Row>(client, ast, opts?.signal);\n },\n\n groupBy(fields: ReadonlyArray<FieldRef>): AggQuery {\n // Transition to aggregation mode. `.groupBy()` alone (without\n // a subsequent `.agg()`) is still a valid intermediate state;\n // the backend rejects with VALIDATION_FAILED when .execute()\n // fires against an AST that has groupBy but no agg.\n return makeAggQuery(client, { ...ast, groupBy: [...fields] });\n },\n\n agg(expressions: Record<string, AggExpr>): AggQuery {\n // Transition to aggregation mode. Shape of `expressions` is\n // validated server-side (Zod owns the agg expression grammar)\n // so the SDK doesn't duplicate the rules.\n return makeAggQuery(client, { ...ast, agg: expressions });\n },\n\n // ── Join entries (Phase 3 slice 3.3) ──────────────────────────\n //\n // .join() / .leftJoin() transition to JoinedQuery whose surface\n // omits write methods + the aggregation suite. The SDK delegates\n // shape validation to the backend's AST validator + the\n // join-translator; today's slice 3.2b-1 cap means combinations\n // with where/orderBy/limit/offset/agg backend-reject with clear\n // \"lands in slice X\" errors.\n\n join<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n ): JoinedQuery<Row & Record<string, T>> {\n return makeJoinedQuery<Row & Record<string, T>>(client, {\n ...ast,\n joins: [...(ast.joins ?? []), buildJoinNode(target, opts, 'inner')],\n });\n },\n\n leftJoin<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'left'>,\n ): JoinedQuery<Row & Record<string, T | null>> {\n return makeJoinedQuery<Row & Record<string, T | null>>(client, {\n ...ast,\n joins: [...(ast.joins ?? []), buildJoinNode(target, opts, 'left')],\n });\n },\n };\n}\n\n// ── JoinedQuery factory (slice 3.3) ─────────────────────────────────\n\n/**\n * Build a `JoinedQuery<Row>` bound to `client` + carrying `ast`.\n * Mirrors `makeQuery` shape but the surface omits write methods +\n * aggregation; `execute()` still returns `Page<Row>`.\n */\nfunction makeJoinedQuery<Row extends BaseRow>(\n client: Client,\n ast: QueryAst,\n): JoinedQuery<Row> {\n const fork = (patch: Partial<QueryAst>): JoinedQuery<Row> =>\n makeJoinedQuery<Row>(client, { ...ast, ...patch });\n\n return {\n toAst(): QueryAst {\n return JSON.parse(JSON.stringify(ast)) as QueryAst;\n },\n\n where(clause: Where<Row>): JoinedQuery<Row> {\n return fork({ where: clause as unknown as WhereClause });\n },\n\n orderBy(clauses: ReadonlyArray<OrderClause>): JoinedQuery<Row> {\n return fork({ orderBy: [...clauses] });\n },\n\n limit(n: number): JoinedQuery<Row> {\n if (!Number.isInteger(n) || n < 1) {\n throw new Error(\n `@dashai/sdk/query: .limit(${n}) must be a positive integer.`,\n );\n }\n return fork({ limit: n });\n },\n\n offset(n: number): JoinedQuery<Row> {\n if (!Number.isInteger(n) || n < 0) {\n throw new Error(\n `@dashai/sdk/query: .offset(${n}) must be a non-negative integer.`,\n );\n }\n return fork({ offset: n });\n },\n\n join<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n ): JoinedQuery<Row & Record<string, T>> {\n return makeJoinedQuery<Row & Record<string, T>>(client, {\n ...ast,\n joins: [...(ast.joins ?? []), buildJoinNode(target, opts, 'inner')],\n });\n },\n\n leftJoin<T = unknown>(\n target: string | QbDepRef,\n opts: JoinOpts<'left'>,\n ): JoinedQuery<Row & Record<string, T | null>> {\n return makeJoinedQuery<Row & Record<string, T | null>>(client, {\n ...ast,\n joins: [...(ast.joins ?? []), buildJoinNode(target, opts, 'left')],\n });\n },\n\n flatten(): JoinedQuery<Record<string, unknown> & BaseRow> {\n // SDK sets the AST flag today. Backend wiring lands in slice\n // 3.5; until then the backend silently returns nested rows.\n // Documented in the .flatten() JSDoc.\n return makeJoinedQuery<Record<string, unknown> & BaseRow>(client, {\n ...ast,\n flatten: true,\n });\n },\n\n async execute(): Promise<Page<Row>> {\n const result = await client.request<Page<Row>>(\n 'POST',\n '/query',\n { ast },\n );\n if (Array.isArray(result)) {\n return {\n rows: result as Row[],\n total: (result as Row[]).length,\n limit: ast.limit ?? (result as Row[]).length,\n offset: ast.offset ?? 0,\n };\n }\n return result;\n },\n\n async explain(): Promise<ExplainResult> {\n // Phase 5 (2026-05-19): joined queries are the supported MVP\n // path — the backend's `executeJoin` exposes its translateJoin\n // SQL cleanly so EXPLAIN wraps it directly. Returns the typed\n // {@link ExplainResult} envelope.\n return postExplain(client, ast);\n },\n\n stream(opts?: { signal?: AbortSignal }): AsyncIterable<Row> {\n // Phase 6 streaming: joined queries stream their nested-row\n // shape — backend's executeJoin produces the same shape `.execute()`\n // returns, just delivered row-by-row via SSE.\n return streamRowsViaSSE<Row>(client, ast, opts?.signal);\n },\n };\n}\n\n/**\n * Build a {@link JoinNode} from the SDK's `.join()` / `.leftJoin()`\n * call shape. Defaults `kind` per the caller's method choice when\n * `opts.kind` is absent.\n */\nfunction buildJoinNode(\n target: string | QbDepRef,\n opts: JoinOpts<'inner' | 'left'>,\n defaultKind: 'inner' | 'left',\n): {\n kind: 'inner' | 'left';\n to: TableRef;\n on: Record<string, string>;\n as?: string;\n} {\n // Phase 4 slice 4.2 (2026-05-19): target accepts a dep ref in\n // addition to a bare own-table slug — same overload semantics as\n // `qb.from()`. The backend's `executeJoin` resolves either side\n // through the right chain (own-table or dep contract).\n let to: TableRef;\n let displaySlug: string;\n if (typeof target === 'string') {\n assertValidTableSlug(target);\n to = { kind: 'owned', slug: target };\n displaySlug = target;\n } else if (target && typeof target === 'object' && target.kind === 'dep') {\n assertValidModuleSlug(target.module);\n assertValidTableSlug(target.slug);\n to = { kind: 'dep', module: target.module, slug: target.slug };\n displaySlug = `${target.module}:${target.slug}`;\n } else {\n throw new Error(\n `@dashai/sdk/query: .join() target must be a table slug string or { kind: 'dep', module, slug }; got ${JSON.stringify(target)}.`,\n );\n }\n if (!opts.on || Object.keys(opts.on).length === 0) {\n throw new Error(\n `@dashai/sdk/query: .join('${displaySlug}') requires an \\`on\\` clause with at least one field pair. Link-field-derived \\`on\\` inference lands in a follow-up slice.`,\n );\n }\n return {\n kind: opts.kind ?? defaultKind,\n to,\n on: opts.on,\n ...(opts.as !== undefined ? { as: opts.as } : {}),\n };\n}\n\n/**\n * Build an `AggQuery` bound to `client` + carrying `ast`. Symmetric\n * with `makeQuery` but `.execute()` returns {@link QueryResult} and\n * the surface omits per-row methods (no `.create` / `.update` etc.)\n * since aggregation results aren't individual rows.\n *\n * Internal — consumers reach AggQuery via `.agg()` or `.groupBy()`\n * on a non-agg Query.\n */\nfunction makeAggQuery(client: Client, ast: QueryAst): AggQuery {\n const fork = (patch: Partial<QueryAst>): AggQuery =>\n makeAggQuery(client, { ...ast, ...patch });\n\n return {\n toAst(): QueryAst {\n return JSON.parse(JSON.stringify(ast)) as QueryAst;\n },\n\n where(clause: Record<string, unknown>): AggQuery {\n return fork({ where: clause as WhereClause });\n },\n\n groupBy(fields: ReadonlyArray<FieldRef>): AggQuery {\n return fork({ groupBy: [...fields] });\n },\n\n agg(expressions: Record<string, AggExpr>): AggQuery {\n return fork({ agg: expressions });\n },\n\n limit(n: number): AggQuery {\n if (!Number.isInteger(n) || n < 1) {\n throw new Error(\n `@dashai/sdk/query: .limit(${n}) must be a positive integer.`,\n );\n }\n return fork({ limit: n });\n },\n\n offset(n: number): AggQuery {\n if (!Number.isInteger(n) || n < 0) {\n throw new Error(\n `@dashai/sdk/query: .offset(${n}) must be a non-negative integer.`,\n );\n }\n return fork({ offset: n });\n },\n\n async execute(): Promise<QueryResult> {\n const result = await client.request<QueryResult>(\n 'POST',\n '/query',\n { ast },\n );\n // Defence in depth: bare-array fallback (matches non-agg path).\n if (Array.isArray(result)) {\n return {\n rows: result as Array<Record<string, unknown>>,\n total: (result as unknown[]).length,\n };\n }\n return result;\n },\n\n async explain(): Promise<ExplainResult> {\n // Phase 5 (2026-05-19): joined aggregations work (backend\n // EXPLAINs the source SELECT — the in-memory GROUP BY\n // post-runs above it and isn't visible to Postgres). Bare\n // own-table / cross-mod aggregations reject server-side with\n // EXPLAIN_NOT_SUPPORTED_YET until the bare-path SQL extraction\n // lands. The SDK passes the call through unchanged either way.\n return postExplain(client, ast);\n },\n };\n}\n"]}
|