@palbase/backend 27.0.0 → 28.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/dist/bin/palbase-backend.cjs +165 -39
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +4 -4
- package/dist/{chunk-GYK6QYS4.js → chunk-3TUJWHC2.js} +28 -3
- package/dist/chunk-3TUJWHC2.js.map +1 -0
- package/dist/{chunk-OO7R25AI.js → chunk-75YROPRZ.js} +118 -39
- package/dist/chunk-75YROPRZ.js.map +1 -0
- package/dist/{chunk-TS4U7NBD.js → chunk-IVZERLTM.js} +2 -2
- package/dist/{chunk-I3C4PFIW.js → chunk-RVP6BTEZ.js} +63 -6
- package/dist/chunk-RVP6BTEZ.js.map +1 -0
- package/dist/{chunk-DRZFQRJI.js → chunk-SNDXY565.js} +3 -2
- package/dist/chunk-SNDXY565.js.map +1 -0
- package/dist/db/index.cjs +30 -1
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +3 -3
- package/dist/engine/index.cjs +175 -39
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +3 -3
- package/dist/engine/index.d.ts +3 -3
- package/dist/engine/index.js +11 -4
- package/dist/{index-VtToZmUm.d.cts → index-9C3JHxg-.d.cts} +112 -5
- package/dist/{index-NuzRCuxe.d.ts → index-BbvOoZFr.d.ts} +112 -5
- package/dist/{index-Bve7BBTL.d.cts → index-DtISj9QX.d.cts} +125 -22
- package/dist/{index-BrvvxSpn.d.ts → index-dTTLlHIn.d.ts} +125 -22
- package/dist/index.cjs +93 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -22
- package/dist/index.d.ts +6 -22
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.d.cts +2 -2
- package/dist/openapi/index.d.ts +2 -2
- package/dist/{registry-B0eyOF9x.d.ts → registry-JQNIX-eA.d.ts} +1 -1
- package/dist/{registry-Bk9_rbNd.d.cts → registry-qIPM5BQe.d.cts} +1 -1
- package/dist/test/index.cjs +100 -6
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +88 -6
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/database.md +103 -1
- package/docs/llms-full.txt +104 -2
- package/package.json +1 -1
- package/template/package.json +1 -1
- package/dist/chunk-DRZFQRJI.js.map +0 -1
- package/dist/chunk-GYK6QYS4.js.map +0 -1
- package/dist/chunk-I3C4PFIW.js.map +0 -1
- package/dist/chunk-OO7R25AI.js.map +0 -1
- /package/dist/{chunk-TS4U7NBD.js.map → chunk-IVZERLTM.js.map} +0 -0
package/dist/test/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/test/index.ts","../../../core/src/config.ts","../../../core/src/errors.ts","../../../core/src/pow.ts","../../../core/src/platform.ts","../../../core/src/http.ts","../../../core/src/token.ts","../../src/test/api.ts","../../src/test/container.ts","../../src/db/input-guards.ts","../../src/db/tx-plan.ts","../../src/__tests__/helpers/mock-db.ts","../../src/test/fake-db.ts"],"sourcesContent":["export { api, createTestApi, TestApiError } from \"./api.js\";\nexport { isolated } from \"./container.js\";\nexport type { IsolatedContainer } from \"./container.js\";\nexport { fakeDatabase } from \"./fake-db.js\";\nexport type { FakeDatabase, RecordedQuery } from \"./fake-db.js\";\nexport type {\n CallOptions,\n ErrorEnvelope,\n RecordedRequest,\n TestApi,\n TestApiConfig,\n TestIdentity,\n} from \"./api.js\";\n","import type { HttpClient } from './http.js';\nimport type { ProjectConfig } from './types.js';\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ConfigFetcher {\n protected readonly httpClient: HttpClient;\n private cachedConfig: ProjectConfig | null = null;\n private cacheTimestamp = 0;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n async getConfig(): Promise<ProjectConfig | null> {\n const now = Date.now();\n\n if (this.cachedConfig && now - this.cacheTimestamp < CACHE_TTL_MS) {\n return this.cachedConfig;\n }\n\n try {\n const response = await this.httpClient.request<ProjectConfig>('GET', '/v1/config');\n\n if (response.error || !response.data) {\n return null;\n }\n\n this.cachedConfig = response.data;\n this.cacheTimestamp = now;\n\n return this.cachedConfig;\n } catch {\n return null;\n }\n }\n}\n","export class PalbaseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: string, message: string, status: number, details?: unknown) {\n super(message);\n this.name = 'PalbaseError';\n this.code = code;\n this.status = status;\n this.details = details;\n }\n}\n","// Proof-of-work: the bot gate in front of /auth/signup and /auth/login.\n//\n// The server answers an unsolved request with 403 and a challenge in the body:\n//\n// { \"error\": \"pow_required\", \"challenge\": { \"id\", \"prefix\", \"difficulty\" } }\n//\n// A client finds any nonce whose SHA-256(prefix + nonce) begins with\n// `difficulty` zero bits, then repeats the request carrying the id and nonce as\n// headers. The work is the point: a person signing up pays it once and does not\n// notice, a script signing up ten thousand times pays it ten thousand times.\n//\n// # Why this lives in core, and not one layer up\n//\n// Until 2026-08-14 nothing shipped could solve it: the gate was written with the\n// server and its own integration harness, and every real client sent requests\n// without the headers and got 403. It was then solved in @palbase/web's own\n// request path — which covers `pb.call` and the module facades and NOT\n// `pb.auth.*`, because those go through @palbase/auth's client and from there\n// into core's HttpClient. So the fix landed everywhere except the two endpoints\n// the gate actually guards, and `npm i @palbase/web` still could not sign a\n// person in. Measured 2026-08-18 against a real stack, on the published 7.3.0.\n//\n// The lesson is where a retry belongs: at the layer that ISSUES the request.\n// Core owns fetch for every client in this repo, so core owns the challenge.\n//\n// WebCrypto rather than a hashing dependency: `crypto.subtle` is present in\n// browsers and in Node 18+, which is the same floor the rest of the SDK sets.\n// Measured at the server's default difficulty of 16: ~330ms, ~65k digests.\n\n/** The challenge a `pow_required` response carries. */\nexport interface PowChallenge {\n id: string;\n prefix: string;\n difficulty: number;\n}\n\n/** Header names the retry must carry. Mirrors the server's constants. */\nexport const POW_CHALLENGE_ID_HEADER = 'X-PoW-Challenge-ID';\nexport const POW_NONCE_HEADER = 'X-PoW-Nonce';\n\n/**\n * Reads a challenge out of an error envelope, or returns null when the envelope\n * is not a `pow_required` one.\n *\n * The whole wire envelope is stored on the error, so the challenge arrives\n * without the HTTP layer having to know about proof-of-work at all.\n */\nexport function asPowChallenge(details: unknown): PowChallenge | null {\n if (typeof details !== 'object' || details === null) return null;\n const env = details as Record<string, unknown>;\n if (env.error !== 'pow_required') return null;\n const c = env.challenge;\n if (typeof c !== 'object' || c === null) return null;\n const { id, prefix, difficulty } = c as Record<string, unknown>;\n if (typeof id !== 'string' || typeof prefix !== 'string') return null;\n if (typeof difficulty !== 'number' || !Number.isInteger(difficulty) || difficulty < 0) return null;\n return { id, prefix, difficulty };\n}\n\nconst encoder = new TextEncoder();\n\n/**\n * One SHA-256, by the fastest route this runtime offers.\n *\n * Awaiting `crypto.subtle.digest` once per nonce is what made this expensive,\n * and the cost is the await rather than the hashing. Measured on one machine,\n * 200k digests of a 40-byte input:\n *\n *\tawaited crypto.subtle.digest 105,597 digests/s\n *\tsync node:crypto createHash 1,324,503 digests/s — 12.5x\n *\n * That is the difference between difficulty 24 taking 159 seconds and taking\n * 13. Node, Bun and Deno all have the sync one; a browser has only WebCrypto,\n * and there it stays async.\n *\n * The specifier is assembled at runtime so a browser bundler does not try to\n * resolve `node:crypto` and fail the build over a branch that never runs there.\n */\ntype Hasher = (input: string) => Uint8Array | Promise<Uint8Array>;\n\nlet hasher: Hasher | null = null;\n\nasync function digester(): Promise<Hasher> {\n if (hasher) return hasher;\n // Read off globalThis with an inline shape rather than by naming `process`,\n // which needs @types/node — a dependency this package does not have and should\n // not grow for one branch. It typechecked locally only because those types\n // were hoisted into node_modules by a sibling package; the publish workflow's\n // clean checkout is what said so, which is exactly what it is for.\n const runtime = globalThis as {\n process?: { versions?: { node?: string; bun?: string } };\n };\n const nodeish =\n runtime.process?.versions?.node !== undefined ||\n runtime.process?.versions?.bun !== undefined;\n if (nodeish) {\n try {\n const mod = (await import(/* @vite-ignore */ `${'node:'}crypto`)) as {\n createHash?: (alg: string) => { update(s: string): { digest(): Uint8Array } };\n };\n if (typeof mod.createHash === 'function') {\n const createHash = mod.createHash;\n hasher = (input: string) => new Uint8Array(createHash('sha256').update(input).digest());\n return hasher;\n }\n } catch {\n // No node:crypto here. WebCrypto below is not a fallback in the apologetic\n // sense — it is the only hash a browser has, and it is correct.\n }\n }\n hasher = async (input: string) =>\n new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(input)));\n return hasher;\n}\n\n/** Monotonic where it exists, wall-clock where it does not. */\nconst now = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n\n/** Counts leading zero bits, stopping at the first byte that has a one. */\nfunction leadingZeroBits(hash: Uint8Array): number {\n let bits = 0;\n for (const byte of hash) {\n if (byte === 0) {\n bits += 8;\n continue;\n }\n // clz32 counts across 32 bits; a byte occupies the low 8, so the first 24\n // are always zero and get subtracted back off.\n return bits + Math.clz32(byte) - 24;\n }\n return bits;\n}\n\n/**\n * The hardest challenge this client will attempt.\n *\n * Not a taste: it is the server's own ceiling. palauth maps a risk score to a\n * difficulty and its worst case is 24 (`DifficultyForRisk`, bot/pow.go:156-166).\n * Anything above that cannot have come from a stack behaving as designed, and\n * the cost of humouring it falls entirely on this side — each step up DOUBLES\n * the work, so difficulty 30 is sixty-four times a legitimate worst case and, on\n * the web, sixty-four times a frozen main thread. Refused immediately, by name.\n */\nexport const MAX_POW_DIFFICULTY = 24;\n\n/**\n * Finds a nonce satisfying the challenge and returns the headers a retry needs.\n *\n * THE BUDGET SCALES WITH THE CHALLENGE, and the first version of this did not.\n * It bounded the search at a flat `1 << 24` — which is not a generous bound for\n * difficulty 24, it is the EXPECTED number of attempts. Finding a nonce is a\n * geometric process: the chance of needing more than 2^d attempts is 1/e, so a\n * flat 2^24 would have failed roughly 37% of legitimate hardest-risk challenges,\n * and failed them for precisely the users the gate exists to slow down — who\n * would have been unable to sign in at all rather than made to wait.\n *\n * Eight times expected puts that at e^-8, about three in ten thousand, while\n * leaving the common case (the server's default 16, and 12 for an unremarkable\n * caller) exactly as cheap as it was.\n *\n * `powBudget` is exported and separate so the RELATIONSHIP can be asserted\n * directly. A test that only watches a cheap challenge succeed cannot tell this\n * budget from the flat one it replaced — measured: reinstating `1 << 24` left\n * such a test green.\n */\nexport function powBudget(difficulty: number): number {\n return 8 * 2 ** difficulty;\n}\n\n/**\n * The longest a solve may be ALLOWED to take, and the difference from a\n * deadline is the whole point.\n *\n * The first version of this was a flat 120s deadline, and it was measured to be\n * worse than the flat iteration budget it was meant to backstop: at ~105k\n * digests/s, difficulty 24 EXPECTS 159 seconds, so a 120s clock killed the\n * majority of legitimate hardest-risk solves — reintroducing, larger, exactly\n * the class of defect that replacing `1 << 24` had removed. Guessing a number\n * for an unknown machine cannot work: the same difficulty is 13 seconds on a\n * runtime with a sync hasher and 159 on one without.\n *\n * So the machine is MEASURED, and the decision moves to the front. A short\n * calibration gives the rate this process actually hashes at; if the whole\n * iteration budget cannot fit in this window at that rate, the solve is refused\n * IMMEDIATELY, naming the numbers. A caller then learns in milliseconds that\n * this difficulty is unpayable here, instead of after two minutes of work\n * thrown away.\n *\n * What remains after that is a guarantee rather than a gamble: a solve that\n * starts can always finish inside its budget, so the only failure left is the\n * budget's own e^-8.\n */\nexport const POW_TIME_BUDGET_MS = 120_000;\n\n/**\n * The window the rate is measured over, and the warm-up it deliberately skips.\n *\n * All of these are REAL attempts — the search starts at nonce 0 and never\n * restarts — so calibration costs nothing but the reading. The first 1024 are\n * excluded from the timing because they include this loop's own JIT warm-up:\n * measured, timing from zero reported 747k digests/s on a machine whose steady\n * rate is 1.32M, and the decision below would have refused a difficulty this\n * machine can pay in half the allowance.\n */\nconst CALIBRATION_WARMUP = 1024;\nconst CALIBRATION_END = 9216;\n\nexport async function solvePowChallenge(\n challenge: PowChallenge,\n maxIterations = powBudget(challenge.difficulty),\n // The caller's AbortSignal, honoured INSIDE the loop rather than only around\n // the fetch it precedes — for the callers that have one. `pb.auth.signIn` does\n // NOT: it reaches the network through @palbase/auth's client, which takes\n // credentials and nothing else. So it is the extra a caller can opt into, and\n // POW_TIME_BUDGET_MS below is what actually bounds the work.\n signal?: AbortSignal,\n): Promise<Record<string, string>> {\n if (challenge.difficulty > MAX_POW_DIFFICULTY) {\n throw new Error(\n `proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`,\n );\n }\n\n const digest = await digester();\n let warmedAt = 0;\n let calibrated = false;\n // Armed by the calibration below, never before it: until the rate is known\n // there is no honest number to put here.\n let deadline = Number.POSITIVE_INFINITY;\n\n for (let nonce = 0; nonce < maxIterations; nonce++) {\n // Checked in batches: reading them is cheap but not free, and a\n // 1024-digest granularity bounds the delay at a few milliseconds.\n if ((nonce & 1023) === 0) {\n if (signal?.aborted) {\n throw new DOMException('proof-of-work solve aborted', 'AbortError');\n }\n if (now() > deadline) {\n throw new Error(\n `proof-of-work: gave up on difficulty ${challenge.difficulty} after ${POW_TIME_BUDGET_MS / 1000}s ` +\n `and ${nonce.toLocaleString()} attempts — the tail this run drew is longer than the allowance`,\n );\n }\n }\n\n // THE DECISION, TAKEN ONCE AND TAKEN EARLY.\n //\n // After CALIBRATION_DIGESTS real attempts the rate of THIS process is\n // known, so the question \"can this machine pay this difficulty\" has an\n // answer instead of an assumption. If the whole budget cannot fit in the\n // time budget, refuse here — milliseconds in, with the numbers — rather\n // than spend two minutes and throw them away. If it fits, everything after\n // this point is guaranteed to finish inside the window, so the only\n // remaining failure is the budget's own e^-8.\n if (nonce === CALIBRATION_WARMUP) {\n warmedAt = now();\n }\n if (!calibrated && nonce === CALIBRATION_END) {\n calibrated = true;\n const elapsed = Math.max(now() - warmedAt, 0.001);\n const rate = (CALIBRATION_END - CALIBRATION_WARMUP) / (elapsed / 1000);\n // EXPECTED, not worst case, and the difference is the whole judgement.\n //\n // Finding a nonce is geometric: 2^difficulty attempts on average, with a\n // long tail the 8x budget covers. Refusing because the TAIL will not fit\n // would turn away work whose expected cost is seventeen seconds — measured\n // exactly that on this machine at difficulty 24. Refusing on the EXPECTED\n // cost turns away only what is genuinely unpayable here, and what it lets\n // through is then cut by the clock with probability e^-(budget/expected):\n // at 120s against a 17s expectation that is one run in a thousand, and at\n // difficulty 20 on a browser it is one in a hundred and fifty thousand.\n const expectedMs = (2 ** challenge.difficulty / rate) * 1000;\n if (expectedMs > POW_TIME_BUDGET_MS) {\n throw new Error(\n `proof-of-work: difficulty ${challenge.difficulty} needs about ${Math.round(expectedMs / 1000)}s here ` +\n `(${Math.round(rate).toLocaleString()} digests/s) and this client allows ${POW_TIME_BUDGET_MS / 1000}s; ` +\n `refusing before spending the time rather than after`,\n );\n }\n deadline = now() + (POW_TIME_BUDGET_MS - (now() - warmedAt));\n }\n\n const hash = await digest(challenge.prefix + nonce);\n if (leadingZeroBits(hash) >= challenge.difficulty) {\n return {\n [POW_CHALLENGE_ID_HEADER]: challenge.id,\n [POW_NONCE_HEADER]: String(nonce),\n };\n }\n }\n throw new Error(\n `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`,\n );\n}\n","export type Platform = 'browser' | 'node' | 'react-native' | 'deno' | 'bun';\n\ndeclare const Deno: unknown;\ndeclare const process: { versions: Record<string, string> } | undefined;\n\nexport function detectPlatform(): Platform {\n if (typeof Deno !== 'undefined') {\n return 'deno';\n }\n\n if (process?.versions) {\n if ('bun' in process.versions) {\n return 'bun';\n }\n if ('node' in process.versions) {\n return 'node';\n }\n }\n\n if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {\n return 'react-native';\n }\n\n return 'browser';\n}\n\n/**\n * The platform word this SDK puts on the wire (`X-Platform`), which the server\n * reads to target flags and to label telemetry.\n *\n * It is NOT `detectPlatform()`'s value verbatim: that reports the JS host\n * (\"browser\"), while the wire wants the platform. iOS sends \"ios\", not the name\n * of its runtime, and a condition author writes `client.platform == 'web'` —\n * the word every other flag vendor uses too. Server hosts keep their own names,\n * where the distinction is the useful part.\n */\nexport function wirePlatform(): string {\n const host = detectPlatform();\n return host === 'browser' ? 'web' : host;\n}\n","import { PalbaseError } from './errors.js';\nimport { asPowChallenge, solvePowChallenge } from './pow.js';\nimport { wirePlatform } from './platform.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\n/**\n * Default production host. Dev / staging / local callers override via\n * `options.url`. Apex-style routing is the only supported production path;\n * Kong resolves Environment identity from the API key.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse the Environment ref from a Palbase API key.\n *\n * Canonical shape: `pb_{environment_ref}_c{random}`, where the Environment ref\n * is 4-24 lowercase ASCII alphanumeric characters and random is AT LEAST 20\n * base62 chars.\n *\n * The length is a floor, not an equality. The stack's own minter writes 20\n * (v2/cmd/palsvc/initenv.go) and the cloud control plane writes 32\n * (v2-cloud/platform/server/services/keys.ts), and the door that admits the\n * request refuses to rule on the difference: *\"a shorter or longer secret is\n * not a security property this door can rule on\"*\n * (v2/internal/platform/identitymw.go: parseAPIKey). A client that is stricter\n * than the server does not add safety — it just refuses working keys, which is\n * exactly what this one did to every cloud project until 2026-08-25.\n *\n * Returns the Environment ref on match; `null` otherwise.\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20,}$/;\n\nfunction parseEnvironmentRef(apiKey: string): string | null {\n return API_KEY_RE.exec(apiKey)?.[1] ?? null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n/**\n * Upper bound on a single 429 retry sleep. A server may return a long\n * Retry-After (a locked account can send minutes/hours); honoring it verbatim\n * would HANG the request for that whole window. Cap each retry at 10s — after\n * MAX_RETRIES the 429 envelope surfaces to the caller (fail fast, don't sleep\n * minutes). The clamp never skips a retry; it only bounds how long each waits.\n */\nconst MAX_RETRY_DELAY_MS = 10_000;\n\n/**\n * Carry a 429's retry hint into the error envelope when only the header has it.\n *\n * A REFUSAL FROM THE EDGE CARRIES NOTHING BUT THE HEADER. Envoy and the\n * gateway limiter answer before any Palbase service is reached, so their 429\n * has no `retry_after` and no `data.retryAfter` — and every reader above this\n * layer (`@palbase/web`'s BackendError, the iOS SDK) looks in the BODY. The\n * seconds were on the wire and unreachable to all of them.\n *\n * Lifted under `retry_after`, the platform's own name for it (palauth's\n * rate-limit envelope), never overwriting a hint the service itself sent — a\n * service knows its window, the edge only knows its own.\n */\nfunction withRetryHint(\n body: Record<string, unknown> | undefined,\n response: Response,\n): Record<string, unknown> | undefined {\n if (response.status !== 429) return body;\n const data = body?.data;\n const alreadyStated =\n typeof body?.retry_after === 'number' ||\n (typeof data === 'object' && data !== null && 'retryAfter' in data);\n if (alreadyStated) return body;\n const seconds = Number.parseInt(response.headers.get('Retry-After') ?? '', 10);\n if (Number.isNaN(seconds) || seconds <= 0) return body;\n return { ...body, retry_after: seconds };\n}\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport type RequestInterceptor = (request: {\n headers: Record<string, string>;\n method: string;\n path: string;\n}) => void | Promise<void>;\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | null = null;\n\n /**\n * Admin JWT used for platform admin endpoints (/admin/*).\n * When set, takes precedence over tokenManager access token in the\n * Authorization header.\n */\n adminToken: string | null = null;\n\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(apiKey: string, options?: HttpClientOptions) {\n this.apiKey = apiKey;\n this.options = options;\n }\n\n /** Set (or clear) the admin JWT used on admin endpoints. */\n setAdminToken(token: string | null): void {\n this.adminToken = token;\n }\n\n /**\n * Create a scoped HttpClient that adds the given extra headers to every\n * request. The returned client shares the admin token and token manager\n * with the parent at runtime — later changes on the parent propagate to\n * the scope and vice versa.\n *\n * Typical use: adding an Environment-routing header for an admin call.\n */\n withHeaders(extra: Record<string, string>): HttpClient {\n const mergedHeaders = { ...(this.options?.headers ?? {}), ...extra };\n\n const scoped: HttpClient = new HttpClient(this.apiKey, {\n ...this.options,\n headers: mergedHeaders,\n });\n scoped.tokenManager = this.tokenManager;\n // Delegate adminToken reads + writes to the parent so the scope always\n // sees the latest token, and setAdminToken on the scope affects the parent.\n Object.defineProperty(scoped, 'adminToken', {\n get: () => this.adminToken,\n set: (v: string | null) => {\n this.adminToken = v;\n },\n configurable: true,\n });\n return scoped;\n }\n\n /** Add a request interceptor. Runs before every request. */\n addInterceptor(interceptor: RequestInterceptor): void {\n this.interceptors.push(interceptor);\n }\n\n async request<T>(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<PalbaseResponse<T>> {\n // If token is expired and refresh is available, refresh before making the request\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead (revoked/expired/forbidden).\n // Clear the session (listeners persist the sign-out) and proceed\n // unauthenticated — the endpoint will 401 into the normal error\n // envelope instead of bricking every subsequent call including\n // the recovery sign-in.\n this.tokenManager.clearSession();\n } else {\n throw e; // network/5xx: transient, stay loud\n }\n }\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n /**\n * A response read as it arrives, for `text/event-stream` routes.\n *\n * Deliberately NOT `executeWithRetry`: a retry replays the request, and a\n * stream the caller has already begun reading cannot be replayed — the frames\n * it handed over would arrive a second time. A stream that fails to open fails\n * to the caller, once, with its status.\n *\n * The buffered path's headers, base URL and interceptors are reused verbatim,\n * so a streaming call is authenticated exactly like every other call; only the\n * body handling differs. `Accept` says what the caller wants, and the status\n * is returned beside the body because the CALLER decides what a non-2xx means\n * (an error envelope arrives as an ordinary buffered body).\n */\n async requestStream(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<{ status: number; body: ReadableStream<Uint8Array> | null; contentType: string }> {\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n this.tokenManager.clearSession();\n } else {\n throw e;\n }\n }\n }\n\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), Accept: 'text/event-stream' };\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = { method, headers, signal: options?.signal };\n if (options?.body !== undefined) fetchOptions.body = JSON.stringify(options.body);\n\n const response = await fetch(url, fetchOptions);\n return {\n status: response.status,\n body: response.body,\n contentType: response.headers.get('content-type') ?? '',\n };\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, staging, test rigs).\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Validate the key shape up front so apex-routed callers still\n // fail loud on a malformed key instead of hitting the gateway\n // with bad credentials.\n if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{environment_ref}_c{at least 20 base62 chars}. For dev/staging pass `url: \"https://api.dev.palbase.studio\"` via options.',\n 0,\n );\n }\n\n return `https://${PALBASE_DEFAULT_HOST}`;\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n // Client identity, the web counterpart of the iOS SDK's\n // ClientInfo.augment(). The server reads these to resolve flag targeting\n // conditions and to label telemetry, so an app declares nothing and calls\n // nothing — whatever the SDK can know, it sends.\n 'X-Platform': wirePlatform(),\n };\n // The host app's own version is not knowable on the web (no bundle to read\n // it from), so it is opt-in; when given it fills the same header iOS fills\n // from CFBundleShortVersionString.\n const appVersion = this.options?.appVersion?.trim();\n if (appVersion) {\n headers['X-Palbase-Client-Version'] = appVersion;\n }\n\n // Palbase Environment keys live in the `apikey` header — never in\n // `Authorization` — because Kong's key-auth resolves them on that\n // header and the gateway's pre-function plugin stamps the downstream\n // identity.\n const effectiveKey = this.apiKey;\n if (effectiveKey) {\n headers['apikey'] = effectiveKey;\n }\n\n // User session token, if any. Kong's pre-function plugin strips\n // Authorization on /v1/* routes anyway (PostgREST has no JWT\n // secret and would crash on a Bearer it can't decode), but\n // sending it preserves the contract for /auth/* endpoints that\n // do consume the bearer (e.g. session refresh).\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // adminToken (platform admin JWT) takes precedence — used by the\n // @palbase/admin internal flows that hit /admin/* routes; those\n // routes verify the bearer themselves and aren't subject to the\n // /v1/* Authorization-strip rule.\n if (this.adminToken) {\n headers['Authorization'] = `Bearer ${this.adminToken}`;\n }\n\n // Merge global custom headers\n if (this.options?.headers) {\n Object.assign(headers, this.options.headers);\n }\n\n // Merge per-request headers\n if (options?.headers) {\n Object.assign(headers, options.headers);\n }\n\n return headers;\n }\n\n private async executeWithRetry<T>(\n method: string,\n path: string,\n options: RequestOptions | undefined,\n attempt: number,\n // Headers a PREVIOUS attempt earned and this one has to carry. Today that\n // is only the solved proof-of-work pair; it is a parameter rather than a\n // field because it belongs to one request's second try, and a field would\n // leak it onto every later call made through this client.\n earned?: Record<string, string>,\n ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), ...earned };\n\n // Run interceptors\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n signal: options?.signal,\n };\n\n if (options?.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, fetchOptions);\n } catch (error) {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;\n await this.delay(backoff);\n // WITHOUT `earned`, and that is the whole point of this line.\n //\n // A network error means the response was lost, not that the request\n // was. If it reached the server, the challenge is already SPENT —\n // palauth's VerifyChallenge reads and deletes in one step\n // (bot/pow.go:96-104), deliberately, because a proof presented twice is\n // not proof. Replaying the nonce would then answer `pow_invalid`, and\n // the one-solve guard below would refuse to try again: a request one\n // fresh solve away from succeeding, failed. Dropping it costs nothing\n // in the other case — if the server never saw the request, a fresh\n // challenge works exactly as well as the old one.\n return this.executeWithRetry<T>(method, path, options, attempt + 1);\n }\n\n // All retries exhausted — throw PalbaseError\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n // Handle 429 Too Many Requests — retry with Retry-After or backoff;\n // if retries exhausted, fall through to normal error response handling below\n if (response.status === 429) {\n if (attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;\n // Clamp the server-requested wait: a long Retry-After (locked account)\n // must not hang the request — cap each sleep, exhaust MAX_RETRIES, then\n // fall through to surface the 429 envelope below.\n const delayMs = Number.isNaN(parsed)\n ? INITIAL_BACKOFF_MS * 2 ** attempt\n : Math.min(parsed * 1000, MAX_RETRY_DELAY_MS);\n await this.delay(delayMs);\n // WITH `earned`, unlike the network path above: a 429 is a refusal the\n // server issued INSTEAD of doing the work, so the challenge was never\n // consumed. The edge's rate limiter answers before palsvc, and on the\n // auth routes palauth's own limiter runs BEFORE the proof-of-work\n // middleware (auth/internal/server/server.go: rl.LoginByIP, then powMW).\n return this.executeWithRetry<T>(method, path, options, attempt + 1, earned);\n }\n }\n\n // Parse response body\n let data: T | null = null;\n let errorBody: { error?: string; error_description?: string; status?: number } | undefined;\n\n // HEAD responses have no body by spec — skip parsing.\n const contentType = response.headers.get('Content-Type');\n if (method !== 'HEAD' && contentType?.includes('json')) {\n const body = (await response.json()) as Record<string, unknown>;\n if (response.ok) {\n data = body as T;\n } else {\n errorBody = body as typeof errorBody;\n }\n }\n\n // Proof-of-work: /auth/signup and /auth/token sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it and repeat the request carrying the two headers; the caller never\n // learns the gate is there.\n //\n // HERE, in core, because this is the layer that issues the request for every\n // client in the repo — @palbase/auth's sign-in, @palbase/web's facades, the\n // server SDK. The same retry lived one layer up in @palbase/web until\n // 2026-08-18 and covered everything EXCEPT `pb.auth.*`, which reaches the\n // network through this method; so the gate stayed unsatisfiable on exactly\n // the two endpoints it guards.\n //\n // ONE retry, and only when the body really carries a challenge: `earned`\n // being set already means this IS the second try. A 403 that says\n // pow_required without a challenge is a server the client cannot satisfy,\n // and looping on it would turn a broken gate into a hang.\n if (response.status === 403 && !earned) {\n const challenge = asPowChallenge(errorBody);\n if (challenge) {\n return this.executeWithRetry<T>(\n method,\n path,\n options,\n attempt,\n await solvePowChallenge(challenge, undefined, options?.signal),\n );\n }\n }\n\n if (!response.ok) {\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'unknown_error',\n errorBody?.error_description ?? response.statusText,\n response.status,\n withRetryHint(errorBody, response),\n ),\n status: response.status,\n };\n }\n\n // Parse PostgREST Content-Range for count queries (e.g. \"0-9/42\" or \"*/42\").\n const contentRange = response.headers.get('Content-Range');\n let count: number | undefined;\n if (contentRange) {\n const slash = contentRange.lastIndexOf('/');\n if (slash >= 0) {\n const totalPart = contentRange.slice(slash + 1);\n if (totalPart !== '*') {\n const parsed = Number.parseInt(totalPart, 10);\n if (!Number.isNaN(parsed)) {\n count = parsed;\n }\n }\n }\n }\n\n return {\n data,\n error: null,\n status: response.status,\n ...(count !== undefined ? { count } : {}),\n };\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { AuthStateCallback, Session, Unsubscribe } from './types.js';\n\nexport class TokenManager {\n private session: Session | null = null;\n private listeners: Set<AuthStateCallback> = new Set();\n private refreshPromise: Promise<void> | null = null;\n private refreshing = false;\n\n refreshFunction: ((refreshToken: string) => Promise<Session>) | null = null;\n\n setSession(session: Session): void {\n this.session = session;\n this.notify('SESSION_SET', session);\n }\n\n getAccessToken(): string | null {\n return this.session?.accessToken ?? null;\n }\n\n getRefreshToken(): string | null {\n return this.session?.refreshToken ?? null;\n }\n\n clearSession(): void {\n this.session = null;\n this.notify('SESSION_CLEARED', null);\n }\n\n isExpired(): boolean {\n if (!this.session) return true;\n return Date.now() >= this.session.expiresAt;\n }\n\n async refreshSession(): Promise<void> {\n if (!this.session?.refreshToken || !this.refreshFunction) {\n return;\n }\n\n // Collapse concurrent refresh calls into a single request\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // Re-entrancy guard: the wired refreshFunction issues its own HTTP request\n // (POST /auth/token/refresh) through HttpClient, whose pre-flight calls\n // refreshSession() again SYNCHRONOUSLY — before `refreshPromise` below is\n // assigned (the whole chain runs before the first real await). Without\n // this flag that recursion is unbounded (stack overflow). Returning early\n // lets the refresh request itself proceed unauthenticated — it carries\n // the refresh token in its body, not the Bearer header.\n if (this.refreshing) {\n return;\n }\n\n this.refreshing = true;\n this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshing = false;\n }\n }\n\n onAuthStateChange(callback: AuthStateCallback): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n private async executeRefresh(refreshToken: string): Promise<void> {\n if (!this.refreshFunction) return;\n const newSession = await this.refreshFunction(refreshToken);\n this.setSession(newSession);\n }\n\n private notify(event: 'SESSION_SET' | 'SESSION_CLEARED', session: Session | null): void {\n for (const listener of this.listeners) {\n listener(event, session);\n }\n }\n}\n","/** The client a tenant's tests use to call their own backend.\n *\n * These tests run against a REAL deployment — the release the deploy just built,\n * serving from the same Environment as production, with the same database, the\n * same secrets and the same gateway in front of it. So this client is a plain\n * HTTP client, not a simulation: every call crosses the gateway, the API key\n * check, the auth rail, the zod validation at the boundary, and row-level\n * security, exactly as a shipped app's call does.\n *\n * There is deliberately no schema knowledge here. The tenant already wrote their\n * types — `import type { TodoSchema } from \"../models/todos/shared.js\"` — so a\n * test types its own call (`api.get<TodoSchema[]>(\"/todos\")`) and can validate it\n * with the same zod schema the endpoint declares. A second generated client would\n * be a second thing to keep in step.\n */\n\nimport { asPowChallenge, solvePowChallenge } from \"@palbase/core\";\n\n/** How to reach the release under test. Supplied by the deploy, never guessed. */\nexport interface TestApiConfig {\n baseUrl: string;\n apiKey: string;\n /** This deploy's secret. Without it the request is served the LIVE release.\n *\n * OPTIONAL against a stack running on this machine: a local stack serves one\n * version — the directory `palbase start` mounted — so there is no candidate\n * to select. Required everywhere else. */\n candidateToken?: string;\n /** The run's minted logins, keyed by the name declared in config/test-users.ts. */\n identities?: Record<string, TestIdentity>;\n /** The fetch to use. Injected by tests of this client; production passes none. */\n fetch?: typeof fetch;\n}\n\n/** One login the deploy minted for this run.\n *\n * `accessToken` is the session issued when the identity was created, and is what\n * `signInAs` uses. The credentials come along for a test that wants to exercise\n * the login rail itself — but a suite that switches users repeatedly must not be\n * signing in each time: those calls come from one address and trip the login rate\n * limiter, failing tests for a reason that has nothing to do with the code\n * under test.\n */\nexport interface TestIdentity {\n id?: string;\n email: string;\n password: string;\n accessToken?: string;\n}\n\n/** One call the suite made, in the order it was made. */\nexport interface RecordedRequest {\n method: string;\n path: string;\n status: number;\n ms: number;\n}\n\n/** A non-2xx answer, carrying the platform's error envelope.\n *\n * The envelope is the contract every Palbase endpoint answers with, so a test\n * asserts on `status`/`error`/`data` rather than parsing a message. The message\n * exists for the human reading a failed deploy.\n */\nexport class TestApiError extends Error {\n readonly status: number;\n readonly error: string;\n /** Payload of an error your code threw — `new BadRequest({ fields })` arrives here. */\n readonly data: unknown;\n /** The whole envelope, exactly as the server sent it. */\n readonly body: ErrorEnvelope;\n\n constructor(method: string, path: string, status: number, body: unknown) {\n const envelope = (body ?? {}) as ErrorEnvelope;\n const code = envelope.error ?? String(status);\n super(`${method} ${path} → ${status} ${code}${envelope.error_description ? `: ${envelope.error_description}` : \"\"}`);\n this.name = \"TestApiError\";\n this.status = status;\n this.error = code;\n this.data = envelope.data;\n this.body = envelope;\n }\n}\n\n/** A Palbase error response.\n *\n * `data` carries the payload of an error your code threw. Validation refused at\n * the boundary — before your handler runs — answers with `details` instead, one\n * entry per field. The index signature is deliberate: whatever the server sends\n * is readable from a test, so no assertion is ever blocked on this type being\n * exhaustive.\n */\nexport interface ErrorEnvelope {\n error?: string;\n error_description?: string;\n status?: number;\n request_id?: string;\n data?: unknown;\n details?: Array<{ field: string; message: string }>;\n [key: string]: unknown;\n}\n\nexport interface CallOptions {\n headers?: Record<string, string>;\n}\n\nexport interface TestApi {\n get<T = unknown>(path: string, opts?: CallOptions): Promise<T>;\n post<T = unknown>(path: string, body?: unknown, opts?: CallOptions): Promise<T>;\n patch<T = unknown>(path: string, body?: unknown, opts?: CallOptions): Promise<T>;\n put<T = unknown>(path: string, body?: unknown, opts?: CallOptions): Promise<T>;\n delete<T = unknown>(path: string, opts?: CallOptions): Promise<T>;\n /** HTTP QUERY (RFC 10008): a safe, idempotent read whose filter travels in the body. */\n query<T = unknown>(path: string, body?: unknown, opts?: CallOptions): Promise<T>;\n /** Sign in with credentials you supply. */\n signIn(credentials: { email: string; password: string }): Promise<{ id: string; email?: string }>;\n /**\n * Sign in as one of the identities this run was given, by the name you\n * declared it under in `config/test-users.ts`.\n *\n * Each is minted by the platform for the length of ONE deploy, seeded with the\n * data that declaration describes, and retired after — which is why this works\n * on every Environment including production, where a committed fixture password\n * is refused on purpose.\n */\n signInAs(name: string): Promise<{ id: string; email?: string }>;\n signOut(): Promise<void>;\n /** Drop the bearer without calling the server — the anonymous caller. */\n asAnonymous(): void;\n /** Every call made, in order. Printed for the failing test in a red deploy. */\n readonly requests: readonly RecordedRequest[];\n}\n\nfunction required(value: string, envName: string): string {\n if (!value) {\n throw new Error(\n `${envName} is not set — the test client has nowhere to send requests. ` +\n `This is set by the deploy that runs your tests; if you are running them by hand, set it yourself.`,\n );\n }\n return value;\n}\n\n/** A stack running on this machine. There is exactly ONE version there — the\n * directory `palbase start` mounted — so there is no candidate to select, and\n * demanding a token for one made local runs invent a value to satisfy a header\n * nothing reads. */\nfunction isLocalTarget(baseUrl: string): boolean {\n try {\n const { hostname } = new URL(baseUrl);\n return hostname === \"127.0.0.1\" || hostname === \"localhost\" || hostname === \"[::1]\" || hostname === \"::1\";\n } catch {\n return false;\n }\n}\n\n/** Seconds until a JWT's `exp`, or null when the token carries no readable one.\n * Read WITHOUT verifying: this is a diagnosis, never a decision — the server\n * remains the only authority on whether a token is good. */\nfunction secondsUntilExpiry(token: string): number | null {\n const body = token.split(\".\")[1];\n if (!body) return null;\n try {\n const claims = JSON.parse(Buffer.from(body, \"base64url\").toString(\"utf8\")) as { exp?: unknown };\n return typeof claims.exp === \"number\" ? claims.exp - Math.floor(Date.now() / 1000) : null;\n } catch {\n return null;\n }\n}\n\nexport function createTestApi(config: TestApiConfig): TestApi {\n const baseUrl = required(config.baseUrl, \"PALBASE_TEST_BASE_URL\").replace(/\\/$/, \"\");\n const apiKey = required(config.apiKey, \"PALBASE_TEST_API_KEY\");\n const local = isLocalTarget(baseUrl);\n // Local stacks serve one version, so there is nothing to select. Everywhere\n // else the token stays REQUIRED: without it the gateway serves the LIVE\n // release and the suite would grade code that is not under test.\n const candidateToken = local ? (config.candidateToken ?? \"\") : required(config.candidateToken ?? \"\", \"PALBASE_TEST_CANDIDATE\");\n const doFetch = config.fetch ?? fetch;\n\n const requests: RecordedRequest[] = [];\n let bearer: string | null = null;\n\n async function call<T>(method: string, path: string, body: unknown, opts: CallOptions = {}): Promise<T> {\n const headers: Record<string, string> = {\n apikey: apiKey,\n // Selects the release under test. Omit it and the gateway serves the LIVE\n // one, which would make the whole suite grade the wrong code. Absent only\n // against a local stack, which has a single version.\n ...(candidateToken ? { \"x-palbase-candidate\": candidateToken } : {}),\n ...opts.headers,\n };\n if (bearer) headers.authorization = `Bearer ${bearer}`;\n if (body !== undefined) headers[\"content-type\"] = \"application/json\";\n\n const startedAt = Date.now();\n const res = await doFetch(`${baseUrl}${path}`, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n const text = await res.text();\n const parsed: unknown = text ? safeParse(text) : undefined;\n\n requests.push({ method, path, status: res.status, ms: Date.now() - startedAt });\n\n if (!res.ok) {\n // A 401 on a token that has simply RUN OUT is the most likely 401 a suite\n // sees, and the least legible: the mint issues ~30 minutes, so a file of\n // credentials written yesterday answers `401 unauthorized` with nothing to\n // act on. Measured on a customer run: the next step taken was to blame the\n // credentials rather than their age.\n if (res.status === 401 && bearer) {\n const left = secondsUntilExpiry(bearer);\n if (left !== null && left <= 0) {\n throw new TestApiError(method, path, res.status, {\n error: \"access_token_expired\",\n error_description:\n `this run's access token EXPIRED ${Math.abs(left)}s ago — a test identity is minted for the ` +\n `length of ONE deploy, so a saved token does not survive to the next run. Re-mint it ` +\n \"(`palbase test-user create --json`, or let `palbase test` do it) and run again.\",\n });\n }\n }\n throw new TestApiError(method, path, res.status, parsed);\n }\n return parsed as T;\n }\n\n return {\n requests,\n get: (path, opts) => call(\"GET\", path, undefined, opts),\n post: (path, body, opts) => call(\"POST\", path, body, opts),\n patch: (path, body, opts) => call(\"PATCH\", path, body, opts),\n put: (path, body, opts) => call(\"PUT\", path, body, opts),\n delete: (path, opts) => call(\"DELETE\", path, undefined, opts),\n query: (path, body, opts) => call(\"QUERY\", path, body, opts),\n\n async signInAs(name) {\n const identity = (config.identities ?? {})[name];\n if (!identity) {\n const declared = Object.keys(config.identities ?? {});\n throw new Error(\n `no test identity named ${JSON.stringify(name)} — the deploy mints one per user declared in ` +\n `config/test-users.ts` +\n (declared.length\n ? `; this run has: ${declared.join(\", \")}`\n : // Not \"you declared none\": from here the two causes are\n // indistinguishable, and blaming the customer's config for a\n // platform failure sends them to look in the wrong file. The\n // deploy log names which one it was.\n \", and this run has none — either your config declares no users \" +\n \"or the deploy could not mint them; the deploy log says which\"),\n );\n }\n // The session the mint already issued — no network call, so switching\n // users is free and the login rail never sees this run.\n if (identity.accessToken) {\n bearer = identity.accessToken;\n return { id: identity.id ?? \"\", email: identity.email };\n }\n return this.signIn(identity);\n },\n\n async signIn(credentials) {\n // PROOF-OF-WORK IS PART OF LOGGING IN, so a client that cannot solve one\n // cannot log in at all. The web SDK has solved it since bot protection\n // shipped; this harness went straight to `fetch` and therefore answered\n // `403 pow_required` on every password login — which made the whole\n // credentials path DEAD on a stack with the gate on, exactly when a\n // suite falls back to it because its minted token ran out.\n //\n // One retry, and only when the refusal really carries a challenge: a 403\n // saying pow_required without one is a server this client cannot satisfy,\n // and looping would turn a broken gate into a hang. Same rule as\n // @palbase/core's own retry.\n const attempt = async (extra?: Record<string, string>) =>\n call<{ access_token: string; user?: { id: string; email?: string } }>(\n \"POST\",\n \"/auth/login\",\n credentials,\n extra ? { headers: extra } : {},\n );\n\n let result: { access_token: string; user?: { id: string; email?: string } };\n try {\n result = await attempt();\n } catch (e) {\n const refusal = e as { status?: number; body?: unknown };\n const challenge = refusal.status === 403 ? asPowChallenge(refusal.body) : null;\n if (!challenge) throw e;\n result = await attempt(await solvePowChallenge(challenge));\n }\n bearer = result.access_token;\n return result.user ?? { id: \"\" };\n },\n async signOut() {\n await call(\"POST\", \"/auth/logout\", undefined);\n bearer = null;\n },\n asAnonymous() {\n bearer = null;\n },\n };\n}\n\n/** The run's identities, as the deploy passed them. Absent is not an error: a\n * project that declares none still runs every test that needs no login. */\nfunction parseIdentities(raw: string | undefined): Record<string, TestIdentity> {\n if (!raw) return {};\n try {\n return JSON.parse(raw) as Record<string, TestIdentity>;\n } catch {\n return {};\n }\n}\n\nfunction safeParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\n/** The client the deploy configured, from the environment it set.\n *\n * Constructed lazily so importing this module outside a test run — a typecheck,\n * an editor — does not fail on absent configuration.\n */\nlet configured: TestApi | null = null;\n\nexport const api: TestApi = new Proxy({} as TestApi, {\n get(_target, prop) {\n configured ??= createTestApi({\n baseUrl: process.env.PALBASE_TEST_BASE_URL ?? \"\",\n apiKey: process.env.PALBASE_TEST_API_KEY ?? \"\",\n candidateToken: process.env.PALBASE_TEST_CANDIDATE ?? \"\",\n identities: parseIdentities(process.env.PALBASE_TEST_IDENTITIES),\n });\n return Reflect.get(configured, prop, configured);\n },\n});\n","import \"reflect-metadata\";\n\nimport type { Token } from \"../container.js\";\n\nexport interface IsolatedContainer {\n /** Substitutes a token. Chainable; the last write for a token wins. */\n with<T>(t: Token<T>, v: T): IsolatedContainer;\n get<T>(t: Token<T>): T;\n}\n\n/**\n * How a test replaces a dependency.\n *\n * Rebuilds the graph with the overrides in place and never touches the process\n * singleton cache, so the next test in the same process does not meet a doubled\n * instance left behind by this one. Substitution is DEEP: `Report` asks for\n * `Money` and gets whatever the graph was rebuilt with, however many hops down.\n *\n * Substitution is by `with` alone — there is no separate platform map, because\n * platform services are ambient rather than injected (FR-005).\n *\n * Module boundaries are NOT enforced here, deliberately. They are a build-time\n * rule about the shipped application; making a unit test fail on them would\n * force every test to restate a module layout it is not testing. What a test\n * gets is a graph, not a second opinion about the architecture.\n */\nexport function isolated(): IsolatedContainer {\n const over = new Map<Token, unknown>();\n const local = new Map<Token, unknown>();\n\n const make = (c: Token): unknown => {\n if (over.has(c)) return over.get(c);\n const hit = local.get(c);\n if (hit !== undefined) return hit;\n const meta = (Reflect.getMetadata(\"design:paramtypes\", c) as unknown[] | undefined) ?? [];\n const inst = new (c as unknown as new (...a: unknown[]) => unknown)(\n ...meta.map((d) => make(d as Token)),\n );\n local.set(c, inst);\n return inst;\n };\n\n const api: IsolatedContainer = {\n with<T>(t: Token<T>, v: T): IsolatedContainer {\n over.set(t as Token, v);\n return api;\n },\n get<T>(t: Token<T>): T {\n return make(t as Token) as T;\n },\n };\n return api;\n}\n","/**\n * The refusals a Database call gets BEFORE any SQL exists — written once, so the\n * engine and the test double cannot disagree about them.\n *\n * WHY THIS FILE EXISTS. `fakeDatabase()` is a second implementation of the same\n * surface (`__tests__/helpers/mock-db.ts`), and it never touched `compileWhere`\n * or `asBindParams`. Measured against the published 24.1.0: all four of the\n * calls that release had just started refusing went through the fake SILENTLY —\n * `update{title:undefined}`, `insert{title:undefined}`, `findMany{done:{}}`,\n * `deleteMany{owner,created_at:{}}`.\n *\n * The scaffold tells authors to test the service layer against exactly that\n * fake. So a test went green on a call production would throw on, and the\n * author found out in production instead — the same \"the surface does not match\n * the engine\" shape these refusals exist to end, arriving through the door the\n * SDK hands people for testing.\n *\n * These are pure and SQL-free on purpose: an in-memory store can run them as\n * easily as the driver path can.\n */\n\n/**\n * İşaretçilerin MARKASI — `col()` ve `sqlFragment()` ürünlerini bu süreçte\n * üretilmiş olmakla tanımlar.\n *\n * NEDEN ŞEKİL DEĞİL DE MARKA (gözcü W2-A/C1 ve W2-B/C3, ikisi de ÖLÇTÜ):\n * şekil kontrolü, işaretçiyi güvenilmeyen bir istek gövdesinden UYDURULABİLİR\n * kılıyordu. Ölçülen iki sonuç:\n *\n * findMany(\"docs\", { owner_id: JSON.parse('{\"$col\":\"owner_id\"}') })\n * → WHERE true AND t.\"owner_id\" = t.\"owner_id\" ← kiracılık predikatı totoloji\n * findMany(\"todos\", JSON.parse('{\"$sql\":{\"text\":[\"1=1 -- pwned\"],\"values\":[]}}'))\n * → WHERE true AND 1=1 -- pwned ← saldırganın metni SQL'e HARFİYEN\n *\n * `{ where: { tenant_id: tid, ...req.body.filter } }` bu SDK'nın öğrettiği\n * desen; T010/T014 öncesinde aynı anahtarlar \"bilinmeyen operatör\" diye\n * REDDEDİLİYORDU. Marka o reddi geri getiriyor.\n *\n * Sembol GLOBAL kayıttan (`Symbol.for`) ve ENUMERABLE DEĞİL. İkisi de kasıtlı:\n * global kayıt paketin iki kopyası arasında da eşleşir; enumerable olmaması ise\n * `JSON.stringify` ve `{...ref, gt: 5}` yayılımının markayı DÜŞÜRMESİNİ sağlar —\n * yani telden geçen ya da elle karıştırılan hiçbir şey işaretçi sayılmaz.\n * Kardeş özellik (`increment`) zaten `Symbol.for(\"palbase.tx.expr\")` kullanıyor;\n * bu onun aynısı.\n */\nconst REF_BRAND = Symbol.for(\"palbase.db.ref\");\n\n/** İşaretçiyi markalar. Yalnız `col()` ve `sqlFragment()` çağırır. */\nexport function brandRef<T extends object>(v: T, kind: \"col\" | \"sql\" | \"ref\"): T {\n Object.defineProperty(v, REF_BRAND, { value: kind, enumerable: false });\n return v;\n}\n\nfunction brandOf(v: unknown): unknown {\n if (typeof v !== \"object\" || v === null) return undefined;\n // KENDİ özelliği olmalı, prototip zincirinden MİRAS ALINMIŞ değil:\n // `Object.create(col(\"x\"))` markayı zincirden okuyup işaretçi sayılıyordu\n // (gözcü ölçtü). Telden erişilemez — JSON `__proto__` üstünden sembol\n // yazamaz — ama daraltmak bedava ve \"işaretçi bu süreçte ÜRETİLDİ\"\n // iddiasının tam karşılığı budur.\n return Object.hasOwn(v, REF_BRAND) ? (v as Record<symbol, unknown>)[REF_BRAND] : undefined;\n}\n\n/**\n * Bir değer, MARKASIZ bir işaretçi taklidi mi? (`{$col:…}` / `{$sql:…}`)\n *\n * Üst düzeyde bunlar zaten \"bilinmeyen operatör\" diye reddediliyor. Ama\n * operatörün SAĞINDA — `{ amount: { gt: {\"$col\":\"other\"} } }` — sessizce\n * PARAMETRE olarak bağlanıyorlardı: sayısal kolonda sürücünün 22P02'si,\n * jsonb/text kolonunda ise HİÇBİR SATIR, hatasız (gözcü ölçtü).\n *\n * `in` listesindeki aynı kusur adıyla reddediliyor; bu onun bir seviye\n * yanındaki hâli ve aynı cevabı hak ediyor.\n */\nexport function looksLikeUnbrandedRef(v: unknown): \"col\" | \"sql\" | null {\n if (typeof v !== \"object\" || v === null) return null;\n if (brandOf(v) !== undefined) return null; // gerçek işaretçi\n const o = v as { $col?: unknown; $sql?: unknown };\n if (typeof o.$col === \"string\") return \"col\";\n if (o.$sql !== undefined && typeof o.$sql === \"object\" && o.$sql !== null) return \"sql\";\n return null;\n}\n\n/**\n * `col()` ürünü mü? (FR-011)\n *\n * Burada, çünkü bu dosya \"iki uygulamanın da okuduğu kurallar\" dosyası: motor,\n * `fakeDatabase` ve guard AYNI cevabı vermek zorunda.\n */\nexport function isColRef(v: unknown): v is { readonly $col: string } {\n return brandOf(v) === \"col\" && typeof (v as { $col?: unknown }).$col === \"string\";\n}\n\n/**\n * `sqlFragment` ürünü mü? (FR-018)\n *\n * `isColRef` ile aynı gerekçeyle burada: motor, guard ve `fakeDatabase` üçü de\n * aynı cevabı vermek zorunda — biri fragment'i \"kolon haritası\" sanarsa filtre\n * sessizce düşer.\n */\n/**\n * Plan REFERANSI mı? (`{ $ref: { op, field } }`)\n *\n * `$ref` bu dilin MARKASIZ KALAN TEK işaretçisiydi — `engine/db.ts` onu\n * `\"$ref\" in v` diye tanıyordu — ve SDK'nın öğrettiği desen\n * `{ where: { tenant_id: tid, ...req.body.filter } }`. Ölçüldü (gözcü):\n * istek gövdesinden gelen `{\"id\":{\"$ref\":{\"op\":0,\"field\":\"id\"}}}` filtreyi\n * ÖNCEKİ bir işlemin satır değeriyle karşılaştırtıyor —\n * DELETE … WHERE t.\"tenant_id\" = $1 AND t.\"id\" = $2 PRM [\"t1\",\"SIZAN_DEGER\"]\n * gövdenin hiç görmediği bir değer. Enjeksiyon değil (değerler bound) ama bir\n * ORACLE: `op`/`field` seçip `rows_affected`'tan o değeri öğrenmek.\n *\n * Marka `Symbol.for` olduğu için SDK'nın İKİ KOPYASI arasında da eşleşiyor —\n * kontrolcü bundle'ı kendi kopyasını inline ediyor, planı çalıştıran ise\n * runtime'ınki. Ve plan gövdesi JSON'lanmıyor: tek üretim `txPlan` uygulaması\n * süreç içi (`engine/db.ts`), doğrulandı.\n */\nexport function isPlanRef(v: unknown): v is { readonly $ref: { op: number; field: string } } {\n if (brandOf(v) !== \"ref\") return false;\n const r = (v as { $ref?: { op?: unknown; field?: unknown } }).$ref;\n return r !== undefined && typeof r.op === \"number\" && typeof r.field === \"string\";\n}\n\n/** Markasız bir `{ $ref: … }` taklidi mi? Adıyla reddedilmesi için. */\nexport function looksLikeUnbrandedPlanRef(v: unknown): boolean {\n if (typeof v !== \"object\" || v === null || brandOf(v) !== undefined) return false;\n const r = (v as { $ref?: unknown }).$ref;\n return r !== undefined && typeof r === \"object\" && r !== null;\n}\n\nexport function isSqlFragment(v: unknown): v is { readonly $sql: { text: string[]; values: unknown[] } } {\n if (brandOf(v) !== \"sql\") return false;\n const f = (v as { $sql?: { text?: unknown; values?: unknown } }).$sql;\n return f !== undefined && Array.isArray(f.text) && Array.isArray(f.values);\n}\n\n/**\n * İFADE TUTAMAĞI DEĞER DEĞİLDİR — değer bekleyen yollarda adıyla reddedilir.\n *\n * `increment()` / `decrement()` / `now()` bir Proxy döndürür ve yalnız\n * `updateMany` ile plan yolunun `updateWhere`'i onu SQL'e derler. `insert` /\n * `update` / `put` / `supersede` derlemez; oralarda tutamak bound parametre\n * olarak sürücüye gidiyordu ve reddi SÜRÜCÜ veriyordu (\"Unknown object is not\n * a valid PostgreSQL type\") — yazarın yazdığı hiçbir şeyi adlandırmayan bir\n * mesaj (inceleme I-2/I8, ölçüldü). Plan yolu aynı hatayı kendi diliyle\n * reddediyor; bu, doğrudan yolun karşılığı.\n *\n * Sembol `tx-plan.ts`'in markasıyla AYNI global kayıttan okunuyor; bu dosya\n * kural dosyası olduğu için oraya bağımlılık kurmuyor.\n */\nconst TX_EXPR = Symbol.for(\"palbase.tx.expr\");\n\nexport function isColumnExpr(v: unknown): boolean {\n if (typeof v !== \"object\" && typeof v !== \"function\") return false;\n if (v === null) return false;\n try {\n return (v as Record<symbol, unknown>)[TX_EXPR] !== undefined;\n } catch {\n // Tutamak bir Proxy; bilinmeyen bir prop'ta trap fırlatabilir.\n return false;\n }\n}\n\n/**\n * Değer bekleyen bir yazma yolunda ifade tutamağı ya da `col()` var mı?\n *\n * Motor ve `fakeDatabase` AYNI cevabı vermek zorunda: fake tutamağı satıra\n * YAZIYORDU (`row[k] = proxy`) ve satır artık JSON'a bile çevrilemiyordu, motor\n * ise sürücüde patlıyordu. İki farklı yanlış, tek doğru.\n */\nexport function assertNoExpressionHandles(\n caller: string,\n table: string,\n cols: readonly string[],\n data: Record<string, unknown>,\n): void {\n for (const c of cols) {\n const v = data[c];\n if (isColumnExpr(v)) {\n throw new Error(\n `${caller}(${table}): \"${c}\" bir ifade tutamağı aldı (increment()/decrement()/now()). ` +\n `Bu yolda değer beklenir. Sayaç artışı için updateMany(where, { ${c}: increment(n) }) ` +\n `ya da $transaction içinde tx.tables.${table}.updateWhere(where, { ${c}: increment(n) }) kullanın.`,\n );\n }\n if (isColRef(v)) {\n throw new Error(\n `${caller}(${table}): \"${c}\" bir col() aldı. Kolon referansı yalnız FİLTREDE durabilir; ` +\n `bir kolonun değerini başka bir kolona yazmak için $query kullanın.`,\n );\n }\n }\n}\n\n/** The comparison operators a filter value may carry. Kept here because the\n * guard has to tell an operator object from a plain value. */\nconst KNOWN_OPS = new Set([\n \"gt\", \"gte\", \"lt\", \"lte\", \"neq\", \"in\",\n // K1 metin operatörleri (FR-005) ve null testi (FR-006). Bu küme\n // `fakeDatabase()` ile ORTAK kaynaktır: fake bir çağrıyı motorun reddettiği\n // yerde kabul ederse, yazarın testi üretimde patlayan koda karşı yeşil verir.\n \"contains\", \"icontains\", \"startsWith\", \"endsWith\", \"isNull\",\n]);\n\n/**\n * `eq` ADIYLA reddedilir, ve reddi buradadır çünkü guard'ı motor da fake de\n * okuyor.\n *\n * Eşitliğin yazımı ÇIPLAK DEĞERDİR: `{ owner: \"u1\" }`. `eq`'i ikinci bir yazım\n * olarak eklemek, bu run'ın kapatmak için var olduğu şeyi — aynı iş için iki\n * uyumsuz yazım — filtre dilinin İÇİNDE yeniden açardı. Ve eskiden kabul eden\n * ile reddeden ayrışıyordu: guard `eq`'i geçiriyor, derleyici\n * `bilinmeyen operatör \"eq\"` diyordu (gözcü ölçtü).\n */\nconst REFUSED_OPS: Record<string, string> = {\n eq: 'eşitlik ÇIPLAK yazılır: { <kolon>: <değer> } (ya da kolon karşılaştırması için { <kolon>: col(\"…\") })',\n};\n\n/**\n * Refuse a filter that would compile to something other than what it reads like.\n *\n * Three shapes, each measured in production before it was closed:\n *\n * `{ col: undefined }` binds NULL; `= NULL` matches no row, so the query\n * answered \"no records\" and said nothing.\n * `{ col: {} }` produces no term at all — every row on the read\n * path, a dropped condition on the write path.\n * `{ col: { gte: undefined } }` and an `undefined` inside `in`: the same NULL,\n * one level down.\n */\nexport function assertUsableFilter(\n caller: string,\n table: string,\n where: Record<string, unknown> | undefined,\n): void {\n // Bileşim anahtarları (FR-007) bir KOLON adı değildir; kolon doğrulamasından\n // ve operatör kontrolünden muaftır, kendi dalları özyinelemeli olarak aynı\n // kurallardan geçer.\n const COMPOSITES = new Set([\"OR\", \"AND\", \"NOT\"]);\n\n if (!where) return;\n // Fragment bir kolon haritası DEĞİLDİR (FR-018): içeriği SQL'dir, kolon\n // doğrulaması ona uygulanamaz. Değerleri zaten bound gidiyor.\n if (isSqlFragment(where)) return;\n for (const [col, cond] of Object.entries(where)) {\n // Bileşim anahtarları (FR-007) kolon DEĞİLDİR: dalları aynı kurallardan\n // özyinelemeli geçer, ama kendileri operatör kontrolüne girmez.\n if (COMPOSITES.has(col)) {\n const branches = col === \"NOT\" ? [cond] : cond;\n if (!Array.isArray(branches) && col !== \"NOT\") {\n throw new Error(`${caller}(${table}): where.${col} bir dizi olmalı`);\n }\n for (const b of branches as unknown[]) {\n if (b === null || typeof b !== \"object\") {\n throw new Error(`${caller}(${table}): where.${col} dalları filtre nesnesi olmalı`);\n }\n assertUsableFilter(caller, table, b as Record<string, unknown>);\n }\n continue;\n }\n // `has` de kolon DEĞİLDİR: anahtarları İLİŞKİ adları, değerleri BİR TABLO\n // ÖTESİNİN filtresi. İç filtre aynı kurallardan geçiyor — `has` ikinci bir\n // filtre dili değil, aynı dilin bir tablo ötesi.\n //\n // İlişki ADI burada doğrulanMIYOR: grafiği yalnız motor tanıyor (ve tip,\n // derleme anında). Guard'ın onu bilmesi ilişki grafiğinin İKİNCİ bir\n // yorumcusu demekti — `buildRelations`'ın yorumunun adıyla yasakladığı şey.\n if (col === \"has\") {\n if (cond === null || typeof cond !== \"object\" || Array.isArray(cond)) {\n throw new Error(`${caller}(${table}): where.has bir ilişki haritası olmalı ({ <ilişki>: { … } })`);\n }\n for (const [rel, inner] of Object.entries(cond as Record<string, unknown>)) {\n if (inner === null || typeof inner !== \"object\" || Array.isArray(inner)) {\n throw new Error(`${caller}(${table}): where.has.${rel} bir filtre nesnesi olmalı`);\n }\n assertUsableFilter(caller, table, inner as Record<string, unknown>);\n }\n continue;\n }\n if (cond === undefined) {\n throw new Error(\n `${caller}(${table}): where.${col} değeri undefined — bu bir filtre değeri değil. ` +\n `Bağlanınca NULL olur ve '= NULL' hiçbir satıra uymaz, yani sorgu sessizce ` +\n `boş sonuç dönerdi. Değer yoksa anahtarı filtreye hiç koymayın.`,\n );\n }\n if (cond === null || typeof cond !== \"object\" || Array.isArray(cond)) continue;\n // `col()` ürünü bir DEĞER'dir, operatör nesnesi değil (FR-011). Ayırt\n // edilmezse `{ $col: \"x\" }` bir operatör haritası sanılır ve \"bilinmeyen\n // operatör $col\" diye reddedilirdi.\n if (isColRef(cond)) continue;\n\n const entries = Object.entries(cond as Record<string, unknown>);\n if (entries.length === 0) {\n throw new Error(\n `${caller}(${table}): where.${col} boş bir operatör nesnesi ({}) — hiçbir koşul ` +\n `üretmez, yani bu alan filtreden sessizce DÜŞERDİ. Koşul kurulmayacaksa ` +\n `anahtarı filtreye hiç koymayın (D-21).`,\n );\n }\n for (const [op, v] of entries) {\n if (op === \"in\") {\n if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${col}.in bir dizi olmalı`);\n if (v.some((x) => x === undefined)) {\n throw new Error(\n `${caller}(${table}): where.${col}.in listesinde undefined var — sessizce NULL'a ` +\n `bağlanır ve o eleman hiçbir satırla eşleşmez. Listeyi kurarken eleyin.`,\n );\n }\n continue;\n }\n // Sağ tarafta kolon durabilir: `{ total: { gt: col(\"amount_paid\") } }`.\n // Değer kontrolleri (undefined) ona da uygulanır, ama `in` gibi şekil\n // kontrolleri değil — o dal aşağıda zaten ayrı.\n if (REFUSED_OPS[op] !== undefined) {\n // Bilinmeyen değil — BİLİNEREK reddedilen. Hata çalışan yazımı söylüyor.\n throw new Error(`${caller}(${table}): where.${col}.${op} bu filtre dilinde yok — ${REFUSED_OPS[op]}`);\n }\n if (!KNOWN_OPS.has(op)) {\n throw new Error(\n `${caller}(${table}): where.${col} bilinmeyen operatör \"${op}\" (gt/gte/lt/lte/neq/in/contains/icontains/startsWith/endsWith/isNull)`,\n );\n }\n if (v === undefined) {\n throw new Error(\n `${caller}(${table}): where.${col}.${op} değeri undefined — karşılaştırmanın ` +\n `sağ tarafı NULL olur ve sonuç hiçbir satıra uymaz. Koşulu kurmayın.`,\n );\n }\n }\n }\n}\n\n/**\n * Refuse a write whose value never arrived.\n *\n * `{ title: req.body.title }` with no `title` in the body bound NULL and\n * answered 200 — the column was ERASED. `null` is untouched, and the difference\n * is the whole point: null is an author SAYING \"empty this column\"; undefined is\n * nobody saying anything.\n */\nexport function assertUsableWriteValues(\n caller: string,\n table: string,\n cols: readonly string[],\n data: Record<string, unknown>,\n): void {\n for (const c of cols) {\n if (data[c] === undefined) {\n throw new Error(\n `${caller}(${table}): \"${c}\" değeri undefined — bu bir yazma değeri değil. ` +\n `Kolonu boşaltmak istiyorsan null yaz; kolonu değiştirmek istemiyorsan nesneye hiç koyma ` +\n `(bir eksik istek alanı sessizce NULL yazıyordu — FR-016).`,\n );\n }\n }\n}\n","/**\n * tx-plan.ts — `Database.$transaction()` as a PLAN, not a pinned session.\n *\n * A transaction used to be a conversation: BEGIN, then one network round trip\n * per operation, then COMMIT. Each of those round trips cost ~4 ms and, because\n * the pooler runs in transaction mode, an open transaction pinned a Postgres\n * backend for the whole conversation. A 121-operation statement upload pinned\n * one backend for ~490 ms.\n *\n * So the callback no longer TALKS to the database. It DESCRIBES what should\n * happen; the description is serialised and sent once; the broker runs the whole\n * thing inside one transaction and answers once. Committing on return and\n * rolling back on throw is unchanged — that is the only property tenant code\n * actually asked for.\n *\n * The consequences, stated plainly, because they are the whole design:\n *\n * - The callback is SYNCHRONOUS. There is nothing to await: no statement has\n * run yet when it returns. `async` on the callback and `await` inside it are\n * both compile errors (see {@link TxPlan} and {@link NotAwaitable}).\n * - `insert()` does not hand back a row, it hands back {@link TxRows}. Reading\n * a field requires `.expectOne(err)` first, which makes \"what if the row\n * isn't there\" a question you cannot route around: it is the argument.\n * - A field read from a row is a {@link Ref} — a PROMISE OF A VALUE THE SERVER\n * WILL PRODUCE, not the value. It can be written into a later operation and\n * it can be returned from the callback (it is substituted for the real value\n * before `transaction()` resolves). It cannot be branched on. See the\n * \"Truthiness\" note below — this is the sharp edge of the whole design.\n * - Control flow that needs a real value must move OUT of the callback: read\n * before the transaction, or express the condition as a guard\n * (`updateWhere({ id, accepted_at: null }, …).expectOne(new Conflict(…))`)\n * which the server evaluates and which rolls the whole plan back.\n *\n * # Truthiness — the hole this file CANNOT close\n *\n * JavaScript does not let a Proxy trap truthiness. `if (ref)` takes the true\n * branch, always, for every Ref, and no `get` handler ever runs. `tsc` is silent\n * because a Ref is a perfectly good object. So:\n *\n * const pot = tx.tables.pots.select({ id }, { limit: 1 }).expectOne(e);\n * if (!pot.balance) { … } // ← ALWAYS false. Silently wrong data.\n *\n * What this file does close: coercion (`Symbol.toPrimitive`/`valueOf`/\n * `toString`), awaiting (`then` is a callable member with a non-thenable\n * signature, which is a *compile* error), serialisation (`toJSON`), and nesting\n * a Ref inside a literal value where the server would store it as data. What it\n * cannot close is a bare truthiness test. The real defence is the build-time\n * static analysis (`tx_analysis.js`, phase P4); until that ships, this hole is\n * open and this comment is the only warning.\n *\n * # Wire contract\n *\n * The JSON this file emits is consumed by\n * the plan executor in `engine/db.ts`. That executor rejects\n * unknown fields at every level, so an op carries EXACTLY the fields its kind\n * takes. Everything here that looks like a needless restriction is one of the\n * server's rules made visible early:\n *\n * - `$ref` only points BACKWARDS, and only at an op statically known to yield\n * at most one row (insert, or a `one`/`atMost 1` guard, or `select limit 1`).\n * `.expectOne()` is what this file uses to satisfy that, always.\n * - `$expr` is a closed set: `inc`/`dec` (update only — they read the column's\n * current value) and `now()`.\n * - `update`/`delete` require a `where`; `insert` refuses one.\n * - `insertMany` rows must all set the same columns.\n * - ≤1000 ops, ≤5000 rows per insertMany, ≤8 MiB of JSON.\n *\n * Column keys are emitted SORTED, so the same callback always produces byte-\n * identical JSON. That is what lets the Go decoder be locked to golden files\n * this SDK emits (`testdata/tx_plan_golden/`).\n */\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * A plan handle was used as if it were a value: awaited, coerced to a string or\n * number, serialised, or nested inside another value.\n *\n * Thrown while the callback is still BUILDING the plan, so nothing has been sent\n * and nothing has been written.\n */\n// TİP-ONLY, ve döngü kasıtlı: `typed-db.ts` bu dosyadan tip alıyor, bu dosya\n// ondan `WhereOp` alıyor. Çalışma zamanında hiçbir şey ithal edilmiyor (import\n// type), yani modül döngüsü yok — paylaşılan olan şey TEK FİLTRE DİLİ, ve onu\n// iki yerde ayrı ayrı tanımlamak bu run'ın kapattığı \"iki yazım\"ın tipteki\n// hâli olurdu.\nimport type { WhereOpWith, ColRefOf, HasOnly } from \"./typed-db.js\";\nimport { isColRef, isSqlFragment, brandRef } from \"./input-guards.js\";\n\nexport class TxRefError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TxRefError\";\n }\n}\n\n/**\n * The plan the callback described cannot be sent: it breaks a rule the server\n * would reject, and rejecting it here names the line that wrote it instead of\n * returning a 400 about an op index.\n */\nexport class TxPlanError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TxPlanError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Wire types — mirror the plan executor in `engine/db.ts` exactly.\n// ---------------------------------------------------------------------------\n\n/** A backwards reference to an earlier op's single-row result. */\nexport interface TxWireRef {\n $ref: { op: number; field: string };\n}\n\n/** A call from the server's closed function set. */\nexport interface TxWireExpr {\n $expr: { fn: \"inc\" | \"dec\"; by: number | string } | { fn: \"now\" };\n}\n\n/** One value in a `values`/`set`/`where` map: a literal, a `$ref` or a `$expr`. */\nexport type TxWireValue = TxWireRef | TxWireExpr | unknown;\n\n/** The tenant's declared expectation about an op's row count. `slot` indexes the\n * client-side error table — the error OBJECT never travels. */\nexport interface TxWireGuard {\n kind: \"one\" | \"none\" | \"atLeast\" | \"atMost\";\n n: number;\n slot: number;\n}\n\n/** One operation in the wire plan. Fields are omitted, never null: the decoder\n * rejects a field that does not belong to the op's kind. */\nexport interface TxWireOp {\n /** upsert and insertMany: the columns Postgres matches on. */\n onConflict?: readonly string[];\n /** insertMany only: what a collision does. Absent means no ON CONFLICT clause\n * at all, which is what every insertMany did before this option existed. */\n action?: \"ignore\" | \"update\";\n op: \"insert\" | \"insertMany\" | \"upsert\" | \"update\" | \"delete\" | \"select\";\n table: string;\n values?: Record<string, TxWireValue>;\n rows?: Record<string, TxWireValue>[];\n set?: Record<string, TxWireValue>;\n where?: Record<string, TxWireValue>;\n limit?: number;\n lock?: \"update\";\n guard?: TxWireGuard;\n}\n\n/** The plan the engine executes on the request's own transaction. */\nexport interface TxPlanBody {\n ops: TxWireOp[];\n}\n\n/** One op's outcome, positionally matched to the plan's ops. */\nexport interface TxPlanOpResult {\n rows: Record<string, unknown>[];\n rows_affected: number;\n}\n\n/** One result per op, in plan order. */\nexport interface TxPlanResponse {\n results: TxPlanOpResult[];\n}\n\n/**\n * The fields the runtime must copy from the broker's error envelope onto the\n * rejection it throws out of {@link DBClient.txPlan}.\n *\n * `slot` is the whole point: on a guard failure the server answers with the\n * INDEX of the expectation that did not hold, never with an error message of its\n * own, and this SDK maps that index back to the `Error` the callback handed to\n * `.expectOne(…)`. Without `slot` a guard failure degrades to a generic 409.\n */\nexport interface TxPlanRejection {\n status?: number;\n /** `tx_plan_invalid` | `tx_guard_failed` | `tx_ref_unresolved` | a pg class. */\n error_code?: string;\n /** Present only for `tx_guard_failed`: the client-side error table index. */\n slot?: number;\n /** Present on a database error: which op failed. */\n op?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Handle types\n// ---------------------------------------------------------------------------\n\ndeclare const refBrand: unique symbol;\ndeclare const rowBrand: unique symbol;\ndeclare const rowsBrand: unique symbol;\n\n/**\n * Makes a handle a compile error to `await`.\n *\n * `then` is declared as a CALLABLE member whose signature is not `PromiseLike`,\n * which is precisely the shape TypeScript rejects: `await handle` is TS1320 and\n * `async () => handle` is TS1058. A non-callable `then` would not do it — the\n * compiler simply ignores those.\n */\nexport interface NotAwaitable {\n /** Not a promise. Nothing here has run yet; there is nothing to await. */\n then(doNotAwaitAPlanHandle: \"a transaction plan is built synchronously\"): never;\n}\n\n/**\n * A value the SERVER will produce, standing in for a column of a row this plan\n * writes or reads.\n *\n * Legal uses: write it into a later operation's `values`/`set`/`where`, or\n * return it from the callback (it is replaced by the real value before\n * `transaction()` resolves).\n *\n * Illegal, and caught: `await`, `String(ref)`, `` `${ref}` ``, `ref + 1`,\n * `JSON.stringify(ref)`, burying it inside a jsonb object.\n *\n * Illegal, and NOT caught: `if (ref)`. See the truthiness note at the top.\n */\nexport interface Ref<T> extends NotAwaitable {\n readonly [refBrand]: T;\n}\n\n/** The brand carried by a single-row handle, and the seam `Materialized` reads\n * to turn `return st` into the whole row. */\nexport interface TxRowHandle<Row> extends NotAwaitable {\n readonly [rowBrand]: Row;\n}\n\n/**\n * A row this plan is known to produce exactly one of. Every property is a\n * {@link Ref}; returning the handle itself yields the whole row.\n *\n * Only `.expectOne(err)` produces one — which is the design: a row you can read\n * fields from is a row whose absence you have already answered for.\n */\nexport type TxRow<Row> = { readonly [K in keyof Row]: Ref<Row[K]> } & TxRowHandle<Row>;\n\n/**\n * The result of one operation, before any expectation is declared about it.\n *\n * Deliberately not a row and not a list: an operation's row count is not known\n * until the server runs it, so the only thing that can be said about it here is\n * an EXPECTATION. Declaring one is also the only way to get a readable row.\n *\n * At most one expectation per operation — the wire carries one guard per op, and\n * a second call throws rather than silently dropping the first.\n */\nexport interface TxRows<Row> extends NotAwaitable {\n readonly [rowsBrand]: Row;\n /**\n * Require exactly one row, and read it. On any other count the server rolls\n * the whole transaction back and this `error` is thrown to the caller.\n *\n * This is the only way to reach a row's fields, and the only shape a `$ref`\n * may point at.\n */\n expectOne(error: Error): TxRow<Row>;\n /** Require zero rows (e.g. \"this membership must not already exist\"). */\n expectNone(error: Error): void;\n /** Require at least `n` rows. */\n expectAtLeast(n: number, error: Error): void;\n /** Require at most `n` rows. */\n expectAtMost(n: number, error: Error): void;\n}\n\n/** `now()` — the server's clock, usable wherever a value is. */\nexport interface TxNow extends NotAwaitable {\n readonly $expr: { fn: \"now\" };\n}\n\n/** `inc(n)` / `dec(n)` — read the column's CURRENT value and write it back\n * changed. Only meaningful in an update's `set`, which is where the types allow\n * it and where the server allows it. */\nexport interface TxColumnExpr extends NotAwaitable {\n readonly $expr: { fn: \"inc\" | \"dec\"; by: number | string };\n}\n\n/**\n * Resolve a callback's return type against what actually comes back: every\n * {@link Ref} becomes its value, every {@link TxRow} becomes its row, and\n * anything else keeps its shape.\n *\n * A {@link TxRows} resolves to an explanatory string type rather than a row\n * list: it has no single answer to give, and saying so in the type is louder\n * than a runtime throw.\n */\nexport type Materialized<T> = T extends Ref<infer U>\n ? U\n : T extends TxRowHandle<infer R>\n ? R\n : T extends TxRows<unknown>\n ? \"a TxRows cannot leave the transaction callback — read a row with .expectOne(err) first\"\n : T extends Date\n ? T\n : T extends object\n ? { [K in keyof T]: Materialized<T[K]> }\n : T;\n\n// ---------------------------------------------------------------------------\n// Author-facing value types\n// ---------------------------------------------------------------------------\n\n/** A value written by an INSERT: a literal, an earlier row's field, or `now()`.\n * `inc`/`dec` are absent on purpose — they read a current value, and an inserted\n * row has none. */\nexport type TxInsertValue<V> = V | Ref<V> | TxNow;\n\n/** A value written by an UPDATE's `set`: everything an insert takes, plus the\n * read-modify-write expressions. */\n/**\n * TEK KURAL: ifade tutamağı yalnız sayısal-benzeri kolonlarda.\n *\n * Bu tip KOŞULSUZDU ve doğrudan yolun `SetValue<V>`'si koşulluydu, yani aynı\n * nesne için İKİ tip kuralı vardı: `tx.tables.todos.updateWhere({id}, { done:\n * increment(1) })` (boolean kolon!) DERLENİYOR, `updateMany`'nin aynısı derleme\n * hatası veriyordu. Bu run'ın kapatmak için var olduğu şey \"aynı iş için iki\n * uyumsuz yazım\"dı; tip kuralı ikinci yazımın kendisi olmuştu (gözcü I6/I-1).\n */\nexport type TxSetValue<V> =\n | V\n | Ref<V>\n | TxNow\n | (NonNullable<V> extends number | string ? TxColumnExpr : never);\n\n/** An insert payload: the table's insert shape, with refs and `now()` allowed. */\nexport type TxInsertShape<Insert> = { [K in keyof Insert]: TxInsertValue<Insert[K]> };\n\n/** An update's `set`: any subset of the insert shape, with expressions allowed. */\nexport type TxSetShape<Insert> = { [K in keyof Insert]?: TxSetValue<Insert[K]> };\n\n/**\n * A filter. Every entry is an equality test and they are AND-ed; a `null`\n * becomes `IS NULL`, which is what makes `{ accepted_at: null }` a usable\n * \"not yet accepted\" guard rather than a clause that matches nothing.\n */\n/**\n * Plan filtresinin tipi — `WhereFilter<Row>` ile AYNI sözlük, artı `Ref`.\n *\n * Eskiden yalnız eşitlikti (`Row[K] | Ref<Row[K]>`), ve iki şeye mal oluyordu:\n * FR-014'ün amiral deseni (`{ balance: { gte: amount } }`) `$transaction`\n * İÇİNDE yazılamıyordu — koşullu bir yazmayı plana koyamayan yazar `$query`'ye\n * düşüyordu — ve motor tarafında tip atlandığında aynı nesne SESSİZCE parametre\n * olarak bağlanıyordu.\n *\n * `Ref` fazladan üye ve öyle kalmalı: bir plan filtresi ÖNCEKİ bir işlemin\n * döndürdüğü değere bakabilir, `findMany` bakamaz — plan dışında böyle bir\n * \"önceki işlem\" yok.\n */\ntype TxWhereField<Row, K extends keyof Row> = WhereOpWith<\n Row[K],\n // `Ref` KOLON REFERANSININ YANINDA duruyor, `V`'nin içinde DEĞİL: `V`'ye\n // eklenseydi `TextOps<V>`'nin `V extends string` sorusu HAYIR olur ve\n // `contains`/`startsWith` sessizce kaybolurdu (ölçüldü).\n ColRefOf<Row, Row[K]> | Ref<Row[K]>\n>;\n\nexport type TxWhere<Row, Rels = unknown> = {\n [K in keyof Row]?: TxWhereField<Row, K>;\n} & {\n OR?: TxWhere<Row, Rels>[];\n AND?: TxWhere<Row, Rels>[];\n NOT?: TxWhere<Row, Rels>;\n} & HasOnly<Rels>;\n\n/** Options for a plan `select`. */\nexport interface TxSelectOptions {\n /** Cap the rows read. */\n limit?: number;\n /** Take a real `FOR UPDATE` row lock for the rest of the transaction. */\n lock?: \"update\";\n}\n\n/** One table, as the plan sees it. */\nexport interface TxTable<Row, Insert, Rels = unknown> {\n /** Insert one row. Returns a handle — call `.expectOne(err)` to read fields. */\n insert(values: TxInsertShape<Insert>): TxRows<Row>;\n /**\n * Insert many rows in ONE statement. Every row must set the same columns\n * (a row that omits one would silently take the column's default).\n *\n * An empty list writes nothing and sends nothing.\n */\n /**\n * Insert many rows in ONE statement, optionally choosing what a collision does.\n *\n * Without `opts` this is a plain multi-row INSERT and a collision aborts the\n * transaction — the behaviour every call had before the option existed.\n *\n * `action: \"ignore\"` emits `ON CONFLICT DO NOTHING`, which is how \"insert the\n * ones that are new\" becomes one round-trip instead of one per row with a\n * 23505 caught around each. **The returned rows are the ones actually\n * INSERTED**: a row that collided is skipped, so it is absent from the result\n * — Postgres does not return what it did not write.\n *\n * `action: \"update\"` emits `ON CONFLICT DO UPDATE`, setting every non-conflict\n * column from the incoming row, and every row comes back.\n */\n insertMany(\n rows: readonly TxInsertShape<Insert>[],\n opts?: {\n onConflict: readonly Extract<keyof Row, string>[];\n action?: \"ignore\" | \"update\";\n },\n ): TxRows<Row>;\n /**\n * Satırı yaz, `onConflict` kolonlarında çakışırsa üzerine yaz — planın\n * savepoint'i içinde, `Database.<şema>.<tablo>.put()` ile AYNI anlamda.\n *\n * Adı bilerek aynı: aynı iş için transaction içinde ve dışında iki farklı\n * yazım, bu run'ın kapatmak için var olduğu şeydir (P1). TEL şekli\n * (`op: \"upsert\"`) değişmedi — o iç sözleşme, yazarın gördüğü ad değil.\n *\n * Bir operasyon olmasının sebebi: alternatifi burada yazılamaz — başarısız\n * bir insert tüm transaction'ı abort eder, yani \"dene, sonra geri düş\" iki\n * plan adımı olamaz.\n */\n put(\n values: TxInsertShape<Insert>,\n options: { onConflict: readonly Extract<keyof Row, string>[] },\n ): TxRows<Row>;\n /**\n * Update every row matching `where`. The filter comes first because it is the\n * dangerous half: an update whose `where` you got wrong rewrites rows you\n * never looked at. The server refuses an update with no `where` at all.\n */\n updateWhere(where: TxWhere<Row, Rels>, set: TxSetShape<Insert>): TxRows<Row>;\n /** Delete every row matching `where`. The server refuses an unfiltered delete. */\n deleteWhere(where: TxWhere<Row, Rels>): TxRows<Row>;\n /** Read rows, optionally locking them for the rest of the transaction. */\n select(where?: TxWhere<Row, Rels>, options?: TxSelectOptions): TxRows<Row>;\n}\n\n/**\n * The handle a transaction callback receives.\n *\n * It carries tables and nothing else: no `query`, no `findById`, no `asService`.\n * A read whose value the plan does not write belongs OUTSIDE the transaction,\n * where it costs one round trip and can be branched on like an ordinary value.\n */\nexport interface TxPlanHandle<TTables> {\n /**\n * @deprecated `tx.public` kullanın. Bu ad public'in takma adı olarak DURUYOR\n * (göç notu onu öğretiyor ve her mevcut çağrı onu kullanıyor), ama ARTIK\n * ÖĞRETİLMİYOR: doğrudan yüzeyde `Database.tables` FR-001 ile kaldırıldı, ve\n * plan yüzeyinin onu öğretmeye devam etmesi yazarı bir yüzeyde çalışıp\n * diğerinde derlenmeyen bir yazıma alıştırıyordu (gözcü M-6).\n */\n tables: TTables;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — expressions\n// ---------------------------------------------------------------------------\n\n/** Internal marker read by the serialiser. */\nconst EXPR = Symbol.for(\"palbase.tx.expr\");\nconst REF = Symbol.for(\"palbase.tx.ref\");\nconst ROW = Symbol.for(\"palbase.tx.row\");\nconst ROWS = Symbol.for(\"palbase.tx.rows\");\n\ninterface RefDescriptor {\n op: number;\n field: string;\n}\n\n/** Property reads that must not silently produce a value on a plan handle.\n * `then` is the one that matters for correctness (it makes `await` a compile\n * error AND stops a handle from being adopted by a promise); the rest turn a\n * silent wrong answer — `\"[object Object]\"`, `NaN`, `{}` — into a thrown one. */\nconst TRAPPED_PROPS: readonly (string | symbol)[] = [\n \"then\",\n \"valueOf\",\n \"toString\",\n \"toJSON\",\n Symbol.toPrimitive,\n];\n\nfunction trap(prop: string | symbol, what: string, hint: string): never {\n const name = typeof prop === \"symbol\" ? prop.description ?? String(prop) : prop;\n throw new TxRefError(\n `${what} was used as a value (via \\`${name}\\`). Nothing in a transaction ` +\n `callback has run yet, so there is no value to read. ${hint}`,\n );\n}\n\n/** The server's `now()`. */\nexport function now(): TxNow {\n return makeExpr({ fn: \"now\" }) as TxNow;\n}\n\n/** Add `by` to the column's current value. Only valid in an update's `set`. */\nexport function increment(by: number | string): TxColumnExpr {\n assertAmount(by, \"increment\");\n return makeExpr({ fn: \"inc\", by }) as TxColumnExpr;\n}\n\n/**\n * `increment`'in eski adı. AYNI fabrikadır — iki uygulama değil, iki ad.\n *\n * @deprecated `increment()` kullanın; bu ad geriye dönük uyumluluk için duruyor.\n */\nexport const inc = increment;\n\n/** Subtract `by` from the column's current value. Only valid in an update's `set`. */\nexport function decrement(by: number | string): TxColumnExpr {\n assertAmount(by, \"decrement\");\n return makeExpr({ fn: \"dec\", by }) as TxColumnExpr;\n}\n\n/**\n * `decrement`'in eski adı. AYNI fabrikadır.\n *\n * @deprecated `decrement()` kullanın.\n */\nexport const dec = decrement;\n\n/**\n * Miktarın taşınabilir olduğunu doğrular.\n *\n * String kabul edilir ve KASITLIDIR (D-007): `numeric` bir kolonda miktar JS\n * `number`'a uğrarsa 0.1 + 0.2 orada 0.30000000000000004'tür ve para hesabı\n * sessizce kayar. String hem burada hem `renderValue`'da bound parametre olarak\n * taşınır — Postgres onu tam ondalık olarak okur.\n */\nfunction assertAmount(by: number | string, fn: string): void {\n if (typeof by === \"string\") {\n // Metin SQL'e girmiyor (bound parametre), ama şekli yine de doğrulanır:\n // \"abc\" bind edilirse hata Postgres'ten gelir, çağıranın diliyle değil.\n if (!/^-?\\d+(\\.\\d+)?$/.test(by)) {\n throw new TxPlanError(\n `${fn}() ondalık bir sayı metni bekliyor, \"${by}\" aldı — kabul edilen biçim: \"12\", \"-12\", \"12.50\"`,\n );\n }\n } else if (typeof by !== \"number\" || !Number.isFinite(by)) {\n // JSON has no NaN/Infinity: they would serialise to `null` and the server\n // would reject the plan with an unhelpful decode error.\n throw new TxPlanError(`${fn}() needs a finite number, got ${String(by)}`);\n }\n // NEGATİF MİKTAR REDDEDİLİR — ve bu şekil kontrolünden çok daha fazlası.\n // `decrement(\"-5\")` `SET c = c - $1` derliyordu, `$1 = -5`, yani beş EKLİYORDU.\n // FR-014'ün amiral deseninde (`where: { balance: { gte: amount } }`) miktar\n // istek gövdesinden geliyorsa `balance >= -5` her zaman doğru: hesap\n // KREDİLENDİRİLİR ve çağrı bunu 1 satırla \"başarı\" diye raporlar. Guard\n // okunduğunda işaret kontrol edilmiş gibi duruyordu (gözcü I9, ölçüldü).\n const negative = typeof by === \"string\" ? by.trimStart().startsWith(\"-\") : by < 0;\n if (negative) {\n const other = fn === \"increment\" ? \"decrement\" : \"increment\";\n throw new TxPlanError(\n `${fn}() negatif miktar almaz (\"${String(by)}\"). Ters yön için ${other}() kullanın — ` +\n `işaretin miktarda saklanması, yönü okuyan hiçbir kod tarafından görülmezdi.`,\n );\n }\n}\n\n/**\n * Bir değer `increment()`/`decrement()` ürünü mü? Öyleyse tel şekli.\n *\n * DOĞRUDAN yol (`updateMany`) da bu ifadeyi anlamak zorunda: aynı nesnenin iki\n * yerde çalışması, \"kolona ekle\"nin tek yazımı olmasının şartı (P1).\n */\nexport function columnExprOf(v: unknown): TxWireExpr[\"$expr\"] | null {\n return exprOf(v);\n}\n\nfunction makeExpr(expr: TxWireExpr[\"$expr\"]): unknown {\n return new Proxy(\n { [EXPR]: expr } as Record<string | symbol, unknown>,\n {\n get(target, prop) {\n if (prop === EXPR) return target[EXPR];\n if (TRAPPED_PROPS.includes(prop)) {\n trap(prop, \"A plan expression\", \"Write it into an operation instead.\");\n }\n return undefined;\n },\n },\n );\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — refs and row handles\n// ---------------------------------------------------------------------------\n\nfunction makeRef(op: number, field: string): unknown {\n const target: Record<string | symbol, unknown> = { [REF]: { op, field } satisfies RefDescriptor };\n return new Proxy(target, {\n get(t, prop) {\n if (prop === REF) return t[REF];\n if (TRAPPED_PROPS.includes(prop)) {\n trap(\n prop,\n `\\`${field}\\` of a row this transaction has not written yet`,\n \"Pass it to another operation in the same plan, or return it from the \" +\n \"callback and read it after `transaction()` resolves.\",\n );\n }\n return undefined;\n },\n });\n}\n\nfunction makeRowHandle(op: number): unknown {\n const target: Record<string | symbol, unknown> = { [ROW]: op };\n return new Proxy(target, {\n get(t, prop) {\n if (prop === ROW) return t[ROW];\n if (TRAPPED_PROPS.includes(prop)) {\n trap(\n prop,\n \"A row this transaction has not written yet\",\n \"Read one of its columns to reference it, or return the row from the \" +\n \"callback and read it after `transaction()` resolves.\",\n );\n }\n if (typeof prop === \"symbol\") return undefined;\n return makeRef(op, prop);\n },\n });\n}\n\nfunction refDescriptor(v: unknown): RefDescriptor | null {\n if (typeof v !== \"object\" || v === null) return null;\n const d = (v as Record<symbol, unknown>)[REF];\n return isRefDescriptor(d) ? d : null;\n}\n\nfunction isRefDescriptor(d: unknown): d is RefDescriptor {\n return (\n typeof d === \"object\" &&\n d !== null &&\n typeof (d as RefDescriptor).op === \"number\" &&\n typeof (d as RefDescriptor).field === \"string\"\n );\n}\n\nfunction rowOpIndex(v: unknown): number | null {\n if (typeof v !== \"object\" || v === null) return null;\n const op = (v as Record<symbol, unknown>)[ROW];\n return typeof op === \"number\" ? op : null;\n}\n\nfunction exprOf(v: unknown): TxWireExpr[\"$expr\"] | null {\n if (typeof v !== \"object\" || v === null) return null;\n const e = (v as Record<symbol, unknown>)[EXPR];\n return typeof e === \"object\" && e !== null ? (e as TxWireExpr[\"$expr\"]) : null;\n}\n\nfunction isRowsHandle(v: unknown): boolean {\n return typeof v === \"object\" && v !== null && (v as Record<symbol, unknown>)[ROWS] !== undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — value encoding\n// ---------------------------------------------------------------------------\n\n/**\n * Encode one value of a `values`/`set`/`where` map.\n *\n * The nesting check is not defensive tidiness. The server treats `$ref` as a\n * tagged value only at the TOP of a column's value; a ref buried inside a jsonb\n * payload is just data, and would be stored as the literal object\n * `{\"$ref\":{...}}` — a write that succeeds, commits, and is wrong. So a nested\n * handle is a hard error here, where the line that wrote it is still on the\n * stack.\n */\nfunction encodeValue(value: unknown, column: string, allowColumnExpr: boolean): TxWireValue {\n const ref = refDescriptor(value);\n if (ref) return brandRef({ $ref: { op: ref.op, field: ref.field } }, \"ref\") satisfies TxWireRef;\n\n const expr = exprOf(value);\n if (expr) {\n if (expr.fn !== \"now\" && !allowColumnExpr) {\n throw new TxPlanError(\n `\\`${column}\\`: ${expr.fn}() reads the column's current value, so it is ` +\n `only valid in updateWhere(where, set).`,\n );\n }\n return { $expr: expr } satisfies TxWireExpr;\n }\n\n if (rowOpIndex(value) !== null) {\n throw new TxPlanError(\n `\\`${column}\\`: a row handle is not a value. Read the column you meant ` +\n `(e.g. \\`row.id\\`).`,\n );\n }\n if (isRowsHandle(value)) {\n throw new TxPlanError(\n `\\`${column}\\`: an operation result is not a value. Declare an expectation ` +\n `first (\\`.expectOne(err)\\`) and read a column from the row.`,\n );\n }\n\n assertNoNestedHandles(value, column);\n return value;\n}\n\n/**\n * Encode a FİLTRE — `values`/`set` ile AYNI kodlayıcı değil, ve olmaması bir\n * düzeltme.\n *\n * `encodeValue` bir `$ref`'i yalnız kolonun EN ÜSTÜNDE kabul ediyor, çünkü bir\n * insert değerinin İÇİNE gömülü ref sunucuda çözülmez, literal JSON olarak\n * SAKLANIR — \"başarıyla commit olan ve yanlış olan bir yazma\". O kural DEĞER\n * yolu için doğru.\n *\n * FİLTREDE öyle değil: motorun `resolveRefsDeep`'i bir ref'i filtrenin HER\n * yerinde çözüyor — operatörün sağında, `OR`/`AND`/`NOT` dallarının içinde. Ama\n * kodlayıcı hâlâ değer kuralını uyguluyordu, yani üç katman üç farklı cevap\n * veriyordu (gözcü C-2): tip kabul, motor çözüyor, kodlayıcı REDDEDİYOR — ve\n * reddin metni değer-yuvalama vakasını anlatıyor, filtrede olmayan bir şeyi.\n *\n * İFADE TUTAMAĞI ve SATIR TUTAMAĞI filtrede HÂLÂ reddediliyor: onları motor\n * filtrede çözmüyor ve çözmemeli — `increment()` bir yazma ifadesi, bir\n * karşılaştırma değil.\n */\nfunction encodeFilterValue(value: unknown, column: string): unknown {\n const ref = refDescriptor(value);\n if (ref) return brandRef({ $ref: { op: ref.op, field: ref.field } }, \"ref\") satisfies TxWireRef;\n\n const expr = exprOf(value);\n if (expr) {\n throw new TxPlanError(\n `\\`${column}\\`: ${expr.fn}() bir YAZMA ifadesi, karşılaştırma değil — ` +\n `filtrede kullanılamaz. Kolonu bir değerle ya da col() ile karşılaştırın.`,\n );\n }\n if (rowOpIndex(value) !== null) {\n throw new TxPlanError(\n `\\`${column}\\`: a row handle is not a value. Read the column you meant (e.g. \\`row.id\\`).`,\n );\n }\n if (isRowsHandle(value)) {\n throw new TxPlanError(\n `\\`${column}\\`: an operation result is not a value. Declare an expectation ` +\n `first (\\`.expectOne(err)\\`) and read a column from the row.`,\n );\n }\n if (Array.isArray(value)) return value.map((v) => encodeFilterValue(v, column));\n // `col()` ve `sqlFragment` OLDUĞU GİBİ geçer: markaları süreç içinde korunur\n // ve derleyici ikisini de kendi tanıyor.\n if (value !== null && typeof value === \"object\" && !(value instanceof Date) && !isColRef(value) && !isSqlFragment(value)) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[k] = encodeFilterValue(v, column);\n }\n return out;\n }\n return value;\n}\n\n/** Filtre haritası — anahtarlar SIRALI (aynı geri çağrı bayt-özdeş JSON üretsin). */\nfunction encodeFilterMap(map: Record<string, unknown>): Record<string, TxWireValue> {\n const out: Record<string, TxWireValue> = {};\n for (const key of Object.keys(map).sort()) {\n const value = map[key];\n if (value === undefined) continue;\n out[key] = encodeFilterValue(value, key) as TxWireValue;\n }\n return out;\n}\n\nfunction assertNoNestedHandles(value: unknown, column: string): void {\n if (typeof value !== \"object\" || value === null) return;\n if (value instanceof Date) return;\n if (refDescriptor(value) || exprOf(value) || rowOpIndex(value) !== null || isRowsHandle(value)) {\n throw new TxPlanError(\n `\\`${column}\\`: a plan handle is nested inside a value. The server would ` +\n `store it as literal JSON, not resolve it. Put the reference directly in ` +\n `the column.`,\n );\n }\n if (Array.isArray(value)) {\n for (const item of value) assertNoNestedHandles(item, column);\n return;\n }\n for (const item of Object.values(value as Record<string, unknown>)) {\n assertNoNestedHandles(item, column);\n }\n}\n\n/**\n * Encode a column map, dropping `undefined` and emitting keys SORTED.\n *\n * Sorting is what makes the same callback produce byte-identical JSON every\n * time, which is what lets the Go decoder be locked to goldens this SDK emits.\n * Dropping `undefined` mirrors what `JSON.stringify` would do anyway, but does\n * it where the resulting column list is still checkable.\n */\nfunction encodeMap(\n map: Record<string, unknown>,\n allowColumnExpr: boolean,\n): Record<string, TxWireValue> {\n const out: Record<string, TxWireValue> = {};\n for (const key of Object.keys(map).sort()) {\n const value = map[key];\n // `undefined` ATLANIR — ve bu, doğrudan yolun ADIYLA REDDETMESİNDEN\n // bilerek ayrılıyor. Buradaki anlam \"kolon varsayılanını al\"; doğrudan\n // yolunki \"kimse bir şey söylemedi, ve bu bir yazma değil\".\n //\n // İkisi de tutarlı, ama AYNI girdiye zıt cevap veriyorlar ve doğrudan\n // yolun cevabı ölçülmüş bir olaya dayanıyor (`{ title: req.body.title }`\n // gövdede `title` yokken kolonu SESSİZCE sildi ve 200 döndü). Bu ayrışma\n // deftere D-17 olarak yazıldı ve kullanıcıya teklif edildi; sekizinci bir\n // kırıcı olduğu için onay almadan kapatılmıyor.\n if (value === undefined) continue;\n out[key] = encodeValue(value, key, allowColumnExpr);\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — the builder\n// ---------------------------------------------------------------------------\n\n/** How many rows an op can produce, as far as the CLIENT can tell before it\n * runs. Only the empty `insertMany` is knowable, and knowing it is what lets a\n * doomed expectation fail on the line that declared it. */\nconst SKIPPED_OP = -1;\n\nclass TxRowsImpl<Row> implements TxRows<Row> {\n // Present so `isRowsHandle` recognises the object; never read for its value.\n readonly [ROWS] = true;\n // Phantom: the type brand that stops a bare object passing as a TxRows. Never\n // present at run time, which is what `declare` says.\n declare readonly [rowsBrand]: Row;\n private guarded = false;\n\n constructor(\n private readonly builder: TxPlanBuilder,\n private readonly opIndex: number,\n private readonly what: string,\n ) {}\n\n // The type-level `await` guard made real: TS rejects `await rows` at compile\n // time, and reaching this means someone called `.then(...)` by hand.\n then(): never {\n throw new TxRefError(\n `${this.what} cannot be awaited: a transaction callback builds a plan, it ` +\n `does not run statements. Remove the \\`await\\`.`,\n );\n }\n\n expectOne(error: Error): TxRow<Row> {\n this.declareGuard(\"one\", 1, error);\n if (this.opIndex === SKIPPED_OP) throw error;\n return makeRowHandle(this.opIndex) as TxRow<Row>;\n }\n\n expectNone(error: Error): void {\n this.declareGuard(\"none\", 0, error);\n }\n\n expectAtLeast(n: number, error: Error): void {\n assertGuardCount(n, \"expectAtLeast\");\n this.declareGuard(\"atLeast\", n, error);\n if (this.opIndex === SKIPPED_OP && n > 0) throw error;\n }\n\n expectAtMost(n: number, error: Error): void {\n assertGuardCount(n, \"expectAtMost\");\n this.declareGuard(\"atMost\", n, error);\n }\n\n private declareGuard(kind: TxWireGuard[\"kind\"], n: number, error: Error): void {\n if (!(error instanceof Error)) {\n // The error object is the ONLY thing that describes this failure to the\n // caller — the server sends back an index, never a message.\n throw new TxPlanError(\n `${this.what}: an expectation needs the Error to throw when it does not ` +\n `hold (e.g. \\`.expect…(new Conflict(\"already accepted\"))\\`).`,\n );\n }\n if (this.guarded) {\n throw new TxPlanError(\n `${this.what} already has an expectation. One operation carries one ` +\n `expectation; declare the second one on its own operation.`,\n );\n }\n this.guarded = true;\n if (this.opIndex === SKIPPED_OP) return;\n this.builder.attachGuard(this.opIndex, kind, n, error);\n }\n}\n\nfunction assertGuardCount(n: number, fn: string): void {\n if (!Number.isInteger(n) || n < 0) {\n throw new TxPlanError(`${fn}(n) needs a non-negative integer, got ${String(n)}`);\n }\n}\n\n/** Bounds mirrored from the plan executor so an over-sized plan is named here rather\n * than rejected as an opaque 400 after it has crossed the network. */\nconst MAX_OPS = 1000;\nconst MAX_ROWS = 5000;\n\n/**\n * Accumulates ops and the client-side error table while the callback runs.\n *\n * Exported for the runtime and for tests that need the serialised plan without\n * a server; author code never sees it.\n */\nexport class TxPlanBuilder {\n private readonly ops: TxWireOp[] = [];\n /** Errors handed to expectations, indexed by the `slot` the server echoes. */\n private readonly slots: Error[] = [];\n\n /** The table surface handed to the callback. Untyped here; the public\n * `transaction()` signatures put the schema types on top. */\n table(name: string): TxTable<Record<string, unknown>, Record<string, unknown>> {\n return {\n insert: (values) => {\n const encoded = encodeMap(values as Record<string, unknown>, false);\n if (Object.keys(encoded).length === 0) {\n throw new TxPlanError(`${name}.insert() needs at least one column`);\n }\n return this.push({ op: \"insert\", table: name, values: encoded }, `${name}.insert()`);\n },\n\n put: (values, options) => {\n const encoded = encodeMap(values as Record<string, unknown>, false);\n if (Object.keys(encoded).length === 0) {\n throw new TxPlanError(`${name}.put() needs at least one column`);\n }\n if (options.onConflict.length === 0) {\n throw new TxPlanError(`${name}.put() needs at least one onConflict column`);\n }\n return this.push(\n { op: \"upsert\", table: name, values: encoded, onConflict: options.onConflict },\n `${name}.upsert()`,\n );\n },\n\n insertMany: (rows, opts) => {\n if (rows.length === 0) {\n // Nothing to write. Emitting an op would be a guaranteed 400 (the\n // server requires rows), and refusing outright would punish the\n // ordinary \"filtered every candidate out\" case.\n return new TxRowsImpl(this, SKIPPED_OP, `${name}.insertMany()`);\n }\n if (rows.length > MAX_ROWS) {\n throw new TxPlanError(\n `${name}.insertMany() has ${rows.length} rows; the limit is ${MAX_ROWS}. ` +\n `Split the write across requests.`,\n );\n }\n const encoded = rows.map((row) => encodeMap(row as Record<string, unknown>, false));\n assertUniformRows(encoded, name);\n if (opts !== undefined && opts.onConflict.length === 0) {\n throw new TxPlanError(\n `${name}.insertMany() was given a conflict action with no onConflict ` +\n `columns. Postgres matches a collision on columns, so name them.`,\n );\n }\n return this.push(\n {\n op: \"insertMany\",\n table: name,\n rows: encoded,\n // Omitted entirely when no options were given, so the op a plain\n // insertMany produces is byte-identical to the one it produced\n // before this option existed.\n ...(opts !== undefined\n ? { onConflict: opts.onConflict, action: opts.action ?? \"ignore\" }\n : {}),\n },\n `${name}.insertMany()`,\n );\n },\n\n updateWhere: (where, set) => {\n const encodedWhere = encodeFilterMap(where as Record<string, unknown>);\n const encodedSet = encodeMap(set as Record<string, unknown>, true);\n if (Object.keys(encodedWhere).length === 0) {\n throw new TxPlanError(\n `${name}.updateWhere() needs a filter. An update with no filter ` +\n `rewrites the whole table.`,\n );\n }\n if (Object.keys(encodedSet).length === 0) {\n throw new TxPlanError(`${name}.updateWhere() needs at least one column to set`);\n }\n return this.push(\n { op: \"update\", table: name, set: encodedSet, where: encodedWhere },\n `${name}.updateWhere()`,\n );\n },\n\n deleteWhere: (where) => {\n const encodedWhere = encodeFilterMap(where as Record<string, unknown>);\n if (Object.keys(encodedWhere).length === 0) {\n throw new TxPlanError(\n `${name}.deleteWhere() needs a filter. A delete with no filter empties ` +\n `the table.`,\n );\n }\n return this.push(\n { op: \"delete\", table: name, where: encodedWhere },\n `${name}.deleteWhere()`,\n );\n },\n\n select: (where, options) => {\n const op: TxWireOp = { op: \"select\", table: name };\n const encodedWhere = encodeFilterMap((where ?? {}) as Record<string, unknown>);\n if (Object.keys(encodedWhere).length > 0) op.where = encodedWhere;\n if (options?.limit !== undefined) {\n if (!Number.isInteger(options.limit) || options.limit < 0) {\n throw new TxPlanError(\n `${name}.select(): limit needs a non-negative integer, got ${String(options.limit)}`,\n );\n }\n op.limit = options.limit;\n }\n if (options?.lock !== undefined) op.lock = options.lock;\n return this.push(op, `${name}.select()`);\n },\n };\n }\n\n private push(op: TxWireOp, what: string): TxRows<Record<string, unknown>> {\n if (this.ops.length >= MAX_OPS) {\n throw new TxPlanError(\n `this transaction has ${MAX_OPS} operations, which is the limit. Use ` +\n `insertMany() for bulk writes, or split the work across requests.`,\n );\n }\n const index = this.ops.length;\n this.ops.push(op);\n return new TxRowsImpl(this, index, what);\n }\n\n /** Attach an expectation to an op and record its error in the slot table. */\n attachGuard(opIndex: number, kind: TxWireGuard[\"kind\"], n: number, error: Error): void {\n const op = this.ops[opIndex];\n // Unreachable: the index came from `push`. A loud failure beats a silently\n // unguarded write if that ever stops being true.\n if (!op) throw new TxPlanError(`internal: expectation on unknown operation ${opIndex}`);\n const slot = this.slots.length;\n this.slots.push(error);\n op.guard = { kind, n, slot };\n }\n\n /** The serialisable plan. Empty when the callback described no writes. */\n body(): TxPlanBody {\n return { ops: this.ops };\n }\n\n /** The error the server's `slot` selects, or `null` when it names one this\n * plan never declared (a server/client disagreement, not a tenant error). */\n errorForSlot(slot: number): Error | null {\n return this.slots[slot] ?? null;\n }\n}\n\nfunction assertUniformRows(rows: Record<string, TxWireValue>[], table: string): void {\n const first = rows[0];\n if (!first) return;\n const want = Object.keys(first);\n const wantKey = want.join(\",\");\n for (let i = 1; i < rows.length; i++) {\n const got = Object.keys(rows[i] as Record<string, TxWireValue>);\n if (got.join(\",\") !== wantKey) {\n // One statement, one column list. A ragged row would take the DB default\n // for the column it omitted — a write that succeeds and is wrong.\n throw new TxPlanError(\n `${table}.insertMany(): every row must set the same columns. Row 0 sets ` +\n `[${want.join(\", \")}] but row ${i} sets [${got.join(\", \")}]. ` +\n `(A property set to \\`undefined\\` counts as absent — use \\`null\\`.)`,\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — materialisation\n// ---------------------------------------------------------------------------\n\n/**\n * Replace every handle in the callback's return value with what the server\n * actually produced.\n *\n * Walks arrays and PLAIN objects only. Class instances (a Date, a Zod schema, a\n * domain object) are returned untouched — recursing into them would rebuild them\n * as bare objects, and nothing inside one can be a handle that this SDK created.\n */\nexport function materializeResult(value: unknown, results: TxPlanOpResult[]): unknown {\n const ref = refDescriptor(value);\n if (ref) {\n const row = rowOf(results, ref.op, `\\`${ref.field}\\``);\n if (!(ref.field in row)) {\n throw new TxPlanError(\n `the transaction's operation ${ref.op} returned no column \\`${ref.field}\\`.`,\n );\n }\n return row[ref.field];\n }\n\n const rowOp = rowOpIndex(value);\n if (rowOp !== null) return rowOf(results, rowOp, \"a row\");\n\n if (isRowsHandle(value)) {\n throw new TxPlanError(\n \"an operation result cannot be returned from a transaction callback: its \" +\n \"row count is not known until the plan runs. Declare an expectation \" +\n \"(`.expectOne(err)`) and return the row, or a column of it.\",\n );\n }\n\n if (Array.isArray(value)) return value.map((item) => materializeResult(item, results));\n\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value)) out[key] = materializeResult(item, results);\n return out;\n }\n\n return value;\n}\n\nfunction rowOf(results: TxPlanOpResult[], opIndex: number, what: string): Record<string, unknown> {\n const result = results[opIndex];\n if (!result) {\n throw new TxPlanError(\n `the transaction returned no result for operation ${opIndex}, so ${what} ` +\n `cannot be read.`,\n );\n }\n const row = result.rows[0];\n if (!row) {\n // Unreachable through the public API: a handle only exists behind an\n // `expectOne`, and the server rolls back rather than answering 200 with a\n // guard unmet. Loud, because the alternative is `undefined` in tenant data.\n throw new TxPlanError(\n `the transaction's operation ${opIndex} returned no row, so ${what} cannot ` +\n `be read.`,\n );\n }\n return row;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — the driver\n// ---------------------------------------------------------------------------\n\n/** What {@link runTxPlan} needs from the runtime: one call, one transaction. */\nexport interface TxPlanTransport {\n txPlan(plan: TxPlanBody): Promise<TxPlanResponse>;\n}\n\n/**\n * Build the plan, send it, and resolve the callback's return value.\n *\n * A callback that throws never reaches the network: there is nothing to roll\n * back because nothing was sent. A callback that describes no writes also skips\n * the round trip entirely.\n *\n * The RETURN type is `unknown` here on purpose. `Materialized<T>` is a compile-\n * time rewrite of the callback's return type, and it belongs on the public\n * `transaction()` signatures where `T` is inferred from the author's callback;\n * threading it through this driver as well would mean inferring `T` twice, from\n * two different positions, and reconciling them. The public wrappers narrow\n * once, at the seam where the value types were erased anyway.\n */\nexport async function runTxPlan<THandle>(\n transport: TxPlanTransport,\n // TUTAMAĞIN TAMAMI, yalnız `tables` DEĞİL. Tutamak artık şema yüzeyini de\n // taşıyor (`tx.public.x`, `tx.<şema>.x`), ve onu BURADA `{ tables }` diye\n // yeniden kurmak o yüzeyi sessizce düşürürdü.\n handle: THandle,\n builder: TxPlanBuilder,\n fn: (tx: THandle) => unknown,\n): Promise<unknown> {\n const returned = fn(handle);\n const body = builder.body();\n if (body.ops.length === 0) {\n return materializeResult(returned, []);\n }\n\n let response: TxPlanResponse;\n try {\n response = await transport.txPlan(body);\n } catch (err) {\n throw translateRejection(err, builder);\n }\n return materializeResult(returned, response.results);\n}\n\n/**\n * Turn the broker's rejection back into the tenant's own error.\n *\n * The server never sees the `Error` an expectation was given — only its slot\n * index — so this is the only place the intended error can be produced. A\n * rejection this SDK does not recognise passes through untouched: inventing an\n * error for it would hide the real failure.\n */\nfunction translateRejection(err: unknown, builder: TxPlanBuilder): unknown {\n if (typeof err !== \"object\" || err === null) return err;\n const rejection = err as TxPlanRejection;\n if (rejection.error_code !== \"tx_guard_failed\" || typeof rejection.slot !== \"number\") {\n return err;\n }\n return builder.errorForSlot(rejection.slot) ?? err;\n}\n","import type { DBClient, DBOps } from \"../../endpoint.js\";\n// The SAME refusals the engine applies. Without these the fake accepted every\n// call the driver path had just started rejecting, and the scaffold points\n// authors at this fake to test their services — so the test went green and\n// production threw. Measured against the published 24.1.0.\nimport { assertUsableFilter, assertUsableWriteValues, assertNoExpressionHandles, isColRef, isSqlFragment } from \"../../db/input-guards.js\";\n\n/**\n * `sqlFragment` sahte veritabanında ÇALIŞTIRILAMAZ — ve sessizce yok sayılamaz.\n *\n * Fake'in bir SQL değerlendiricisi yok. Fragment'i görmezden gelmek, filtreyi\n * hiç uygulamamak demektir: test TÜM satırları görür, üretim ise süzülmüş\n * satırları. Yazarın testi o gün yeşil, üretim yanlış olur — bu dosyanın var\n * olma sebebi tam olarak o sınıf hata. O yüzden adıyla reddediliyor, ve hata\n * çalışan bir alternatif söylüyor (P6).\n */\nfunction refuseFragment(caller: string, table: string, where: unknown): void {\n if (isSqlFragment(where)) {\n throw new Error(\n `${caller}(${table}): fakeDatabase bir sqlFragment'i değerlendiremez — sahte depo SQL çalıştırmaz. ` +\n `Filtreyi tipli filtre diliyle kurun (gt/gte/lt/lte/neq/in/contains/isNull, OR/AND/NOT), ` +\n `ya da bu testi gerçek bir Postgres'e karşı yazın.`,\n );\n }\n // İÇ İÇE de reddedilir. Yalnız ÜST DÜZEYE bakmak, { OR: [ sqlFragment tag, … ] }\n // filtresini fake'te SESSİZCE boş sonuca çeviriyordu; motor onu derliyor\n // (W2-B/C5, ölçüldü). Reddin de bileşim dallarını dolaşması gerekiyor.\n if (where === null || typeof where !== \"object\") return;\n for (const [k, v] of Object.entries(where as Record<string, unknown>)) {\n if (k === \"OR\" || k === \"AND\") {\n for (const branch of (Array.isArray(v) ? v : [])) refuseFragment(caller, table, branch);\n } else if (k === \"NOT\") {\n refuseFragment(caller, table, v);\n }\n }\n}\n\n/**\n * Sayaç aritmetiği — motorun döndürdüğü ALANDA.\n *\n * Postgres `numeric` kolonu STRING döndürür ve toplamayı tam yapar. Fake\n * `Number()` ile hesaplıyordu; ölçülen sonuçlar: `\"0.10\" + \"0.20\"` →\n * `0.30000000000000004`, `\"12345678901234567890\" + 1` →\n * `12345678901234567000`, ve satırın tipi string'den number'a KAYIYORDU.\n * D-007'nin (string miktar) var olma sebebi tam olarak bu kayıptı; fake onu\n * geri getiriyordu (inceleme I-4/I-3).\n *\n * `null` + n = `null`: Postgres'te de öyle, satır değişmez.\n */\nfunction addDecimal(cell: unknown, by: number | string, sign: 1 | -1): unknown {\n if (cell === null || cell === undefined) return null;\n if (typeof cell === \"number\" && typeof by === \"number\") return cell + sign * by;\n const a = String(cell);\n const b = String(by);\n const parse = (x: string): { unit: bigint; scale: number } | null => {\n const m = /^([+-]?)(\\d*)(?:\\.(\\d*))?$/.exec(x.trim());\n if (m === null || (m[2] === \"\" && (m[3] ?? \"\") === \"\")) return null;\n const frac = m[3] ?? \"\";\n const unit = BigInt(`${m[1] === \"-\" ? \"-\" : \"\"}${m[2] === \"\" ? \"0\" : m[2]}${frac}`);\n return { unit, scale: frac.length };\n };\n const pa = parse(a);\n const pb = parse(b);\n if (pa === null || pb === null) {\n // Motor bu durumda Postgres'e sorar ve `operator does not exist:\n // text + integer` alır. Sessizce NaN yazmak yerine ADIYLA reddediyoruz.\n throw new Error(\n `fakeDatabase: increment()/decrement() sayısal olmayan bir değere uygulandı (\"${a}\") — ` +\n `Postgres bunu \"operator does not exist\" ile reddeder.`,\n );\n }\n const scale = Math.max(pa.scale, pb.scale);\n const lift = (v: { unit: bigint; scale: number }): bigint =>\n v.unit * 10n ** BigInt(scale - v.scale);\n const total = lift(pa) + BigInt(sign) * lift(pb);\n if (scale === 0) return typeof cell === \"number\" ? Number(total) : total.toString();\n const neg = total < 0n;\n const digits = (neg ? -total : total).toString().padStart(scale + 1, \"0\");\n const out = `${neg ? \"-\" : \"\"}${digits.slice(0, -scale)}.${digits.slice(-scale)}`;\n return typeof cell === \"number\" ? Number(out) : out;\n}\nimport { columnExprOf } from \"../../db/tx-plan.js\";\nimport type {\n TxPlanBody,\n TxPlanOpResult,\n TxPlanRejection,\n TxPlanResponse,\n TxWireGuard,\n TxWireOp,\n TxWireValue,\n} from \"../../db/tx-plan.js\";\n\n/** Tracked records for assertions. */\ninterface TrackedRecords {\n inserted: Map<string, Record<string, unknown>[]>;\n updated: Map<string, Record<string, unknown>[]>;\n deleted: Map<string, string[]>;\n}\n\n/** Mock DB client with tracking and seed data support. */\n/**\n * TEK EŞLEŞTİRİCİ — `findMany`, `updateMany`, `deleteMany` ve `count` bunu\n * kullanır.\n *\n * Motorda tek bir `compileWhereBare` var; fake'te DÖRT ayrı eşleştirici vardı\n * ve üçü yalnız katı eşitliğe bakıyordu. Ölçülen sonuç (inceleme C3/C6/I2):\n * `updateMany({ id, balance: { gte: \"5.00\" } }, …)` fake'te HİÇBİR satır\n * eşleştirmiyordu, yani FR-014'ün doc'unun ÖĞRETTİĞİ \"yetersiz bakiye\" deseni\n * fake'e karşı HER ZAMAN başarısız dala düşüyor — yazar başarı yolunu hiç test\n * edemiyor, üretimde para gerçekten çekiliyor.\n */\nfunction rowMatchesFilter(\n caller: string,\n table: string,\n row: Record<string, unknown>,\n f: Record<string, unknown>,\n): boolean {\n return Object.entries(f).every(([k, c]) => {\n if (k === \"OR\") return (c as Record<string, unknown>[]).some((b) => rowMatchesFilter(caller, table, row, b));\n if (k === \"AND\") return (c as Record<string, unknown>[]).every((b) => rowMatchesFilter(caller, table, row, b));\n if (k === \"NOT\") return !rowMatchesFilter(caller, table, row, c as Record<string, unknown>);\n // `has` ilişki GRAFİĞİ ister ve sahte deponun grafiği YOK — tablolar bir\n // Map'te, aralarındaki yabancı anahtarlar hiçbir yerde. Sessizce yok saymak\n // filtreyi hiç uygulamamak olurdu: test TÜM satırları görür, üretim\n // süzülmüş satırları. Bu dosyanın var olma sebebi tam olarak o sınıf.\n if (k === \"has\") {\n throw new Error(\n `${caller}(${table}): fakeDatabase bir \\`has\\` filtresini çözemez — ilişki grafiği ` +\n `bildirimden türetiliyor ve sahte deponun bildirimi yok. Bu testi gerçek bir Postgres'e ` +\n `karşı yazın, ya da ilişkiyi filtrede AÇIKÇA kurun (önce ilişki tablosunu okuyup ` +\n `{ id: { in: [...] } } ile süzün).`,\n );\n }\n return matchesCell(caller, table, row, k, c);\n });\n}\n\n/**\n * SQL'in üç değerli mantığı: NULL taşıyan karşılaştırma UNKNOWN'dır, yani satır\n * EŞLEŞMEZ. Fake `===` kullanıyordu ve iki NULL kolonu EŞİT sayıyordu — motorun\n * her yerde uyguladığı FR-006 doktrininin tersi (inceleme I3, ölçüldü).\n */\nfunction cmp(a: unknown, b: unknown, op: string): boolean {\n if (a === null || a === undefined || b === null || b === undefined) return false;\n const l = a instanceof Date ? a.getTime() : a;\n const r = b instanceof Date ? b.getTime() : b;\n switch (op) {\n case \"eq\": return l === r;\n case \"neq\": return l !== r;\n case \"gt\": return (l as number) > (r as number);\n case \"gte\": return (l as number) >= (r as number);\n case \"lt\": return (l as number) < (r as number);\n case \"lte\": return (l as number) <= (r as number);\n default: return false;\n }\n}\n\nfunction matchesCell(\n caller: string,\n table: string,\n row: Record<string, unknown>,\n key: string,\n cond: unknown,\n): boolean {\n // Kolon-kolon karşılaştırma (FR-011) — motorla PARİTE. Fake `col()`'u\n // tanımasaydı `{ $col: \"x\" }` nesnesini DEĞER sanıp eşitlik kurar, hiçbir\n // satır dönmez ve yazarın testi sessizce boş sonuca geçerdi.\n if (isColRef(cond)) return cmp(row[key], row[cond.$col], \"eq\");\n if (cond !== null && typeof cond === \"object\" && !Array.isArray(cond)) {\n return Object.entries(cond as Record<string, unknown>).every(([op, v]) => {\n const cell = row[key];\n if (isColRef(v)) {\n if (![\"neq\", \"gt\", \"gte\", \"lt\", \"lte\"].includes(op)) {\n throw new Error(`${caller}(${table}): where.${key}.${op} col() ile kullanılamaz`);\n }\n return cmp(cell, row[v.$col], op);\n }\n switch (op) {\n case \"in\":\n if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${key}.in bir dizi olmalı`);\n if (v.some(isColRef)) {\n throw new Error(\n `${caller}(${table}): where.${key}.in col() ile kullanılamaz — kolon karşılaştırması için gt/gte/lt/lte/neq kullanın`,\n );\n }\n return v.includes(cell);\n case \"neq\": case \"gt\": case \"gte\": case \"lt\": case \"lte\":\n return cmp(cell, v, op);\n // K1 metin operatörleri (FR-005) ve null testi (FR-006). Bunlar BURADA\n // da olmak zorunda: guard onları KABUL ettiği anda fake sessizce yanlış\n // cevap verir ve yazarın testi, üretimde farklı davranan koda karşı\n // yeşil verirdi (input-guards.ts'in uyardığı tam sınıf).\n case \"isNull\":\n if (typeof v !== \"boolean\") throw new Error(`${caller}(${table}): where.${key}.isNull bir boolean olmalı`);\n return v ? cell == null : cell != null;\n case \"contains\": case \"icontains\": case \"startsWith\": case \"endsWith\": {\n if (typeof v !== \"string\") throw new Error(`${caller}(${table}): where.${key}.${op} bir string olmalı`);\n if (typeof cell !== \"string\") return false;\n if (op === \"contains\") return cell.includes(v);\n if (op === \"icontains\") return cell.toLowerCase().includes(v.toLowerCase());\n if (op === \"startsWith\") return cell.startsWith(v);\n return cell.endsWith(v);\n }\n default:\n throw new Error(`${caller}(${table}): where.${key} bilinmeyen operatör \"${op}\"`);\n }\n });\n }\n return row[key] === cond;\n}\n\nexport interface MockDBClient extends DBClient {\n /** Get records inserted into a table. */\n inserted(table: string): Record<string, unknown>[];\n /** Get records updated in a table. */\n updated(table: string): Record<string, unknown>[];\n /** Get IDs deleted from a table. */\n deleted(table: string): string[];\n /** Pre-seed data into a table for findById/findMany. */\n seed(table: string, data: Record<string, unknown>[]): void;\n}\n\n/** Create a mock DB client with in-memory tracking. */\nexport function createMockDB(): MockDBClient {\n const store = new Map<string, Record<string, unknown>[]>();\n const tracked: TrackedRecords = {\n inserted: new Map(),\n updated: new Map(),\n deleted: new Map(),\n };\n\n function rowsOf(table: string): Record<string, unknown>[] {\n let rows = store.get(table);\n if (!rows) {\n rows = [];\n store.set(table, rows);\n }\n return rows;\n }\n\n function track(\n map: Map<string, Record<string, unknown>[]>,\n table: string,\n row: Record<string, unknown>,\n ): void {\n const list = map.get(table);\n if (list) list.push(row);\n else map.set(table, [row]);\n }\n\n // Build the op surface first (the six string-keyed ops). `txPlan` below\n // interprets a whole plan against the SAME in-memory store and tracking maps,\n // so a transaction's writes are visible to later assertions exactly as a\n // direct write would be.\n const ops: DBOps = {\n // The bulk ops and count run against the SAME in-memory store the direct\n // ops write to, so a test that writes three rows and counts them gets 3 —\n // a mock that answered 0 would make the surface look broken in exactly the\n // tests meant to prove it works.\n async updateMany(table: string, where: Record<string, unknown>, set: Record<string, unknown>) {\n if (Object.keys(where).length === 0) throw new Error(`updateMany(${table}): boş filtre`);\n assertUsableFilter(\"updateMany\", table, where);\n refuseFragment(\"updateMany\", table, where);\n assertUsableWriteValues(\"updateMany\", table, Object.keys(set), set);\n const hit = (store.get(table) ?? []).filter((r) =>\n rowMatchesFilter(\"updateMany\", table, r, where),\n );\n // increment()/decrement() (FR-012) — motorla PARİTE. Fake ifadeyi DEĞER\n // sanıp yazsaydı, sayaç kolonu bir proxy nesnesine dönerdi ve yazarın\n // testi \"artış oldu\" diye değil, sessizce bozuk veriyle geçerdi.\n for (const row of hit) {\n for (const [k, v] of Object.entries(set)) {\n const expr = columnExprOf(v);\n if (expr !== null && expr.fn === \"now\") {\n // Motor `SET col = now()` derliyor; fake'in karşılığı bir zaman damgası.\n row[k] = new Date();\n continue;\n }\n if (expr !== null) {\n row[k] = addDecimal(row[k], expr.by as number | string, expr.fn === \"inc\" ? 1 : -1);\n continue;\n }\n row[k] = v;\n }\n }\n return hit;\n },\n async deleteMany(table: string, where: Record<string, unknown>) {\n if (Object.keys(where).length === 0) throw new Error(`deleteMany(${table}): boş filtre`);\n assertUsableFilter(\"deleteMany\", table, where);\n refuseFragment(\"deleteMany\", table, where);\n const list = store.get(table) ?? [];\n const keep = list.filter((r) => !rowMatchesFilter(\"deleteMany\", table, r, where));\n store.set(table, keep);\n return list.length - keep.length;\n },\n async count(table: string, where: Record<string, unknown> = {}) {\n assertUsableFilter(\"count\", table, where);\n // `refuseFragment` burada ATLANMIŞTI: fragment'li count fake'te sessizce\n // 0 döndürüyordu, motor gerçek sayıyı (W2-B/C4).\n refuseFragment(\"count\", table, where);\n return (store.get(table) ?? []).filter((r) =>\n rowMatchesFilter(\"count\", table, r, where),\n ).length;\n },\n async search(_table: string, _params?: Record<string, unknown>) {\n return [];\n },\n async facets(_table: string, _params?: Record<string, unknown>) {\n return {};\n },\n async similar() {\n return [];\n },\n async recommend() {\n return [];\n },\n async supersede(_table: string, _id: string, row: Record<string, unknown>) {\n return { id: crypto.randomUUID(), ...row };\n },\n async query(_sql: string, _params?: unknown[]) {\n return [];\n },\n\n async insert(table: string, data: Record<string, unknown>) {\n assertUsableWriteValues(\"insert\", table, Object.keys(data), data);\n // Motorla PARİTE: ifade tutamağı bu yolda değer değildir. Fake onu\n // satıra YAZIYORDU; satır artık JSON'a bile çevrilemiyordu.\n assertNoExpressionHandles(\"insert\", table, Object.keys(data), data);\n const record = { id: crypto.randomUUID(), ...data };\n rowsOf(table).push(record);\n track(tracked.inserted, table, record);\n return record;\n },\n\n /**\n * `claim` — motorla PARİTE (FR-033).\n *\n * Fake bunu sunmasaydı `claim` ile yazılmış bir servis, iskelenin yazarlara\n * önerdiği test yolunda `undefined is not a function` verirdi; sunup da\n * FARKLI davransaydı (ör. hep `inserted: true`) idempotency testi üretimde\n * çalışmayan koda karşı yeşil olurdu.\n *\n * Anahtar ZATEN VARSA var olan satır dönüyor ve hiçbir şey yazılmıyor —\n * motorun 23505 dalının aynısı.\n */\n /**\n * `lockRows` — sahte depoda kilit YOKTUR, ama çağrı da patlamamalı.\n *\n * Motorla parite burada \"aynı SQL\" değil, \"aynı SÖZLEŞME\": boş liste\n * no-op, tekrar edenler tekilleşir, ve PK'sı olmayan tablo adıyla\n * reddedilir. Kilidin kendisi tek işlemli bir sahte depoda anlamsız —\n * ama sözleşmeyi bozan bir çağrı burada da hata almalı.\n */\n async lockRows(table: string, ids: readonly string[]) {\n if (ids.length === 0) return;\n const rows = store.get(table) ?? [];\n const unique = [...new Set(ids)].sort();\n const missing = unique.filter((id) => !rows.some((r) => r[\"id\"] === id));\n if (missing.length > 0 && rows.length > 0) {\n // Sessiz geçmek, testin \"kilitledim\" sanmasına yol açardı.\n throw new Error(\n `lockRows(${table}): şu id'ler yok: ${missing.join(\", \")} — kilitlenecek satır bulunamadı.`,\n );\n }\n },\n\n /** Sahte depoda kilit yok; sözleşme (çağrı patlamaz) korunuyor. */\n async advisoryXactLock(_key: string) {\n return undefined;\n },\n\n async claim(\n table: string,\n unique: Record<string, unknown>,\n extra: Record<string, unknown> = {},\n ) {\n const keyCols = Object.keys(unique);\n if (keyCols.length === 0) {\n throw new Error(\n `claim(${table}): benzersiz alan verilmedi. claim, bir anahtarı sahiplenmektir; ` +\n `anahtar yoksa sahiplenecek bir şey de yok — insert(${table}, …) kullanın.`,\n );\n }\n assertUsableWriteValues(\"claim\", table, keyCols, unique);\n const existing = (store.get(table) ?? []).find((r) =>\n keyCols.every((c) => r[c] === unique[c]),\n );\n if (existing) return { inserted: false, row: existing };\n const record = { id: crypto.randomUUID(), ...unique, ...extra };\n rowsOf(table).push(record);\n track(tracked.inserted, table, record);\n return { inserted: true, row: record };\n },\n\n // Same semantics the engine's SQL has: match on the conflict columns, update\n // everything else, and return the resulting row either way.\n async put(\n table: string,\n data: Record<string, unknown>,\n opts: { onConflict: readonly string[] },\n ) {\n if (opts.onConflict.length === 0) {\n throw new Error(`put into ${table}: onConflict en az bir kolon adı ister`);\n }\n assertUsableWriteValues(\"upsert\", table, Object.keys(data), data);\n // Motorla PARİTE: ifade tutamağı bu yolda değer değildir. Fake onu\n // satıra YAZIYORDU; satır artık JSON'a bile çevrilemiyordu.\n assertNoExpressionHandles(\"upsert\", table, Object.keys(data), data);\n const rows = rowsOf(table);\n const existing = rows.find((r) => opts.onConflict.every((c) => r[c] === data[c]));\n if (existing) {\n for (const [k, v] of Object.entries(data)) {\n if (!opts.onConflict.includes(k)) existing[k] = v;\n }\n return existing;\n }\n const record = { id: crypto.randomUUID(), ...data };\n rows.push(record);\n track(tracked.inserted, table, record);\n return record;\n },\n\n async update(table: string, id: string, data: Record<string, unknown>) {\n assertUsableWriteValues(\"update\", table, Object.keys(data), data);\n // Motorla PARİTE: ifade tutamağı bu yolda değer değildir. Fake onu\n // satıra YAZIYORDU; satır artık JSON'a bile çevrilemiyordu.\n assertNoExpressionHandles(\"update\", table, Object.keys(data), data);\n const rows = store.get(table) ?? [];\n const idx = rows.findIndex((r) => r[\"id\"] === id);\n const updated = idx >= 0\n ? { ...rows[idx], ...data }\n : { id, ...data };\n if (idx >= 0) {\n rows[idx] = updated;\n }\n track(tracked.updated, table, updated);\n return updated;\n },\n\n async delete(table: string, id: string) {\n const rows = store.get(table) ?? [];\n const idx = rows.findIndex((r) => r[\"id\"] === id);\n if (idx >= 0) rows.splice(idx, 1);\n const list = tracked.deleted.get(table);\n if (list) list.push(id);\n else tracked.deleted.set(table, [id]);\n },\n\n async findById(table: string, id: string) {\n const rows = store.get(table) ?? [];\n return rows.find((r) => r[\"id\"] === id) ?? null;\n },\n\n // The SAME filter language the engine compiles to SQL: a plain value is\n // equality, an object is an operator set. A fake that understood less would\n // pass a service test that the live database then fails — which is the one\n // thing a stand-in must never do.\n async findMany(\n table: string,\n query?: Record<string, unknown>,\n opts?: {\n orderBy?:\n | { column: string; direction?: \"asc\" | \"desc\"; nulls?: \"first\" | \"last\" }\n | { column: string; direction?: \"asc\" | \"desc\"; nulls?: \"first\" | \"last\" }[];\n select?: string[];\n limit?: number;\n },\n ) {\n assertUsableFilter(\"findMany\", table, query);\n refuseFragment(\"findMany\", table, query);\n const rows = store.get(table) ?? [];\n let out = query\n ? rows.filter((row) => rowMatchesFilter(\"findMany\", table, row, query))\n : [...rows];\n // Çoklu sıralama ve NULL yeri — motorla PARİTE (FR-008). Tek obje de\n // kabul edilir; liste hâline getirilip aynı yoldan geçer.\n const orderSpecs = opts?.orderBy === undefined\n ? []\n : Array.isArray(opts.orderBy) ? opts.orderBy : [opts.orderBy];\n if (orderSpecs.length > 0) {\n out = [...out].sort((a, b) => {\n for (const o of orderSpecs) {\n const dir = o.direction === \"desc\" ? -1 : 1;\n const x = a[o.column];\n const y = b[o.column];\n const xNull = x === null || x === undefined;\n const yNull = y === null || y === undefined;\n if (xNull || yNull) {\n if (xNull && yNull) continue;\n // Verilmezse Postgres varsayılanı: ASC'de NULLS LAST, DESC'te FIRST.\n const nullsFirst = o.nulls === undefined ? dir === -1 : o.nulls === \"first\";\n return (xNull ? 1 : -1) * (nullsFirst ? -1 : 1);\n }\n if (x === y) continue;\n return ((x as never) < (y as never) ? -1 : 1) * dir;\n }\n return 0;\n });\n }\n const page = opts?.limit === undefined ? out : out.slice(0, opts.limit);\n // Projeksiyon (FR-009) — motorla PARİTE. Fake tam satır döndürürse, `select`\n // ile yazılmış bir kod sahte veritabanında seçilmemiş kolonu okur ve GEÇER;\n // gerçek motorda o kolon SQL'e hiç girmediği için `undefined` olur.\n const cols = opts?.select;\n if (cols === undefined || cols.length === 0) return page;\n return page.map((row) => Object.fromEntries(cols.map((c) => [c, row[c]])));\n },\n };\n\n /**\n * Interpret a whole plan, atomically.\n *\n * The rollback is the point. A test that asserts \"the second write failed, so\n * the first one is not there\" must be able to FAIL — a mock that applied ops\n * and left them applied would pass that test while the real broker rolled the\n * transaction back, or the other way round. So the store and the tracking maps\n * are snapshotted, and any failure restores both before rejecting.\n *\n * The rejection carries the same envelope fields the runtime copies off the\n * broker's response (`error_code`, `slot`), because the SDK maps `slot` back\n * to the caller's own Error — a mock that rejected with a bare Error would\n * make every guard in every tenant test look like a generic failure.\n */\n async function txPlan(plan: TxPlanBody): Promise<TxPlanResponse> {\n const snapshot = new Map<string, Record<string, unknown>[]>();\n for (const [table, rows] of store) snapshot.set(table, [...rows]);\n const trackedSnapshot: TrackedRecords = {\n inserted: cloneTracked(tracked.inserted),\n updated: cloneTracked(tracked.updated),\n deleted: new Map([...tracked.deleted].map(([k, v]) => [k, [...v]])),\n };\n\n const results: TxPlanOpResult[] = [];\n try {\n for (const op of plan.ops) {\n const result = applyOp(op, results);\n results.push(result);\n const failure = guardFailure(op.guard, result.rows.length);\n if (failure) throw failure;\n }\n } catch (err) {\n store.clear();\n for (const [table, rows] of snapshot) store.set(table, rows);\n tracked.inserted = trackedSnapshot.inserted;\n tracked.updated = trackedSnapshot.updated;\n tracked.deleted = trackedSnapshot.deleted;\n throw err;\n }\n return { results };\n }\n\n function applyOp(op: TxWireOp, results: TxPlanOpResult[]): TxPlanOpResult {\n switch (op.op) {\n case \"upsert\": {\n const values = resolveMap(op.values ?? {}, results, null);\n const conflict = op.onConflict ?? [];\n const rows = rowsOf(op.table);\n const hit = rows.find((r) => conflict.every((c) => r[c] === values[c]));\n if (hit) {\n for (const [key, value] of Object.entries(values)) {\n if (!conflict.includes(key)) hit[key] = value;\n }\n return { rows: [hit], rows_affected: 1 };\n }\n const created = { id: crypto.randomUUID(), ...values };\n rows.push(created);\n track(tracked.inserted, op.table, created);\n return { rows: [created], rows_affected: 1 };\n }\n case \"insert\": {\n const record = { id: crypto.randomUUID(), ...resolveMap(op.values ?? {}, results, null) };\n rowsOf(op.table).push(record);\n track(tracked.inserted, op.table, record);\n return { rows: [record], rows_affected: 1 };\n }\n case \"insertMany\": {\n const written = (op.rows ?? []).map((row) => {\n const record = { id: crypto.randomUUID(), ...resolveMap(row, results, null) };\n rowsOf(op.table).push(record);\n track(tracked.inserted, op.table, record);\n return record;\n });\n return { rows: written, rows_affected: written.length };\n }\n case \"update\": {\n const rows = rowsOf(op.table);\n const where = resolveMap(op.where ?? {}, results, null);\n const written: Record<string, unknown>[] = [];\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n if (!row || !matches(row, where)) continue;\n const next = { ...row, ...resolveMap(op.set ?? {}, results, row) };\n rows[i] = next;\n track(tracked.updated, op.table, next);\n written.push(next);\n }\n return { rows: written, rows_affected: written.length };\n }\n case \"delete\": {\n const rows = rowsOf(op.table);\n const where = resolveMap(op.where ?? {}, results, null);\n const removed = rows.filter((row) => matches(row, where));\n for (const row of removed) {\n rows.splice(rows.indexOf(row), 1);\n const id = row[\"id\"];\n const list = tracked.deleted.get(op.table);\n const key = typeof id === \"string\" ? id : String(id);\n if (list) list.push(key);\n else tracked.deleted.set(op.table, [key]);\n }\n return { rows: removed, rows_affected: removed.length };\n }\n case \"select\": {\n const where = resolveMap(op.where ?? {}, results, null);\n let found = rowsOf(op.table).filter((row) => matches(row, where));\n if (op.limit !== undefined) found = found.slice(0, op.limit);\n return { rows: found, rows_affected: found.length };\n }\n }\n }\n\n const client: MockDBClient = {\n ...ops,\n\n // No real savepoint in memory: the fake runs the callback against the SAME\n // store. An assertion about rollback here would be asserting the fake.\n attempt: <T,>(fn: (tx: DBOps) => Promise<T>): Promise<T> => fn(ops),\n\n txPlan,\n\n // In tests there is no real DB role; `asService()` returns the same\n // in-memory client so RLS-bypass code paths still hit the same store and\n // tracking maps. The omitted `asService` matches the contract (no\n // double-bypass), so callers can't recurse.\n asService(): Omit<DBClient, \"asService\"> {\n return client;\n },\n\n inserted(table: string) {\n return tracked.inserted.get(table) ?? [];\n },\n\n updated(table: string) {\n return tracked.updated.get(table) ?? [];\n },\n\n deleted(table: string) {\n return tracked.deleted.get(table) ?? [];\n },\n\n seed(table: string, data: Record<string, unknown>[]) {\n store.set(table, [...data]);\n },\n };\n\n return client;\n}\n\nfunction cloneTracked(\n map: Map<string, Record<string, unknown>[]>,\n): Map<string, Record<string, unknown>[]> {\n return new Map([...map].map(([k, v]) => [k, [...v]]));\n}\n\n/** Resolve one plan value: a `$ref` into an earlier result, a `$expr`, or a\n * literal. `current` is the row being updated, which is what `inc`/`dec` read. */\nfunction resolveValue(\n value: TxWireValue,\n results: TxPlanOpResult[],\n current: Record<string, unknown> | null,\n column: string,\n): unknown {\n if (typeof value !== \"object\" || value === null) return value;\n const tagged = value as { $ref?: { op: number; field: string }; $expr?: Record<string, unknown> };\n\n if (tagged.$ref) {\n const row = results[tagged.$ref.op]?.rows[0];\n if (!row) {\n throw txRejection(409, \"tx_ref_unresolved\", {\n message: `operation ${tagged.$ref.op} produced no row to reference`,\n });\n }\n return row[tagged.$ref.field];\n }\n\n if (tagged.$expr) {\n const fn = tagged.$expr[\"fn\"];\n if (fn === \"now\") return new Date().toISOString();\n const by = Number(tagged.$expr[\"by\"]);\n const base = Number(current?.[column] ?? 0);\n return fn === \"dec\" ? base - by : base + by;\n }\n\n return value;\n}\n\nfunction resolveMap(\n map: Record<string, TxWireValue>,\n results: TxPlanOpResult[],\n current: Record<string, unknown> | null,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(map)) {\n out[key] = resolveValue(value, results, current, key);\n }\n return out;\n}\n\n/** Equality filter, with `null` meaning IS NULL — the broker's rule, so a\n * `{ accepted_at: null }` guard behaves the same in a test as in production. */\nfunction matches(row: Record<string, unknown>, where: Record<string, unknown>): boolean {\n return Object.entries(where).every(([key, value]) =>\n value === null ? row[key] === null || row[key] === undefined : row[key] === value,\n );\n}\n\nfunction guardFailure(guard: TxWireGuard | undefined, count: number): unknown {\n if (!guard) return null;\n const ok =\n guard.kind === \"one\"\n ? count === 1\n : guard.kind === \"none\"\n ? count === 0\n : guard.kind === \"atLeast\"\n ? count >= guard.n\n : count <= guard.n;\n if (ok) return null;\n return txRejection(409, \"tx_guard_failed\", {\n slot: guard.slot,\n message: `expected ${guard.kind} ${guard.n} row(s), got ${count}`,\n });\n}\n\n/** Build a rejection shaped like the one the runtime throws for a broker error:\n * an Error carrying the envelope's `status`/`error_code`/`slot`. */\nfunction txRejection(\n status: number,\n code: string,\n extra: { slot?: number; message: string },\n): Error & TxPlanRejection {\n const err = new Error(extra.message) as Error & TxPlanRejection;\n err.status = status;\n err.error_code = code;\n if (extra.slot !== undefined) err.slot = extra.slot;\n return err;\n}\n","/**\n * `fakeDatabase()` — the in-memory `Database` a SERVICE-LAYER test runs against.\n *\n * WHY IT IS PUBLIC. The scaffold's own AGENTS.md tells authors to \"test the\n * service layer… the test passes a stand-in\", and the SDK shipped no stand-in to\n * pass. So every project wrote its own: a measured customer run carried two — a\n * hand-written `MembershipDb` interface for one service, and a bare `{ query }`\n * object in the tests of another. Both are guesses at this SDK's own surface,\n * and both stop compiling the moment the surface grows.\n *\n * The engine already had exactly this object; it just lived under `__tests__/`\n * where only this package could reach it.\n *\n * WHAT IT IS NOT. It does not interpret SQL. `query()` records what it was asked\n * and answers no rows, because a fake that parsed SQL would be a second, worse\n * Postgres — and a test that passed against it would prove nothing about the\n * real one. Assert on `queries` when the SQL is the thing under test, and put\n * anything that depends on what SQL RETURNS in a live test (`palbase test`).\n */\nimport { createMockDB } from \"../__tests__/helpers/mock-db.js\";\nimport type { DBClient } from \"../endpoint.js\";\n\n/** One `Database.$query(...)` call, as the service made it. */\nexport interface RecordedQuery {\n sql: string;\n params: unknown[];\n}\n\n/** What a service-layer test is handed. */\nexport interface FakeDatabase {\n /** Pass this where the service expects `Database`. */\n db: DBClient;\n /** Every `query()` call, in order. */\n queries: readonly RecordedQuery[];\n /** Put rows in a table before the code under test runs. */\n seed(table: string, rows: Record<string, unknown>[]): void;\n /** Rows inserted into a table, for asserting a write happened. */\n inserted(table: string): Record<string, unknown>[];\n /** Rows updated in a table. */\n updated(table: string): Record<string, unknown>[];\n /** Ids deleted from a table. */\n deleted(table: string): string[];\n}\n\nexport function fakeDatabase(): FakeDatabase {\n const mock = createMockDB();\n const queries: RecordedQuery[] = [];\n\n // The recorder wraps `query` and leaves every other op alone, so the fake's\n // behaviour is the engine's mock plus one observation.\n const db: DBClient = Object.assign(Object.create(Object.getPrototypeOf(mock) as object) as DBClient, mock, {\n query: async (sql: string, params: unknown[] = []) => {\n queries.push({ sql, params });\n return mock.query(sql, params);\n },\n });\n\n return {\n db,\n queries,\n seed: (table, rows) => mock.seed(table, rows),\n inserted: (table) => mock.inserted(table),\n updated: (table) => mock.updated(table),\n deleted: (table) => mock.deleted(table),\n };\n}\n"],"mappings":"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","names":["TestApiError","Error","status","error","data","body","method","path","envelope","code","String","error_description","name","required","value","envName","isLocalTarget","baseUrl","hostname","URL","secondsUntilExpiry","token","split","claims","JSON","parse","Buffer","from","toString","exp","Math","floor","Date","now","createTestApi","config","replace","apiKey","local","candidateToken","doFetch","fetch","requests","bearer","call","opts","headers","apikey","authorization","undefined","startedAt","res","stringify","text","parsed","safeParse","push","ms","ok","left","abs","get","post","patch","put","delete","query","signInAs","identity","identities","declared","Object","keys","length","join","accessToken","id","email","signIn","credentials","attempt","extra","result","e","refusal","challenge","asPowChallenge","solvePowChallenge","access_token","user","signOut","asAnonymous","parseIdentities","raw","configured","api","Proxy","_target","prop","process","env","PALBASE_TEST_BASE_URL","PALBASE_TEST_API_KEY","PALBASE_TEST_CANDIDATE","PALBASE_TEST_IDENTITIES","Reflect","isolated","over","Map","local","make","c","has","get","hit","undefined","meta","Reflect","getMetadata","inst","map","d","set","api","with","t","v","REF_BRAND","Symbol","for","brandOf","v","undefined","Object","hasOwn","REF_BRAND","isColRef","v","brandOf","$col","isSqlFragment","v","brandOf","f","$sql","undefined","Array","isArray","text","values","TX_EXPR","Symbol","for","isColumnExpr","assertNoExpressionHandles","caller","table","cols","data","c","Error","isColRef","KNOWN_OPS","Set","REFUSED_OPS","eq","assertUsableFilter","where","COMPOSITES","col","cond","Object","entries","has","branches","b","rel","inner","length","op","some","x","assertUsableWriteValues","TxRefError","Error","message","name","TxPlanError","EXPR","Symbol","for","REF","ROW","ROWS","TRAPPED_PROPS","toPrimitive","trap","prop","what","hint","description","String","columnExprOf","v","exprOf","makeRef","op","field","target","REF","Proxy","get","t","prop","TRAPPED_PROPS","includes","trap","undefined","makeRowHandle","ROW","exprOf","v","e","EXPR","SKIPPED_OP","TxRowsImpl","ROWS","guarded","builder","opIndex","what","then","TxRefError","expectOne","error","declareGuard","makeRowHandle","expectNone","expectAtLeast","n","assertGuardCount","expectAtMost","kind","Error","TxPlanError","attachGuard","fn","Number","isInteger","String","refuseFragment","caller","table","where","isSqlFragment","Error","k","v","Object","entries","branch","Array","isArray","addDecimal","cell","by","sign","undefined","a","String","b","parse","x","m","exec","trim","frac","unit","BigInt","scale","length","pa","pb","Math","max","lift","total","Number","toString","neg","digits","padStart","out","slice","rowMatchesFilter","row","f","every","c","some","matchesCell","cmp","op","l","Date","getTime","r","key","cond","isColRef","$col","includes","toLowerCase","startsWith","endsWith","createMockDB","store","Map","tracked","inserted","updated","deleted","rowsOf","rows","get","set","track","map","list","push","ops","updateMany","keys","assertUsableFilter","assertUsableWriteValues","hit","filter","expr","columnExprOf","fn","deleteMany","keep","count","search","_table","_params","facets","similar","recommend","supersede","_id","id","crypto","randomUUID","query","_sql","insert","data","assertNoExpressionHandles","record","lockRows","ids","unique","Set","sort","missing","join","advisoryXactLock","_key","claim","extra","keyCols","existing","find","put","opts","onConflict","update","idx","findIndex","delete","splice","findById","findMany","orderSpecs","orderBy","o","dir","direction","column","y","xNull","yNull","nullsFirst","nulls","page","limit","cols","select","fromEntries","txPlan","plan","snapshot","trackedSnapshot","cloneTracked","results","result","applyOp","failure","guardFailure","guard","err","clear","values","resolveMap","conflict","value","rows_affected","created","written","i","matches","next","removed","indexOf","found","client","attempt","asService","seed","resolveValue","current","tagged","$ref","txRejection","message","field","$expr","toISOString","base","ok","kind","n","slot","status","code","error_code","fakeDatabase","mock","createMockDB","queries","db","Object","assign","create","getPrototypeOf","query","sql","params","push","seed","table","rows","inserted","updated","deleted"]}
|
|
1
|
+
{"version":3,"sources":["../../src/test/index.ts","../../../core/src/config.ts","../../../core/src/errors.ts","../../../core/src/pow.ts","../../../core/src/platform.ts","../../../core/src/http.ts","../../../core/src/token.ts","../../src/test/api.ts","../../src/test/container.ts","../../src/db/input-guards.ts","../../src/db/tx-plan.ts","../../src/__tests__/helpers/mock-db.ts","../../src/test/fake-db.ts"],"sourcesContent":["export { api, createTestApi, TestApiError } from \"./api.js\";\nexport { isolated } from \"./container.js\";\nexport type { IsolatedContainer } from \"./container.js\";\nexport { fakeDatabase } from \"./fake-db.js\";\nexport type { FakeDatabase, RecordedQuery } from \"./fake-db.js\";\nexport type {\n CallOptions,\n ErrorEnvelope,\n RecordedRequest,\n TestApi,\n TestApiConfig,\n TestIdentity,\n} from \"./api.js\";\n","import type { HttpClient } from './http.js';\nimport type { ProjectConfig } from './types.js';\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ConfigFetcher {\n protected readonly httpClient: HttpClient;\n private cachedConfig: ProjectConfig | null = null;\n private cacheTimestamp = 0;\n\n constructor(httpClient: HttpClient) {\n this.httpClient = httpClient;\n }\n\n async getConfig(): Promise<ProjectConfig | null> {\n const now = Date.now();\n\n if (this.cachedConfig && now - this.cacheTimestamp < CACHE_TTL_MS) {\n return this.cachedConfig;\n }\n\n try {\n const response = await this.httpClient.request<ProjectConfig>('GET', '/v1/config');\n\n if (response.error || !response.data) {\n return null;\n }\n\n this.cachedConfig = response.data;\n this.cacheTimestamp = now;\n\n return this.cachedConfig;\n } catch {\n return null;\n }\n }\n}\n","export class PalbaseError extends Error {\n readonly code: string;\n readonly status: number;\n readonly details?: unknown;\n\n constructor(code: string, message: string, status: number, details?: unknown) {\n super(message);\n this.name = 'PalbaseError';\n this.code = code;\n this.status = status;\n this.details = details;\n }\n}\n","// Proof-of-work: the bot gate in front of /auth/signup and /auth/login.\n//\n// The server answers an unsolved request with 403 and a challenge in the body:\n//\n// { \"error\": \"pow_required\", \"challenge\": { \"id\", \"prefix\", \"difficulty\" } }\n//\n// A client finds any nonce whose SHA-256(prefix + nonce) begins with\n// `difficulty` zero bits, then repeats the request carrying the id and nonce as\n// headers. The work is the point: a person signing up pays it once and does not\n// notice, a script signing up ten thousand times pays it ten thousand times.\n//\n// # Why this lives in core, and not one layer up\n//\n// Until 2026-08-14 nothing shipped could solve it: the gate was written with the\n// server and its own integration harness, and every real client sent requests\n// without the headers and got 403. It was then solved in @palbase/web's own\n// request path — which covers `pb.call` and the module facades and NOT\n// `pb.auth.*`, because those go through @palbase/auth's client and from there\n// into core's HttpClient. So the fix landed everywhere except the two endpoints\n// the gate actually guards, and `npm i @palbase/web` still could not sign a\n// person in. Measured 2026-08-18 against a real stack, on the published 7.3.0.\n//\n// The lesson is where a retry belongs: at the layer that ISSUES the request.\n// Core owns fetch for every client in this repo, so core owns the challenge.\n//\n// WebCrypto rather than a hashing dependency: `crypto.subtle` is present in\n// browsers and in Node 18+, which is the same floor the rest of the SDK sets.\n// Measured at the server's default difficulty of 16: ~330ms, ~65k digests.\n\n/** The challenge a `pow_required` response carries. */\nexport interface PowChallenge {\n id: string;\n prefix: string;\n difficulty: number;\n}\n\n/** Header names the retry must carry. Mirrors the server's constants. */\nexport const POW_CHALLENGE_ID_HEADER = 'X-PoW-Challenge-ID';\nexport const POW_NONCE_HEADER = 'X-PoW-Nonce';\n\n/**\n * Reads a challenge out of an error envelope, or returns null when the envelope\n * is not a `pow_required` one.\n *\n * The whole wire envelope is stored on the error, so the challenge arrives\n * without the HTTP layer having to know about proof-of-work at all.\n */\nexport function asPowChallenge(details: unknown): PowChallenge | null {\n if (typeof details !== 'object' || details === null) return null;\n const env = details as Record<string, unknown>;\n if (env.error !== 'pow_required') return null;\n const c = env.challenge;\n if (typeof c !== 'object' || c === null) return null;\n const { id, prefix, difficulty } = c as Record<string, unknown>;\n if (typeof id !== 'string' || typeof prefix !== 'string') return null;\n if (typeof difficulty !== 'number' || !Number.isInteger(difficulty) || difficulty < 0) return null;\n return { id, prefix, difficulty };\n}\n\nconst encoder = new TextEncoder();\n\n/**\n * One SHA-256, by the fastest route this runtime offers.\n *\n * Awaiting `crypto.subtle.digest` once per nonce is what made this expensive,\n * and the cost is the await rather than the hashing. Measured on one machine,\n * 200k digests of a 40-byte input:\n *\n *\tawaited crypto.subtle.digest 105,597 digests/s\n *\tsync node:crypto createHash 1,324,503 digests/s — 12.5x\n *\n * That is the difference between difficulty 24 taking 159 seconds and taking\n * 13. Node, Bun and Deno all have the sync one; a browser has only WebCrypto,\n * and there it stays async.\n *\n * The specifier is assembled at runtime so a browser bundler does not try to\n * resolve `node:crypto` and fail the build over a branch that never runs there.\n */\ntype Hasher = (input: string) => Uint8Array | Promise<Uint8Array>;\n\nlet hasher: Hasher | null = null;\n\nasync function digester(): Promise<Hasher> {\n if (hasher) return hasher;\n // Read off globalThis with an inline shape rather than by naming `process`,\n // which needs @types/node — a dependency this package does not have and should\n // not grow for one branch. It typechecked locally only because those types\n // were hoisted into node_modules by a sibling package; the publish workflow's\n // clean checkout is what said so, which is exactly what it is for.\n const runtime = globalThis as {\n process?: { versions?: { node?: string; bun?: string } };\n };\n const nodeish =\n runtime.process?.versions?.node !== undefined ||\n runtime.process?.versions?.bun !== undefined;\n if (nodeish) {\n try {\n const mod = (await import(/* @vite-ignore */ `${'node:'}crypto`)) as {\n createHash?: (alg: string) => { update(s: string): { digest(): Uint8Array } };\n };\n if (typeof mod.createHash === 'function') {\n const createHash = mod.createHash;\n hasher = (input: string) => new Uint8Array(createHash('sha256').update(input).digest());\n return hasher;\n }\n } catch {\n // No node:crypto here. WebCrypto below is not a fallback in the apologetic\n // sense — it is the only hash a browser has, and it is correct.\n }\n }\n hasher = async (input: string) =>\n new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(input)));\n return hasher;\n}\n\n/** Monotonic where it exists, wall-clock where it does not. */\nconst now = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n\n/** Counts leading zero bits, stopping at the first byte that has a one. */\nfunction leadingZeroBits(hash: Uint8Array): number {\n let bits = 0;\n for (const byte of hash) {\n if (byte === 0) {\n bits += 8;\n continue;\n }\n // clz32 counts across 32 bits; a byte occupies the low 8, so the first 24\n // are always zero and get subtracted back off.\n return bits + Math.clz32(byte) - 24;\n }\n return bits;\n}\n\n/**\n * The hardest challenge this client will attempt.\n *\n * Not a taste: it is the server's own ceiling. palauth maps a risk score to a\n * difficulty and its worst case is 24 (`DifficultyForRisk`, bot/pow.go:156-166).\n * Anything above that cannot have come from a stack behaving as designed, and\n * the cost of humouring it falls entirely on this side — each step up DOUBLES\n * the work, so difficulty 30 is sixty-four times a legitimate worst case and, on\n * the web, sixty-four times a frozen main thread. Refused immediately, by name.\n */\nexport const MAX_POW_DIFFICULTY = 24;\n\n/**\n * Finds a nonce satisfying the challenge and returns the headers a retry needs.\n *\n * THE BUDGET SCALES WITH THE CHALLENGE, and the first version of this did not.\n * It bounded the search at a flat `1 << 24` — which is not a generous bound for\n * difficulty 24, it is the EXPECTED number of attempts. Finding a nonce is a\n * geometric process: the chance of needing more than 2^d attempts is 1/e, so a\n * flat 2^24 would have failed roughly 37% of legitimate hardest-risk challenges,\n * and failed them for precisely the users the gate exists to slow down — who\n * would have been unable to sign in at all rather than made to wait.\n *\n * Eight times expected puts that at e^-8, about three in ten thousand, while\n * leaving the common case (the server's default 16, and 12 for an unremarkable\n * caller) exactly as cheap as it was.\n *\n * `powBudget` is exported and separate so the RELATIONSHIP can be asserted\n * directly. A test that only watches a cheap challenge succeed cannot tell this\n * budget from the flat one it replaced — measured: reinstating `1 << 24` left\n * such a test green.\n */\nexport function powBudget(difficulty: number): number {\n return 8 * 2 ** difficulty;\n}\n\n/**\n * The longest a solve may be ALLOWED to take, and the difference from a\n * deadline is the whole point.\n *\n * The first version of this was a flat 120s deadline, and it was measured to be\n * worse than the flat iteration budget it was meant to backstop: at ~105k\n * digests/s, difficulty 24 EXPECTS 159 seconds, so a 120s clock killed the\n * majority of legitimate hardest-risk solves — reintroducing, larger, exactly\n * the class of defect that replacing `1 << 24` had removed. Guessing a number\n * for an unknown machine cannot work: the same difficulty is 13 seconds on a\n * runtime with a sync hasher and 159 on one without.\n *\n * So the machine is MEASURED, and the decision moves to the front. A short\n * calibration gives the rate this process actually hashes at; if the whole\n * iteration budget cannot fit in this window at that rate, the solve is refused\n * IMMEDIATELY, naming the numbers. A caller then learns in milliseconds that\n * this difficulty is unpayable here, instead of after two minutes of work\n * thrown away.\n *\n * What remains after that is a guarantee rather than a gamble: a solve that\n * starts can always finish inside its budget, so the only failure left is the\n * budget's own e^-8.\n */\nexport const POW_TIME_BUDGET_MS = 120_000;\n\n/**\n * The window the rate is measured over, and the warm-up it deliberately skips.\n *\n * All of these are REAL attempts — the search starts at nonce 0 and never\n * restarts — so calibration costs nothing but the reading. The first 1024 are\n * excluded from the timing because they include this loop's own JIT warm-up:\n * measured, timing from zero reported 747k digests/s on a machine whose steady\n * rate is 1.32M, and the decision below would have refused a difficulty this\n * machine can pay in half the allowance.\n */\nconst CALIBRATION_WARMUP = 1024;\nconst CALIBRATION_END = 9216;\n\nexport async function solvePowChallenge(\n challenge: PowChallenge,\n maxIterations = powBudget(challenge.difficulty),\n // The caller's AbortSignal, honoured INSIDE the loop rather than only around\n // the fetch it precedes — for the callers that have one. `pb.auth.signIn` does\n // NOT: it reaches the network through @palbase/auth's client, which takes\n // credentials and nothing else. So it is the extra a caller can opt into, and\n // POW_TIME_BUDGET_MS below is what actually bounds the work.\n signal?: AbortSignal,\n): Promise<Record<string, string>> {\n if (challenge.difficulty > MAX_POW_DIFFICULTY) {\n throw new Error(\n `proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`,\n );\n }\n\n const digest = await digester();\n let warmedAt = 0;\n let calibrated = false;\n // Armed by the calibration below, never before it: until the rate is known\n // there is no honest number to put here.\n let deadline = Number.POSITIVE_INFINITY;\n\n for (let nonce = 0; nonce < maxIterations; nonce++) {\n // Checked in batches: reading them is cheap but not free, and a\n // 1024-digest granularity bounds the delay at a few milliseconds.\n if ((nonce & 1023) === 0) {\n if (signal?.aborted) {\n throw new DOMException('proof-of-work solve aborted', 'AbortError');\n }\n if (now() > deadline) {\n throw new Error(\n `proof-of-work: gave up on difficulty ${challenge.difficulty} after ${POW_TIME_BUDGET_MS / 1000}s ` +\n `and ${nonce.toLocaleString()} attempts — the tail this run drew is longer than the allowance`,\n );\n }\n }\n\n // THE DECISION, TAKEN ONCE AND TAKEN EARLY.\n //\n // After CALIBRATION_DIGESTS real attempts the rate of THIS process is\n // known, so the question \"can this machine pay this difficulty\" has an\n // answer instead of an assumption. If the whole budget cannot fit in the\n // time budget, refuse here — milliseconds in, with the numbers — rather\n // than spend two minutes and throw them away. If it fits, everything after\n // this point is guaranteed to finish inside the window, so the only\n // remaining failure is the budget's own e^-8.\n if (nonce === CALIBRATION_WARMUP) {\n warmedAt = now();\n }\n if (!calibrated && nonce === CALIBRATION_END) {\n calibrated = true;\n const elapsed = Math.max(now() - warmedAt, 0.001);\n const rate = (CALIBRATION_END - CALIBRATION_WARMUP) / (elapsed / 1000);\n // EXPECTED, not worst case, and the difference is the whole judgement.\n //\n // Finding a nonce is geometric: 2^difficulty attempts on average, with a\n // long tail the 8x budget covers. Refusing because the TAIL will not fit\n // would turn away work whose expected cost is seventeen seconds — measured\n // exactly that on this machine at difficulty 24. Refusing on the EXPECTED\n // cost turns away only what is genuinely unpayable here, and what it lets\n // through is then cut by the clock with probability e^-(budget/expected):\n // at 120s against a 17s expectation that is one run in a thousand, and at\n // difficulty 20 on a browser it is one in a hundred and fifty thousand.\n const expectedMs = (2 ** challenge.difficulty / rate) * 1000;\n if (expectedMs > POW_TIME_BUDGET_MS) {\n throw new Error(\n `proof-of-work: difficulty ${challenge.difficulty} needs about ${Math.round(expectedMs / 1000)}s here ` +\n `(${Math.round(rate).toLocaleString()} digests/s) and this client allows ${POW_TIME_BUDGET_MS / 1000}s; ` +\n `refusing before spending the time rather than after`,\n );\n }\n deadline = now() + (POW_TIME_BUDGET_MS - (now() - warmedAt));\n }\n\n const hash = await digest(challenge.prefix + nonce);\n if (leadingZeroBits(hash) >= challenge.difficulty) {\n return {\n [POW_CHALLENGE_ID_HEADER]: challenge.id,\n [POW_NONCE_HEADER]: String(nonce),\n };\n }\n }\n throw new Error(\n `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`,\n );\n}\n","export type Platform = 'browser' | 'node' | 'react-native' | 'deno' | 'bun';\n\ndeclare const Deno: unknown;\ndeclare const process: { versions: Record<string, string> } | undefined;\n\nexport function detectPlatform(): Platform {\n if (typeof Deno !== 'undefined') {\n return 'deno';\n }\n\n if (process?.versions) {\n if ('bun' in process.versions) {\n return 'bun';\n }\n if ('node' in process.versions) {\n return 'node';\n }\n }\n\n if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {\n return 'react-native';\n }\n\n return 'browser';\n}\n\n/**\n * The platform word this SDK puts on the wire (`X-Platform`), which the server\n * reads to target flags and to label telemetry.\n *\n * It is NOT `detectPlatform()`'s value verbatim: that reports the JS host\n * (\"browser\"), while the wire wants the platform. iOS sends \"ios\", not the name\n * of its runtime, and a condition author writes `client.platform == 'web'` —\n * the word every other flag vendor uses too. Server hosts keep their own names,\n * where the distinction is the useful part.\n */\nexport function wirePlatform(): string {\n const host = detectPlatform();\n return host === 'browser' ? 'web' : host;\n}\n","import { PalbaseError } from './errors.js';\nimport { asPowChallenge, solvePowChallenge } from './pow.js';\nimport { wirePlatform } from './platform.js';\nimport type { TokenManager } from './token.js';\nimport type { HttpClientOptions, PalbaseResponse, RequestOptions } from './types.js';\n\n/**\n * Default production host. Dev / staging / local callers override via\n * `options.url`. Apex-style routing is the only supported production path;\n * Kong resolves Environment identity from the API key.\n */\nconst PALBASE_DEFAULT_HOST = 'api.palbase.studio';\n\n/**\n * Parse the Environment ref from a Palbase API key.\n *\n * Canonical shape: `pb_{environment_ref}_c{random}`, where the Environment ref\n * is 4-24 lowercase ASCII alphanumeric characters and random is AT LEAST 20\n * base62 chars.\n *\n * The length is a floor, not an equality. The stack's own minter writes 20\n * (v2/cmd/palsvc/initenv.go) and the cloud control plane writes 32\n * (v2-cloud/platform/server/services/keys.ts), and the door that admits the\n * request refuses to rule on the difference: *\"a shorter or longer secret is\n * not a security property this door can rule on\"*\n * (v2/internal/platform/identitymw.go: parseAPIKey). A client that is stricter\n * than the server does not add safety — it just refuses working keys, which is\n * exactly what this one did to every cloud project until 2026-08-25.\n *\n * Returns the Environment ref on match; `null` otherwise.\n */\nconst API_KEY_RE = /^pb_([a-z0-9]{4,24})_c[A-Za-z0-9]{20,}$/;\n\nfunction parseEnvironmentRef(apiKey: string): string | null {\n return API_KEY_RE.exec(apiKey)?.[1] ?? null;\n}\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 200;\n/**\n * Upper bound on a single 429 retry sleep. A server may return a long\n * Retry-After (a locked account can send minutes/hours); honoring it verbatim\n * would HANG the request for that whole window. Cap each retry at 10s — after\n * MAX_RETRIES the 429 envelope surfaces to the caller (fail fast, don't sleep\n * minutes). The clamp never skips a retry; it only bounds how long each waits.\n */\nconst MAX_RETRY_DELAY_MS = 10_000;\n\n/**\n * Carry a 429's retry hint into the error envelope when only the header has it.\n *\n * A REFUSAL FROM THE EDGE CARRIES NOTHING BUT THE HEADER. Envoy and the\n * gateway limiter answer before any Palbase service is reached, so their 429\n * has no `retry_after` and no `data.retryAfter` — and every reader above this\n * layer (`@palbase/web`'s BackendError, the iOS SDK) looks in the BODY. The\n * seconds were on the wire and unreachable to all of them.\n *\n * Lifted under `retry_after`, the platform's own name for it (palauth's\n * rate-limit envelope), never overwriting a hint the service itself sent — a\n * service knows its window, the edge only knows its own.\n */\nfunction withRetryHint(\n body: Record<string, unknown> | undefined,\n response: Response,\n): Record<string, unknown> | undefined {\n if (response.status !== 429) return body;\n const data = body?.data;\n const alreadyStated =\n typeof body?.retry_after === 'number' ||\n (typeof data === 'object' && data !== null && 'retryAfter' in data);\n if (alreadyStated) return body;\n const seconds = Number.parseInt(response.headers.get('Retry-After') ?? '', 10);\n if (Number.isNaN(seconds) || seconds <= 0) return body;\n return { ...body, retry_after: seconds };\n}\n\n/**\n * Request interceptor. Runs before every HTTP request.\n * Can modify headers, body, or reject the request.\n */\nexport type RequestInterceptor = (request: {\n headers: Record<string, string>;\n method: string;\n path: string;\n}) => void | Promise<void>;\n\nexport class HttpClient {\n protected readonly apiKey: string;\n protected readonly options?: HttpClientOptions;\n\n tokenManager: TokenManager | null = null;\n\n /**\n * Admin JWT used for platform admin endpoints (/admin/*).\n * When set, takes precedence over tokenManager access token in the\n * Authorization header.\n */\n adminToken: string | null = null;\n\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(apiKey: string, options?: HttpClientOptions) {\n this.apiKey = apiKey;\n this.options = options;\n }\n\n /** Set (or clear) the admin JWT used on admin endpoints. */\n setAdminToken(token: string | null): void {\n this.adminToken = token;\n }\n\n /**\n * Create a scoped HttpClient that adds the given extra headers to every\n * request. The returned client shares the admin token and token manager\n * with the parent at runtime — later changes on the parent propagate to\n * the scope and vice versa.\n *\n * Typical use: adding an Environment-routing header for an admin call.\n */\n withHeaders(extra: Record<string, string>): HttpClient {\n const mergedHeaders = { ...(this.options?.headers ?? {}), ...extra };\n\n const scoped: HttpClient = new HttpClient(this.apiKey, {\n ...this.options,\n headers: mergedHeaders,\n });\n scoped.tokenManager = this.tokenManager;\n // Delegate adminToken reads + writes to the parent so the scope always\n // sees the latest token, and setAdminToken on the scope affects the parent.\n Object.defineProperty(scoped, 'adminToken', {\n get: () => this.adminToken,\n set: (v: string | null) => {\n this.adminToken = v;\n },\n configurable: true,\n });\n return scoped;\n }\n\n /** Add a request interceptor. Runs before every request. */\n addInterceptor(interceptor: RequestInterceptor): void {\n this.interceptors.push(interceptor);\n }\n\n async request<T>(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<PalbaseResponse<T>> {\n // If token is expired and refresh is available, refresh before making the request\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n // Terminal: the refresh token is dead (revoked/expired/forbidden).\n // Clear the session (listeners persist the sign-out) and proceed\n // unauthenticated — the endpoint will 401 into the normal error\n // envelope instead of bricking every subsequent call including\n // the recovery sign-in.\n this.tokenManager.clearSession();\n } else {\n throw e; // network/5xx: transient, stay loud\n }\n }\n }\n\n return this.executeWithRetry<T>(method, path, options, 0);\n }\n\n /**\n * A response read as it arrives, for `text/event-stream` routes.\n *\n * Deliberately NOT `executeWithRetry`: a retry replays the request, and a\n * stream the caller has already begun reading cannot be replayed — the frames\n * it handed over would arrive a second time. A stream that fails to open fails\n * to the caller, once, with its status.\n *\n * The buffered path's headers, base URL and interceptors are reused verbatim,\n * so a streaming call is authenticated exactly like every other call; only the\n * body handling differs. `Accept` says what the caller wants, and the status\n * is returned beside the body because the CALLER decides what a non-2xx means\n * (an error envelope arrives as an ordinary buffered body).\n */\n async requestStream(\n method: string,\n path: string,\n options?: RequestOptions,\n ): Promise<{ status: number; body: ReadableStream<Uint8Array> | null; contentType: string }> {\n if (\n this.tokenManager?.isExpired() &&\n this.tokenManager.getRefreshToken() &&\n this.tokenManager.refreshFunction\n ) {\n try {\n await this.tokenManager.refreshSession();\n } catch (e) {\n const status = e instanceof PalbaseError ? e.status : 0;\n if (status === 400 || status === 401 || status === 403) {\n this.tokenManager.clearSession();\n } else {\n throw e;\n }\n }\n }\n\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), Accept: 'text/event-stream' };\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = { method, headers, signal: options?.signal };\n if (options?.body !== undefined) fetchOptions.body = JSON.stringify(options.body);\n\n const response = await fetch(url, fetchOptions);\n return {\n status: response.status,\n body: response.body,\n contentType: response.headers.get('content-type') ?? '',\n };\n }\n\n private getBaseUrl(): string {\n // Explicit URL always wins (local dev, staging, test rigs).\n if (this.options?.url) {\n return this.options.url;\n }\n\n // Validate the key shape up front so apex-routed callers still\n // fail loud on a malformed key instead of hitting the gateway\n // with bad credentials.\n if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {\n throw new PalbaseError(\n 'invalid_api_key',\n 'Invalid API key format. Expected pb_{environment_ref}_c{at least 20 base62 chars}. For dev/staging pass `url: \"https://api.dev.palbase.studio\"` via options.',\n 0,\n );\n }\n\n return `https://${PALBASE_DEFAULT_HOST}`;\n }\n\n private buildHeaders(options?: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n // Client identity, the web counterpart of the iOS SDK's\n // ClientInfo.augment(). The server reads these to resolve flag targeting\n // conditions and to label telemetry, so an app declares nothing and calls\n // nothing — whatever the SDK can know, it sends.\n 'X-Platform': wirePlatform(),\n };\n // The host app's own version is not knowable on the web (no bundle to read\n // it from), so it is opt-in; when given it fills the same header iOS fills\n // from CFBundleShortVersionString.\n const appVersion = this.options?.appVersion?.trim();\n if (appVersion) {\n headers['X-Palbase-Client-Version'] = appVersion;\n }\n\n // Palbase Environment keys live in the `apikey` header — never in\n // `Authorization` — because Kong's key-auth resolves them on that\n // header and the gateway's pre-function plugin stamps the downstream\n // identity.\n const effectiveKey = this.apiKey;\n if (effectiveKey) {\n headers['apikey'] = effectiveKey;\n }\n\n // User session token, if any. Kong's pre-function plugin strips\n // Authorization on /v1/* routes anyway (PostgREST has no JWT\n // secret and would crash on a Bearer it can't decode), but\n // sending it preserves the contract for /auth/* endpoints that\n // do consume the bearer (e.g. session refresh).\n const token = this.tokenManager?.getAccessToken();\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // adminToken (platform admin JWT) takes precedence — used by the\n // @palbase/admin internal flows that hit /admin/* routes; those\n // routes verify the bearer themselves and aren't subject to the\n // /v1/* Authorization-strip rule.\n if (this.adminToken) {\n headers['Authorization'] = `Bearer ${this.adminToken}`;\n }\n\n // Merge global custom headers\n if (this.options?.headers) {\n Object.assign(headers, this.options.headers);\n }\n\n // Merge per-request headers\n if (options?.headers) {\n Object.assign(headers, options.headers);\n }\n\n return headers;\n }\n\n private async executeWithRetry<T>(\n method: string,\n path: string,\n options: RequestOptions | undefined,\n attempt: number,\n // Headers a PREVIOUS attempt earned and this one has to carry. Today that\n // is only the solved proof-of-work pair; it is a parameter rather than a\n // field because it belongs to one request's second try, and a field would\n // leak it onto every later call made through this client.\n earned?: Record<string, string>,\n ): Promise<PalbaseResponse<T>> {\n const url = `${this.getBaseUrl()}${path}`;\n const headers = { ...this.buildHeaders(options), ...earned };\n\n // Run interceptors\n for (const interceptor of this.interceptors) {\n await interceptor({ headers, method, path });\n }\n\n const fetchOptions: RequestInit = {\n method,\n headers,\n signal: options?.signal,\n };\n\n if (options?.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let response: Response;\n try {\n response = await fetch(url, fetchOptions);\n } catch (error) {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;\n await this.delay(backoff);\n // WITHOUT `earned`, and that is the whole point of this line.\n //\n // A network error means the response was lost, not that the request\n // was. If it reached the server, the challenge is already SPENT —\n // palauth's VerifyChallenge reads and deletes in one step\n // (bot/pow.go:96-104), deliberately, because a proof presented twice is\n // not proof. Replaying the nonce would then answer `pow_invalid`, and\n // the one-solve guard below would refuse to try again: a request one\n // fresh solve away from succeeding, failed. Dropping it costs nothing\n // in the other case — if the server never saw the request, a fresh\n // challenge works exactly as well as the old one.\n return this.executeWithRetry<T>(method, path, options, attempt + 1);\n }\n\n // All retries exhausted — throw PalbaseError\n throw new PalbaseError(\n 'network_error',\n error instanceof Error ? error.message : 'Network request failed',\n 0,\n );\n }\n\n // Handle 429 Too Many Requests — retry with Retry-After or backoff;\n // if retries exhausted, fall through to normal error response handling below\n if (response.status === 429) {\n if (attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;\n // Clamp the server-requested wait: a long Retry-After (locked account)\n // must not hang the request — cap each sleep, exhaust MAX_RETRIES, then\n // fall through to surface the 429 envelope below.\n const delayMs = Number.isNaN(parsed)\n ? INITIAL_BACKOFF_MS * 2 ** attempt\n : Math.min(parsed * 1000, MAX_RETRY_DELAY_MS);\n await this.delay(delayMs);\n // WITH `earned`, unlike the network path above: a 429 is a refusal the\n // server issued INSTEAD of doing the work, so the challenge was never\n // consumed. The edge's rate limiter answers before palsvc, and on the\n // auth routes palauth's own limiter runs BEFORE the proof-of-work\n // middleware (auth/internal/server/server.go: rl.LoginByIP, then powMW).\n return this.executeWithRetry<T>(method, path, options, attempt + 1, earned);\n }\n }\n\n // Parse response body\n let data: T | null = null;\n let errorBody: { error?: string; error_description?: string; status?: number } | undefined;\n\n // HEAD responses have no body by spec — skip parsing.\n const contentType = response.headers.get('Content-Type');\n if (method !== 'HEAD' && contentType?.includes('json')) {\n const body = (await response.json()) as Record<string, unknown>;\n if (response.ok) {\n data = body as T;\n } else {\n errorBody = body as typeof errorBody;\n }\n }\n\n // Proof-of-work: /auth/signup and /auth/token sit behind a bot gate that\n // answers an unsolved request with 403 and the challenge in the body. Solve\n // it and repeat the request carrying the two headers; the caller never\n // learns the gate is there.\n //\n // HERE, in core, because this is the layer that issues the request for every\n // client in the repo — @palbase/auth's sign-in, @palbase/web's facades, the\n // server SDK. The same retry lived one layer up in @palbase/web until\n // 2026-08-18 and covered everything EXCEPT `pb.auth.*`, which reaches the\n // network through this method; so the gate stayed unsatisfiable on exactly\n // the two endpoints it guards.\n //\n // ONE retry, and only when the body really carries a challenge: `earned`\n // being set already means this IS the second try. A 403 that says\n // pow_required without a challenge is a server the client cannot satisfy,\n // and looping on it would turn a broken gate into a hang.\n if (response.status === 403 && !earned) {\n const challenge = asPowChallenge(errorBody);\n if (challenge) {\n return this.executeWithRetry<T>(\n method,\n path,\n options,\n attempt,\n await solvePowChallenge(challenge, undefined, options?.signal),\n );\n }\n }\n\n if (!response.ok) {\n return {\n data: null,\n error: new PalbaseError(\n errorBody?.error ?? 'unknown_error',\n errorBody?.error_description ?? response.statusText,\n response.status,\n withRetryHint(errorBody, response),\n ),\n status: response.status,\n };\n }\n\n // Parse PostgREST Content-Range for count queries (e.g. \"0-9/42\" or \"*/42\").\n const contentRange = response.headers.get('Content-Range');\n let count: number | undefined;\n if (contentRange) {\n const slash = contentRange.lastIndexOf('/');\n if (slash >= 0) {\n const totalPart = contentRange.slice(slash + 1);\n if (totalPart !== '*') {\n const parsed = Number.parseInt(totalPart, 10);\n if (!Number.isNaN(parsed)) {\n count = parsed;\n }\n }\n }\n }\n\n return {\n data,\n error: null,\n status: response.status,\n ...(count !== undefined ? { count } : {}),\n };\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { AuthStateCallback, Session, Unsubscribe } from './types.js';\n\nexport class TokenManager {\n private session: Session | null = null;\n private listeners: Set<AuthStateCallback> = new Set();\n private refreshPromise: Promise<void> | null = null;\n private refreshing = false;\n\n refreshFunction: ((refreshToken: string) => Promise<Session>) | null = null;\n\n setSession(session: Session): void {\n this.session = session;\n this.notify('SESSION_SET', session);\n }\n\n getAccessToken(): string | null {\n return this.session?.accessToken ?? null;\n }\n\n getRefreshToken(): string | null {\n return this.session?.refreshToken ?? null;\n }\n\n clearSession(): void {\n this.session = null;\n this.notify('SESSION_CLEARED', null);\n }\n\n isExpired(): boolean {\n if (!this.session) return true;\n return Date.now() >= this.session.expiresAt;\n }\n\n async refreshSession(): Promise<void> {\n if (!this.session?.refreshToken || !this.refreshFunction) {\n return;\n }\n\n // Collapse concurrent refresh calls into a single request\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n // Re-entrancy guard: the wired refreshFunction issues its own HTTP request\n // (POST /auth/token/refresh) through HttpClient, whose pre-flight calls\n // refreshSession() again SYNCHRONOUSLY — before `refreshPromise` below is\n // assigned (the whole chain runs before the first real await). Without\n // this flag that recursion is unbounded (stack overflow). Returning early\n // lets the refresh request itself proceed unauthenticated — it carries\n // the refresh token in its body, not the Bearer header.\n if (this.refreshing) {\n return;\n }\n\n this.refreshing = true;\n this.refreshPromise = this.executeRefresh(this.session.refreshToken);\n\n try {\n await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshing = false;\n }\n }\n\n onAuthStateChange(callback: AuthStateCallback): Unsubscribe {\n this.listeners.add(callback);\n return () => {\n this.listeners.delete(callback);\n };\n }\n\n private async executeRefresh(refreshToken: string): Promise<void> {\n if (!this.refreshFunction) return;\n const newSession = await this.refreshFunction(refreshToken);\n this.setSession(newSession);\n }\n\n private notify(event: 'SESSION_SET' | 'SESSION_CLEARED', session: Session | null): void {\n for (const listener of this.listeners) {\n listener(event, session);\n }\n }\n}\n","/** The client a tenant's tests use to call their own backend.\n *\n * These tests run against a REAL deployment — the release the deploy just built,\n * serving from the same Environment as production, with the same database, the\n * same secrets and the same gateway in front of it. So this client is a plain\n * HTTP client, not a simulation: every call crosses the gateway, the API key\n * check, the auth rail, the zod validation at the boundary, and row-level\n * security, exactly as a shipped app's call does.\n *\n * There is deliberately no schema knowledge here. The tenant already wrote their\n * types — `import type { TodoSchema } from \"../models/todos/shared.js\"` — so a\n * test types its own call (`api.get<TodoSchema[]>(\"/todos\")`) and can validate it\n * with the same zod schema the endpoint declares. A second generated client would\n * be a second thing to keep in step.\n */\n\nimport { asPowChallenge, solvePowChallenge } from \"@palbase/core\";\n\n/** How to reach the release under test. Supplied by the deploy, never guessed. */\nexport interface TestApiConfig {\n baseUrl: string;\n apiKey: string;\n /** This deploy's secret. Without it the request is served the LIVE release.\n *\n * OPTIONAL against a stack running on this machine: a local stack serves one\n * version — the directory `palbase start` mounted — so there is no candidate\n * to select. Required everywhere else. */\n candidateToken?: string;\n /** The run's minted logins, keyed by the name declared in config/test-users.ts. */\n identities?: Record<string, TestIdentity>;\n /** The fetch to use. Injected by tests of this client; production passes none. */\n fetch?: typeof fetch;\n}\n\n/** One login the deploy minted for this run.\n *\n * `accessToken` is the session issued when the identity was created, and is what\n * `signInAs` uses. The credentials come along for a test that wants to exercise\n * the login rail itself — but a suite that switches users repeatedly must not be\n * signing in each time: those calls come from one address and trip the login rate\n * limiter, failing tests for a reason that has nothing to do with the code\n * under test.\n */\nexport interface TestIdentity {\n id?: string;\n email: string;\n password: string;\n accessToken?: string;\n}\n\n/** One call the suite made, in the order it was made. */\nexport interface RecordedRequest {\n method: string;\n path: string;\n status: number;\n ms: number;\n}\n\n/** A non-2xx answer, carrying the platform's error envelope.\n *\n * The envelope is the contract every Palbase endpoint answers with, so a test\n * asserts on `status`/`error`/`data` rather than parsing a message. The message\n * exists for the human reading a failed deploy.\n */\nexport class TestApiError extends Error {\n readonly status: number;\n readonly error: string;\n /** Payload of an error your code threw — `new BadRequest({ fields })` arrives here. */\n readonly data: unknown;\n /** The whole envelope, exactly as the server sent it. */\n readonly body: ErrorEnvelope;\n\n constructor(method: string, path: string, status: number, body: unknown) {\n const envelope = (body ?? {}) as ErrorEnvelope;\n const code = envelope.error ?? String(status);\n super(`${method} ${path} → ${status} ${code}${envelope.error_description ? `: ${envelope.error_description}` : \"\"}`);\n this.name = \"TestApiError\";\n this.status = status;\n this.error = code;\n this.data = envelope.data;\n this.body = envelope;\n }\n}\n\n/** A Palbase error response.\n *\n * `data` carries the payload of an error your code threw. Validation refused at\n * the boundary — before your handler runs — answers with `details` instead, one\n * entry per field. The index signature is deliberate: whatever the server sends\n * is readable from a test, so no assertion is ever blocked on this type being\n * exhaustive.\n */\nexport interface ErrorEnvelope {\n error?: string;\n error_description?: string;\n status?: number;\n request_id?: string;\n data?: unknown;\n details?: Array<{ field: string; message: string }>;\n [key: string]: unknown;\n}\n\nexport interface CallOptions {\n headers?: Record<string, string>;\n}\n\nexport interface TestApi {\n get<T = unknown>(path: string, opts?: CallOptions): Promise<T>;\n post<T = unknown>(path: string, body?: unknown, opts?: CallOptions): Promise<T>;\n patch<T = unknown>(path: string, body?: unknown, opts?: CallOptions): Promise<T>;\n put<T = unknown>(path: string, body?: unknown, opts?: CallOptions): Promise<T>;\n delete<T = unknown>(path: string, opts?: CallOptions): Promise<T>;\n /** HTTP QUERY (RFC 10008): a safe, idempotent read whose filter travels in the body. */\n query<T = unknown>(path: string, body?: unknown, opts?: CallOptions): Promise<T>;\n /** Sign in with credentials you supply. */\n signIn(credentials: { email: string; password: string }): Promise<{ id: string; email?: string }>;\n /**\n * Sign in as one of the identities this run was given, by the name you\n * declared it under in `config/test-users.ts`.\n *\n * Each is minted by the platform for the length of ONE deploy, seeded with the\n * data that declaration describes, and retired after — which is why this works\n * on every Environment including production, where a committed fixture password\n * is refused on purpose.\n */\n signInAs(name: string): Promise<{ id: string; email?: string }>;\n signOut(): Promise<void>;\n /** Drop the bearer without calling the server — the anonymous caller. */\n asAnonymous(): void;\n /** Every call made, in order. Printed for the failing test in a red deploy. */\n readonly requests: readonly RecordedRequest[];\n}\n\nfunction required(value: string, envName: string): string {\n if (!value) {\n throw new Error(\n `${envName} is not set — the test client has nowhere to send requests. ` +\n `This is set by the deploy that runs your tests; if you are running them by hand, set it yourself.`,\n );\n }\n return value;\n}\n\n/** A stack running on this machine. There is exactly ONE version there — the\n * directory `palbase start` mounted — so there is no candidate to select, and\n * demanding a token for one made local runs invent a value to satisfy a header\n * nothing reads. */\nfunction isLocalTarget(baseUrl: string): boolean {\n try {\n const { hostname } = new URL(baseUrl);\n return hostname === \"127.0.0.1\" || hostname === \"localhost\" || hostname === \"[::1]\" || hostname === \"::1\";\n } catch {\n return false;\n }\n}\n\n/** Seconds until a JWT's `exp`, or null when the token carries no readable one.\n * Read WITHOUT verifying: this is a diagnosis, never a decision — the server\n * remains the only authority on whether a token is good. */\nfunction secondsUntilExpiry(token: string): number | null {\n const body = token.split(\".\")[1];\n if (!body) return null;\n try {\n const claims = JSON.parse(Buffer.from(body, \"base64url\").toString(\"utf8\")) as { exp?: unknown };\n return typeof claims.exp === \"number\" ? claims.exp - Math.floor(Date.now() / 1000) : null;\n } catch {\n return null;\n }\n}\n\nexport function createTestApi(config: TestApiConfig): TestApi {\n const baseUrl = required(config.baseUrl, \"PALBASE_TEST_BASE_URL\").replace(/\\/$/, \"\");\n const apiKey = required(config.apiKey, \"PALBASE_TEST_API_KEY\");\n const local = isLocalTarget(baseUrl);\n // Local stacks serve one version, so there is nothing to select. Everywhere\n // else the token stays REQUIRED: without it the gateway serves the LIVE\n // release and the suite would grade code that is not under test.\n const candidateToken = local ? (config.candidateToken ?? \"\") : required(config.candidateToken ?? \"\", \"PALBASE_TEST_CANDIDATE\");\n const doFetch = config.fetch ?? fetch;\n\n const requests: RecordedRequest[] = [];\n let bearer: string | null = null;\n\n async function call<T>(method: string, path: string, body: unknown, opts: CallOptions = {}): Promise<T> {\n const headers: Record<string, string> = {\n apikey: apiKey,\n // Selects the release under test. Omit it and the gateway serves the LIVE\n // one, which would make the whole suite grade the wrong code. Absent only\n // against a local stack, which has a single version.\n ...(candidateToken ? { \"x-palbase-candidate\": candidateToken } : {}),\n ...opts.headers,\n };\n if (bearer) headers.authorization = `Bearer ${bearer}`;\n if (body !== undefined) headers[\"content-type\"] = \"application/json\";\n\n const startedAt = Date.now();\n const res = await doFetch(`${baseUrl}${path}`, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n const text = await res.text();\n const parsed: unknown = text ? safeParse(text) : undefined;\n\n requests.push({ method, path, status: res.status, ms: Date.now() - startedAt });\n\n if (!res.ok) {\n // A 401 on a token that has simply RUN OUT is the most likely 401 a suite\n // sees, and the least legible: the mint issues ~30 minutes, so a file of\n // credentials written yesterday answers `401 unauthorized` with nothing to\n // act on. Measured on a customer run: the next step taken was to blame the\n // credentials rather than their age.\n if (res.status === 401 && bearer) {\n const left = secondsUntilExpiry(bearer);\n if (left !== null && left <= 0) {\n throw new TestApiError(method, path, res.status, {\n error: \"access_token_expired\",\n error_description:\n `this run's access token EXPIRED ${Math.abs(left)}s ago — a test identity is minted for the ` +\n `length of ONE deploy, so a saved token does not survive to the next run. Re-mint it ` +\n \"(`palbase test-user create --json`, or let `palbase test` do it) and run again.\",\n });\n }\n }\n throw new TestApiError(method, path, res.status, parsed);\n }\n return parsed as T;\n }\n\n return {\n requests,\n get: (path, opts) => call(\"GET\", path, undefined, opts),\n post: (path, body, opts) => call(\"POST\", path, body, opts),\n patch: (path, body, opts) => call(\"PATCH\", path, body, opts),\n put: (path, body, opts) => call(\"PUT\", path, body, opts),\n delete: (path, opts) => call(\"DELETE\", path, undefined, opts),\n query: (path, body, opts) => call(\"QUERY\", path, body, opts),\n\n async signInAs(name) {\n const identity = (config.identities ?? {})[name];\n if (!identity) {\n const declared = Object.keys(config.identities ?? {});\n throw new Error(\n `no test identity named ${JSON.stringify(name)} — the deploy mints one per user declared in ` +\n `config/test-users.ts` +\n (declared.length\n ? `; this run has: ${declared.join(\", \")}`\n : // Not \"you declared none\": from here the two causes are\n // indistinguishable, and blaming the customer's config for a\n // platform failure sends them to look in the wrong file. The\n // deploy log names which one it was.\n \", and this run has none — either your config declares no users \" +\n \"or the deploy could not mint them; the deploy log says which\"),\n );\n }\n // The session the mint already issued — no network call, so switching\n // users is free and the login rail never sees this run.\n if (identity.accessToken) {\n bearer = identity.accessToken;\n return { id: identity.id ?? \"\", email: identity.email };\n }\n return this.signIn(identity);\n },\n\n async signIn(credentials) {\n // PROOF-OF-WORK IS PART OF LOGGING IN, so a client that cannot solve one\n // cannot log in at all. The web SDK has solved it since bot protection\n // shipped; this harness went straight to `fetch` and therefore answered\n // `403 pow_required` on every password login — which made the whole\n // credentials path DEAD on a stack with the gate on, exactly when a\n // suite falls back to it because its minted token ran out.\n //\n // One retry, and only when the refusal really carries a challenge: a 403\n // saying pow_required without one is a server this client cannot satisfy,\n // and looping would turn a broken gate into a hang. Same rule as\n // @palbase/core's own retry.\n const attempt = async (extra?: Record<string, string>) =>\n call<{ access_token: string; user?: { id: string; email?: string } }>(\n \"POST\",\n \"/auth/login\",\n credentials,\n extra ? { headers: extra } : {},\n );\n\n let result: { access_token: string; user?: { id: string; email?: string } };\n try {\n result = await attempt();\n } catch (e) {\n const refusal = e as { status?: number; body?: unknown };\n const challenge = refusal.status === 403 ? asPowChallenge(refusal.body) : null;\n if (!challenge) throw e;\n result = await attempt(await solvePowChallenge(challenge));\n }\n bearer = result.access_token;\n return result.user ?? { id: \"\" };\n },\n async signOut() {\n await call(\"POST\", \"/auth/logout\", undefined);\n bearer = null;\n },\n asAnonymous() {\n bearer = null;\n },\n };\n}\n\n/** The run's identities, as the deploy passed them. Absent is not an error: a\n * project that declares none still runs every test that needs no login. */\nfunction parseIdentities(raw: string | undefined): Record<string, TestIdentity> {\n if (!raw) return {};\n try {\n return JSON.parse(raw) as Record<string, TestIdentity>;\n } catch {\n return {};\n }\n}\n\nfunction safeParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\n/** The client the deploy configured, from the environment it set.\n *\n * Constructed lazily so importing this module outside a test run — a typecheck,\n * an editor — does not fail on absent configuration.\n */\nlet configured: TestApi | null = null;\n\nexport const api: TestApi = new Proxy({} as TestApi, {\n get(_target, prop) {\n configured ??= createTestApi({\n baseUrl: process.env.PALBASE_TEST_BASE_URL ?? \"\",\n apiKey: process.env.PALBASE_TEST_API_KEY ?? \"\",\n candidateToken: process.env.PALBASE_TEST_CANDIDATE ?? \"\",\n identities: parseIdentities(process.env.PALBASE_TEST_IDENTITIES),\n });\n return Reflect.get(configured, prop, configured);\n },\n});\n","import \"reflect-metadata\";\n\nimport type { Token } from \"../container.js\";\n\nexport interface IsolatedContainer {\n /** Substitutes a token. Chainable; the last write for a token wins. */\n with<T>(t: Token<T>, v: T): IsolatedContainer;\n get<T>(t: Token<T>): T;\n}\n\n/**\n * How a test replaces a dependency.\n *\n * Rebuilds the graph with the overrides in place and never touches the process\n * singleton cache, so the next test in the same process does not meet a doubled\n * instance left behind by this one. Substitution is DEEP: `Report` asks for\n * `Money` and gets whatever the graph was rebuilt with, however many hops down.\n *\n * Substitution is by `with` alone — there is no separate platform map, because\n * platform services are ambient rather than injected (FR-005).\n *\n * Module boundaries are NOT enforced here, deliberately. They are a build-time\n * rule about the shipped application; making a unit test fail on them would\n * force every test to restate a module layout it is not testing. What a test\n * gets is a graph, not a second opinion about the architecture.\n */\nexport function isolated(): IsolatedContainer {\n const over = new Map<Token, unknown>();\n const local = new Map<Token, unknown>();\n\n const make = (c: Token): unknown => {\n if (over.has(c)) return over.get(c);\n const hit = local.get(c);\n if (hit !== undefined) return hit;\n const meta = (Reflect.getMetadata(\"design:paramtypes\", c) as unknown[] | undefined) ?? [];\n const inst = new (c as unknown as new (...a: unknown[]) => unknown)(\n ...meta.map((d) => make(d as Token)),\n );\n local.set(c, inst);\n return inst;\n };\n\n const api: IsolatedContainer = {\n with<T>(t: Token<T>, v: T): IsolatedContainer {\n over.set(t as Token, v);\n return api;\n },\n get<T>(t: Token<T>): T {\n return make(t as Token) as T;\n },\n };\n return api;\n}\n","/**\n * The refusals a Database call gets BEFORE any SQL exists — written once, so the\n * engine and the test double cannot disagree about them.\n *\n * WHY THIS FILE EXISTS. `fakeDatabase()` is a second implementation of the same\n * surface (`__tests__/helpers/mock-db.ts`), and it never touched `compileWhere`\n * or `asBindParams`. Measured against the published 24.1.0: all four of the\n * calls that release had just started refusing went through the fake SILENTLY —\n * `update{title:undefined}`, `insert{title:undefined}`, `findMany{done:{}}`,\n * `deleteMany{owner,created_at:{}}`.\n *\n * The scaffold tells authors to test the service layer against exactly that\n * fake. So a test went green on a call production would throw on, and the\n * author found out in production instead — the same \"the surface does not match\n * the engine\" shape these refusals exist to end, arriving through the door the\n * SDK hands people for testing.\n *\n * These are pure and SQL-free on purpose: an in-memory store can run them as\n * easily as the driver path can.\n */\n\n/**\n * İşaretçilerin MARKASI — `col()` ve `sqlFragment()` ürünlerini bu süreçte\n * üretilmiş olmakla tanımlar.\n *\n * NEDEN ŞEKİL DEĞİL DE MARKA (gözcü W2-A/C1 ve W2-B/C3, ikisi de ÖLÇTÜ):\n * şekil kontrolü, işaretçiyi güvenilmeyen bir istek gövdesinden UYDURULABİLİR\n * kılıyordu. Ölçülen iki sonuç:\n *\n * findMany(\"docs\", { owner_id: JSON.parse('{\"$col\":\"owner_id\"}') })\n * → WHERE true AND t.\"owner_id\" = t.\"owner_id\" ← kiracılık predikatı totoloji\n * findMany(\"todos\", JSON.parse('{\"$sql\":{\"text\":[\"1=1 -- pwned\"],\"values\":[]}}'))\n * → WHERE true AND 1=1 -- pwned ← saldırganın metni SQL'e HARFİYEN\n *\n * `{ where: { tenant_id: tid, ...req.body.filter } }` bu SDK'nın öğrettiği\n * desen; T010/T014 öncesinde aynı anahtarlar \"bilinmeyen operatör\" diye\n * REDDEDİLİYORDU. Marka o reddi geri getiriyor.\n *\n * Sembol GLOBAL kayıttan (`Symbol.for`) ve ENUMERABLE DEĞİL. İkisi de kasıtlı:\n * global kayıt paketin iki kopyası arasında da eşleşir; enumerable olmaması ise\n * `JSON.stringify` ve `{...ref, gt: 5}` yayılımının markayı DÜŞÜRMESİNİ sağlar —\n * yani telden geçen ya da elle karıştırılan hiçbir şey işaretçi sayılmaz.\n * Kardeş özellik (`increment`) zaten `Symbol.for(\"palbase.tx.expr\")` kullanıyor;\n * bu onun aynısı.\n */\nconst REF_BRAND = Symbol.for(\"palbase.db.ref\");\n\n/** İşaretçiyi markalar. Yalnız `col()` ve `sqlFragment()` çağırır. */\nexport function brandRef<T extends object>(v: T, kind: \"col\" | \"sql\" | \"ref\"): T {\n Object.defineProperty(v, REF_BRAND, { value: kind, enumerable: false });\n return v;\n}\n\nfunction brandOf(v: unknown): unknown {\n if (typeof v !== \"object\" || v === null) return undefined;\n // KENDİ özelliği olmalı, prototip zincirinden MİRAS ALINMIŞ değil:\n // `Object.create(col(\"x\"))` markayı zincirden okuyup işaretçi sayılıyordu\n // (gözcü ölçtü). Telden erişilemez — JSON `__proto__` üstünden sembol\n // yazamaz — ama daraltmak bedava ve \"işaretçi bu süreçte ÜRETİLDİ\"\n // iddiasının tam karşılığı budur.\n return Object.hasOwn(v, REF_BRAND) ? (v as Record<symbol, unknown>)[REF_BRAND] : undefined;\n}\n\n/**\n * Bir değer, MARKASIZ bir işaretçi taklidi mi? (`{$col:…}` / `{$sql:…}`)\n *\n * Üst düzeyde bunlar zaten \"bilinmeyen operatör\" diye reddediliyor. Ama\n * operatörün SAĞINDA — `{ amount: { gt: {\"$col\":\"other\"} } }` — sessizce\n * PARAMETRE olarak bağlanıyorlardı: sayısal kolonda sürücünün 22P02'si,\n * jsonb/text kolonunda ise HİÇBİR SATIR, hatasız (gözcü ölçtü).\n *\n * `in` listesindeki aynı kusur adıyla reddediliyor; bu onun bir seviye\n * yanındaki hâli ve aynı cevabı hak ediyor.\n */\nexport function looksLikeUnbrandedRef(v: unknown): \"col\" | \"sql\" | \"expr\" | null {\n if (typeof v !== \"object\" || v === null) return null;\n if (brandOf(v) !== undefined) return null; // gerçek işaretçi\n if (isColumnExpr(v)) return null; // gerçek ifade tutamağı (kendi markası var)\n const o = v as { $col?: unknown; $sql?: unknown; $expr?: unknown };\n if (typeof o.$col === \"string\") return \"col\";\n if (o.$sql !== undefined && typeof o.$sql === \"object\" && o.$sql !== null) return \"sql\";\n // `$expr` `now()`/`increment()`'in TEL BİÇİMİ. `now()` filtrede geçerli bir\n // değer olduğu andan itibaren bu şekil de uydurulabilir hâle geldi: ÖLÇÜLDÜ,\n // gövdeden gelen `{\"$expr\":{\"fn\":\"now\"}}` sessizce PARAMETRE olarak bağlanıp\n // sorguyu hatasız biçimde boş sonuca çeviriyordu. `$col`/`$sql`/`$ref` ile\n // aynı kapı, aynı gerekçe.\n if (o.$expr !== undefined && typeof o.$expr === \"object\" && o.$expr !== null) return \"expr\";\n return null;\n}\n\n/**\n * `col()` ürünü mü? (FR-011)\n *\n * Burada, çünkü bu dosya \"iki uygulamanın da okuduğu kurallar\" dosyası: motor,\n * `fakeDatabase` ve guard AYNI cevabı vermek zorunda.\n */\nexport function isColRef(v: unknown): v is { readonly $col: string } {\n return brandOf(v) === \"col\" && typeof (v as { $col?: unknown }).$col === \"string\";\n}\n\n/**\n * `sqlFragment` ürünü mü? (FR-018)\n *\n * `isColRef` ile aynı gerekçeyle burada: motor, guard ve `fakeDatabase` üçü de\n * aynı cevabı vermek zorunda — biri fragment'i \"kolon haritası\" sanarsa filtre\n * sessizce düşer.\n */\n/**\n * Plan REFERANSI mı? (`{ $ref: { op, field } }`)\n *\n * `$ref` bu dilin MARKASIZ KALAN TEK işaretçisiydi — `engine/db.ts` onu\n * `\"$ref\" in v` diye tanıyordu — ve SDK'nın öğrettiği desen\n * `{ where: { tenant_id: tid, ...req.body.filter } }`. Ölçüldü (gözcü):\n * istek gövdesinden gelen `{\"id\":{\"$ref\":{\"op\":0,\"field\":\"id\"}}}` filtreyi\n * ÖNCEKİ bir işlemin satır değeriyle karşılaştırtıyor —\n * DELETE … WHERE t.\"tenant_id\" = $1 AND t.\"id\" = $2 PRM [\"t1\",\"SIZAN_DEGER\"]\n * gövdenin hiç görmediği bir değer. Enjeksiyon değil (değerler bound) ama bir\n * ORACLE: `op`/`field` seçip `rows_affected`'tan o değeri öğrenmek.\n *\n * Marka `Symbol.for` olduğu için SDK'nın İKİ KOPYASI arasında da eşleşiyor —\n * kontrolcü bundle'ı kendi kopyasını inline ediyor, planı çalıştıran ise\n * runtime'ınki. Ve plan gövdesi JSON'lanmıyor: tek üretim `txPlan` uygulaması\n * süreç içi (`engine/db.ts`), doğrulandı.\n */\nexport function isPlanRef(v: unknown): v is { readonly $ref: { op: number; field: string } } {\n if (brandOf(v) !== \"ref\") return false;\n const r = (v as { $ref?: { op?: unknown; field?: unknown } }).$ref;\n return r !== undefined && typeof r.op === \"number\" && typeof r.field === \"string\";\n}\n\n/** Markasız bir `{ $ref: … }` taklidi mi? Adıyla reddedilmesi için. */\nexport function looksLikeUnbrandedPlanRef(v: unknown): boolean {\n if (typeof v !== \"object\" || v === null || brandOf(v) !== undefined) return false;\n const r = (v as { $ref?: unknown }).$ref;\n return r !== undefined && typeof r === \"object\" && r !== null;\n}\n\nexport function isSqlFragment(v: unknown): v is { readonly $sql: { text: string[]; values: unknown[] } } {\n if (brandOf(v) !== \"sql\") return false;\n const f = (v as { $sql?: { text?: unknown; values?: unknown } }).$sql;\n return f !== undefined && Array.isArray(f.text) && Array.isArray(f.values);\n}\n\n/**\n * İFADE TUTAMAĞI DEĞER DEĞİLDİR — değer bekleyen yollarda adıyla reddedilir.\n *\n * `increment()` / `decrement()` / `now()` bir Proxy döndürür ve yalnız\n * `updateMany` ile plan yolunun `updateWhere`'i onu SQL'e derler. `insert` /\n * `update` / `put` / `supersede` derlemez; oralarda tutamak bound parametre\n * olarak sürücüye gidiyordu ve reddi SÜRÜCÜ veriyordu (\"Unknown object is not\n * a valid PostgreSQL type\") — yazarın yazdığı hiçbir şeyi adlandırmayan bir\n * mesaj (inceleme I-2/I8, ölçüldü). Plan yolu aynı hatayı kendi diliyle\n * reddediyor; bu, doğrudan yolun karşılığı.\n *\n * Sembol `tx-plan.ts`'in markasıyla AYNI global kayıttan okunuyor; bu dosya\n * kural dosyası olduğu için oraya bağımlılık kurmuyor.\n */\nconst TX_EXPR = Symbol.for(\"palbase.tx.expr\");\n\n/**\n * `now()` — SUNUCU SAATİ, karşılaştırma değeri olarak.\n *\n * `increment()`/`decrement()` bir YAZMA ifadesidir ve filtrede anlamsızdır;\n * `now()` öyle değil: `expires_at > now()` sıradan bir karşılaştırma ve her\n * backend'in en sık yazdığı yüklemlerden biri. Filtrede TÜM ifade tutamaklarını\n * reddetmek, yazarı bunun için `sqlFragment`e düşürüyordu — yani sorgunun\n * içine giren parça, en sıradan koşul için gerekiyordu.\n *\n * Ayrım MARKAYLA: bir istek gövdesinden gelen `{\"$expr\":{\"fn\":\"now\"}}` bu\n * süreçte `now()` ile üretilmediği için marka taşımaz ve reddedilir.\n */\nexport function isNowExpr(v: unknown): boolean {\n if (!isColumnExpr(v)) return false;\n try {\n const e = (v as Record<symbol, unknown>)[TX_EXPR];\n return typeof e === \"object\" && e !== null && (e as { fn?: unknown }).fn === \"now\";\n } catch {\n return false;\n }\n}\n\nexport function isColumnExpr(v: unknown): boolean {\n if (typeof v !== \"object\" && typeof v !== \"function\") return false;\n if (v === null) return false;\n try {\n return (v as Record<symbol, unknown>)[TX_EXPR] !== undefined;\n } catch {\n // Tutamak bir Proxy; bilinmeyen bir prop'ta trap fırlatabilir.\n return false;\n }\n}\n\n/**\n * Değer bekleyen bir yazma yolunda ifade tutamağı ya da `col()` var mı?\n *\n * Motor ve `fakeDatabase` AYNI cevabı vermek zorunda: fake tutamağı satıra\n * YAZIYORDU (`row[k] = proxy`) ve satır artık JSON'a bile çevrilemiyordu, motor\n * ise sürücüde patlıyordu. İki farklı yanlış, tek doğru.\n */\nexport function assertNoExpressionHandles(\n caller: string,\n table: string,\n cols: readonly string[],\n data: Record<string, unknown>,\n): void {\n for (const c of cols) {\n const v = data[c];\n // `now()` ile sayaç ifadeleri AYRI cevaplar hak ediyor: ikisinin de\n // çalışan alternatifi var ama farklı (P6 — hata çalışan bir alternatifi\n // ADIYLA söyler). Tek bir \"ifade tutamağı\" mesajı, `now()` yazan kişiye\n // `increment()` öneriyordu.\n if (isNowExpr(v)) {\n throw new Error(\n `${caller}(${table}): \"${c}\" now() aldı — bu yolda değer beklenir. ` +\n `Satır EKLENİRKEN sunucu saatini yazmanın yolu kolonu defaultNow() ile ` +\n `bildirmek (varsayılan kolonun yanında durur, her çağrıda tekrarlanmaz); ` +\n `var olan bir satırı damgalamak için updateMany({ where, set: { ${c}: now() } }) ` +\n `ya da $transaction içinde tx.public.${table}.updateWhere(where, { ${c}: now() }).`,\n );\n }\n if (isColumnExpr(v)) {\n throw new Error(\n `${caller}(${table}): \"${c}\" bir ifade tutamağı aldı (increment()/decrement()). ` +\n `Bu yolda değer beklenir. Sayaç artışı için updateMany({ where, set: { ${c}: increment(n) } }) ` +\n `ya da $transaction içinde tx.public.${table}.updateWhere(where, { ${c}: increment(n) }) kullanın.`,\n );\n }\n if (isColRef(v)) {\n throw new Error(\n `${caller}(${table}): \"${c}\" bir col() aldı. Kolon referansı yalnız FİLTREDE durabilir; ` +\n `bir kolonun değerini başka bir kolona yazmak için $query kullanın.`,\n );\n }\n }\n}\n\n/** The comparison operators a filter value may carry. Kept here because the\n * guard has to tell an operator object from a plain value. */\nconst KNOWN_OPS = new Set([\n \"gt\", \"gte\", \"lt\", \"lte\", \"neq\", \"in\",\n // K1 metin operatörleri (FR-005) ve null testi (FR-006). Bu küme\n // `fakeDatabase()` ile ORTAK kaynaktır: fake bir çağrıyı motorun reddettiği\n // yerde kabul ederse, yazarın testi üretimde patlayan koda karşı yeşil verir.\n \"contains\", \"icontains\", \"startsWith\", \"endsWith\", \"isNull\",\n]);\n\n/**\n * `eq` ADIYLA reddedilir, ve reddi buradadır çünkü guard'ı motor da fake de\n * okuyor.\n *\n * Eşitliğin yazımı ÇIPLAK DEĞERDİR: `{ owner: \"u1\" }`. `eq`'i ikinci bir yazım\n * olarak eklemek, bu run'ın kapatmak için var olduğu şeyi — aynı iş için iki\n * uyumsuz yazım — filtre dilinin İÇİNDE yeniden açardı. Ve eskiden kabul eden\n * ile reddeden ayrışıyordu: guard `eq`'i geçiriyor, derleyici\n * `bilinmeyen operatör \"eq\"` diyordu (gözcü ölçtü).\n */\nconst REFUSED_OPS: Record<string, string> = {\n eq: 'eşitlik ÇIPLAK yazılır: { <kolon>: <değer> } (ya da kolon karşılaştırması için { <kolon>: col(\"…\") })',\n};\n\n/**\n * Refuse a filter that would compile to something other than what it reads like.\n *\n * Three shapes, each measured in production before it was closed:\n *\n * `{ col: undefined }` binds NULL; `= NULL` matches no row, so the query\n * answered \"no records\" and said nothing.\n * `{ col: {} }` produces no term at all — every row on the read\n * path, a dropped condition on the write path.\n * `{ col: { gte: undefined } }` and an `undefined` inside `in`: the same NULL,\n * one level down.\n */\nexport function assertUsableFilter(\n caller: string,\n table: string,\n where: Record<string, unknown> | undefined,\n): void {\n // Bileşim anahtarları (FR-007) bir KOLON adı değildir; kolon doğrulamasından\n // ve operatör kontrolünden muaftır, kendi dalları özyinelemeli olarak aynı\n // kurallardan geçer.\n const COMPOSITES = new Set([\"OR\", \"AND\", \"NOT\"]);\n\n if (!where) return;\n // Fragment bir kolon haritası DEĞİLDİR (FR-018): içeriği SQL'dir, kolon\n // doğrulaması ona uygulanamaz. Değerleri zaten bound gidiyor.\n if (isSqlFragment(where)) return;\n for (const [col, cond] of Object.entries(where)) {\n // Bileşim anahtarları (FR-007) kolon DEĞİLDİR: dalları aynı kurallardan\n // özyinelemeli geçer, ama kendileri operatör kontrolüne girmez.\n if (COMPOSITES.has(col)) {\n const branches = col === \"NOT\" ? [cond] : cond;\n if (!Array.isArray(branches) && col !== \"NOT\") {\n throw new Error(`${caller}(${table}): where.${col} bir dizi olmalı`);\n }\n for (const b of branches as unknown[]) {\n if (b === null || typeof b !== \"object\") {\n throw new Error(`${caller}(${table}): where.${col} dalları filtre nesnesi olmalı`);\n }\n assertUsableFilter(caller, table, b as Record<string, unknown>);\n }\n continue;\n }\n // `has` de kolon DEĞİLDİR: anahtarları İLİŞKİ adları, değerleri BİR TABLO\n // ÖTESİNİN filtresi. İç filtre aynı kurallardan geçiyor — `has` ikinci bir\n // filtre dili değil, aynı dilin bir tablo ötesi.\n //\n // İlişki ADI burada doğrulanMIYOR: grafiği yalnız motor tanıyor (ve tip,\n // derleme anında). Guard'ın onu bilmesi ilişki grafiğinin İKİNCİ bir\n // yorumcusu demekti — `buildRelations`'ın yorumunun adıyla yasakladığı şey.\n if (col === \"has\") {\n if (cond === null || typeof cond !== \"object\" || Array.isArray(cond)) {\n throw new Error(`${caller}(${table}): where.has bir ilişki haritası olmalı ({ <ilişki>: { … } })`);\n }\n for (const [rel, inner] of Object.entries(cond as Record<string, unknown>)) {\n if (inner === null || typeof inner !== \"object\" || Array.isArray(inner)) {\n throw new Error(`${caller}(${table}): where.has.${rel} bir filtre nesnesi olmalı`);\n }\n assertUsableFilter(caller, table, inner as Record<string, unknown>);\n }\n continue;\n }\n if (cond === undefined) {\n throw new Error(\n `${caller}(${table}): where.${col} değeri undefined — bu bir filtre değeri değil. ` +\n `Bağlanınca NULL olur ve '= NULL' hiçbir satıra uymaz, yani sorgu sessizce ` +\n `boş sonuç dönerdi. Değer yoksa anahtarı filtreye hiç koymayın.`,\n );\n }\n if (cond === null || typeof cond !== \"object\" || Array.isArray(cond)) continue;\n // `col()` ürünü bir DEĞER'dir, operatör nesnesi değil (FR-011). Ayırt\n // edilmezse `{ $col: \"x\" }` bir operatör haritası sanılır ve \"bilinmeyen\n // operatör $col\" diye reddedilirdi.\n if (isColRef(cond)) continue;\n // `now()` de bir DEĞER'dir, aynı gerekçeyle — ve tutamak bir Proxy olduğu\n // için `Object.entries` BOŞ döner: ayırt edilmezse \"boş operatör nesnesi\"\n // diye reddedilirdi, yani doğru yazım yanlış bir hatayla karşılanırdı.\n if (isNowExpr(cond)) continue;\n\n const entries = Object.entries(cond as Record<string, unknown>);\n if (entries.length === 0) {\n throw new Error(\n `${caller}(${table}): where.${col} boş bir operatör nesnesi ({}) — hiçbir koşul ` +\n `üretmez, yani bu alan filtreden sessizce DÜŞERDİ. Koşul kurulmayacaksa ` +\n `anahtarı filtreye hiç koymayın (D-21).`,\n );\n }\n for (const [op, v] of entries) {\n if (op === \"in\") {\n if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${col}.in bir dizi olmalı`);\n if (v.some((x) => x === undefined)) {\n throw new Error(\n `${caller}(${table}): where.${col}.in listesinde undefined var — sessizce NULL'a ` +\n `bağlanır ve o eleman hiçbir satırla eşleşmez. Listeyi kurarken eleyin.`,\n );\n }\n continue;\n }\n // Sağ tarafta kolon durabilir: `{ total: { gt: col(\"amount_paid\") } }`.\n // Değer kontrolleri (undefined) ona da uygulanır, ama `in` gibi şekil\n // kontrolleri değil — o dal aşağıda zaten ayrı.\n if (REFUSED_OPS[op] !== undefined) {\n // Bilinmeyen değil — BİLİNEREK reddedilen. Hata çalışan yazımı söylüyor.\n throw new Error(`${caller}(${table}): where.${col}.${op} bu filtre dilinde yok — ${REFUSED_OPS[op]}`);\n }\n if (!KNOWN_OPS.has(op)) {\n throw new Error(\n `${caller}(${table}): where.${col} bilinmeyen operatör \"${op}\" (gt/gte/lt/lte/neq/in/contains/icontains/startsWith/endsWith/isNull)`,\n );\n }\n if (v === undefined) {\n throw new Error(\n `${caller}(${table}): where.${col}.${op} değeri undefined — karşılaştırmanın ` +\n `sağ tarafı NULL olur ve sonuç hiçbir satıra uymaz. Koşulu kurmayın.`,\n );\n }\n }\n }\n}\n\n/**\n * Refuse a write whose value never arrived.\n *\n * `{ title: req.body.title }` with no `title` in the body bound NULL and\n * answered 200 — the column was ERASED. `null` is untouched, and the difference\n * is the whole point: null is an author SAYING \"empty this column\"; undefined is\n * nobody saying anything.\n */\nexport function assertUsableWriteValues(\n caller: string,\n table: string,\n cols: readonly string[],\n data: Record<string, unknown>,\n): void {\n for (const c of cols) {\n if (data[c] === undefined) {\n throw new Error(\n `${caller}(${table}): \"${c}\" değeri undefined — bu bir yazma değeri değil. ` +\n `Kolonu boşaltmak istiyorsan null yaz; kolonu değiştirmek istemiyorsan nesneye hiç koyma ` +\n `(bir eksik istek alanı sessizce NULL yazıyordu — FR-016).`,\n );\n }\n }\n}\n","/**\n * tx-plan.ts — `Database.$transaction()` as a PLAN, not a pinned session.\n *\n * A transaction used to be a conversation: BEGIN, then one network round trip\n * per operation, then COMMIT. Each of those round trips cost ~4 ms and, because\n * the pooler runs in transaction mode, an open transaction pinned a Postgres\n * backend for the whole conversation. A 121-operation statement upload pinned\n * one backend for ~490 ms.\n *\n * So the callback no longer TALKS to the database. It DESCRIBES what should\n * happen; the description is serialised and sent once; the broker runs the whole\n * thing inside one transaction and answers once. Committing on return and\n * rolling back on throw is unchanged — that is the only property tenant code\n * actually asked for.\n *\n * The consequences, stated plainly, because they are the whole design:\n *\n * - The callback is SYNCHRONOUS. There is nothing to await: no statement has\n * run yet when it returns. `async` on the callback and `await` inside it are\n * both compile errors (see {@link TxPlan} and {@link NotAwaitable}).\n * - `insert()` does not hand back a row, it hands back {@link TxRows}. Reading\n * a field requires `.expectOne(err)` first, which makes \"what if the row\n * isn't there\" a question you cannot route around: it is the argument.\n * - A field read from a row is a {@link Ref} — a PROMISE OF A VALUE THE SERVER\n * WILL PRODUCE, not the value. It can be written into a later operation and\n * it can be returned from the callback (it is substituted for the real value\n * before `transaction()` resolves). It cannot be branched on. See the\n * \"Truthiness\" note below — this is the sharp edge of the whole design.\n * - Control flow that needs a real value must move OUT of the callback: read\n * before the transaction, or express the condition as a guard\n * (`updateWhere({ id, accepted_at: null }, …).expectOne(new Conflict(…))`)\n * which the server evaluates and which rolls the whole plan back.\n *\n * # Truthiness — the hole this file CANNOT close\n *\n * JavaScript does not let a Proxy trap truthiness. `if (ref)` takes the true\n * branch, always, for every Ref, and no `get` handler ever runs. `tsc` is silent\n * because a Ref is a perfectly good object. So:\n *\n * const pot = tx.tables.pots.select({ id }, { limit: 1 }).expectOne(e);\n * if (!pot.balance) { … } // ← ALWAYS false. Silently wrong data.\n *\n * What this file does close: coercion (`Symbol.toPrimitive`/`valueOf`/\n * `toString`), awaiting (`then` is a callable member with a non-thenable\n * signature, which is a *compile* error), serialisation (`toJSON`), and nesting\n * a Ref inside a literal value where the server would store it as data. What it\n * cannot close is a bare truthiness test. The real defence is the build-time\n * static analysis (`tx_analysis.js`, phase P4); until that ships, this hole is\n * open and this comment is the only warning.\n *\n * # Wire contract\n *\n * The JSON this file emits is consumed by\n * the plan executor in `engine/db.ts`. That executor rejects\n * unknown fields at every level, so an op carries EXACTLY the fields its kind\n * takes. Everything here that looks like a needless restriction is one of the\n * server's rules made visible early:\n *\n * - `$ref` only points BACKWARDS, and only at an op statically known to yield\n * at most one row (insert, or a `one`/`atMost 1` guard, or `select limit 1`).\n * `.expectOne()` is what this file uses to satisfy that, always.\n * - `$expr` is a closed set: `inc`/`dec` (update only — they read the column's\n * current value) and `now()`.\n * - `update`/`delete` require a `where`; `insert` refuses one.\n * - `insertMany` rows must all set the same columns.\n * - ≤1000 ops, ≤5000 rows per insertMany, ≤8 MiB of JSON.\n *\n * Column keys are emitted SORTED, so the same callback always produces byte-\n * identical JSON. That is what lets the Go decoder be locked to golden files\n * this SDK emits (`testdata/tx_plan_golden/`).\n */\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * A plan handle was used as if it were a value: awaited, coerced to a string or\n * number, serialised, or nested inside another value.\n *\n * Thrown while the callback is still BUILDING the plan, so nothing has been sent\n * and nothing has been written.\n */\n// TİP-ONLY, ve döngü kasıtlı: `typed-db.ts` bu dosyadan tip alıyor, bu dosya\n// ondan `WhereOp` alıyor. Çalışma zamanında hiçbir şey ithal edilmiyor (import\n// type), yani modül döngüsü yok — paylaşılan olan şey TEK FİLTRE DİLİ, ve onu\n// iki yerde ayrı ayrı tanımlamak bu run'ın kapattığı \"iki yazım\"ın tipteki\n// hâli olurdu.\nimport type { WhereOpWith, ColRefOf, HasOnly, SqlFragment } from \"./typed-db.js\";\nimport { isColRef, isSqlFragment, brandRef, isNowExpr } from \"./input-guards.js\";\n\nexport class TxRefError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TxRefError\";\n }\n}\n\n/**\n * The plan the callback described cannot be sent: it breaks a rule the server\n * would reject, and rejecting it here names the line that wrote it instead of\n * returning a 400 about an op index.\n */\nexport class TxPlanError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TxPlanError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Wire types — mirror the plan executor in `engine/db.ts` exactly.\n// ---------------------------------------------------------------------------\n\n/** A backwards reference to an earlier op's single-row result. */\nexport interface TxWireRef {\n $ref: { op: number; field: string };\n}\n\n/** A call from the server's closed function set. */\nexport interface TxWireExpr {\n $expr: { fn: \"inc\" | \"dec\"; by: number | string } | { fn: \"now\" };\n}\n\n/** One value in a `values`/`set`/`where` map: a literal, a `$ref` or a `$expr`. */\nexport type TxWireValue = TxWireRef | TxWireExpr | unknown;\n\n/** The tenant's declared expectation about an op's row count. `slot` indexes the\n * client-side error table — the error OBJECT never travels. */\nexport interface TxWireGuard {\n kind: \"one\" | \"none\" | \"atLeast\" | \"atMost\";\n n: number;\n slot: number;\n}\n\n/** One operation in the wire plan. Fields are omitted, never null: the decoder\n * rejects a field that does not belong to the op's kind. */\nexport interface TxWireOp {\n /** upsert and insertMany: the columns Postgres matches on. */\n onConflict?: readonly string[];\n /** insertMany only: what a collision does. Absent means no ON CONFLICT clause\n * at all, which is what every insertMany did before this option existed. */\n action?: \"ignore\" | \"update\";\n op: \"insert\" | \"insertMany\" | \"upsert\" | \"update\" | \"delete\" | \"select\";\n table: string;\n values?: Record<string, TxWireValue>;\n rows?: Record<string, TxWireValue>[];\n set?: Record<string, TxWireValue>;\n where?: Record<string, TxWireValue>;\n limit?: number;\n lock?: \"update\";\n guard?: TxWireGuard;\n}\n\n/** The plan the engine executes on the request's own transaction. */\nexport interface TxPlanBody {\n ops: TxWireOp[];\n}\n\n/** One op's outcome, positionally matched to the plan's ops. */\nexport interface TxPlanOpResult {\n rows: Record<string, unknown>[];\n rows_affected: number;\n}\n\n/** One result per op, in plan order. */\nexport interface TxPlanResponse {\n results: TxPlanOpResult[];\n}\n\n/**\n * The fields the runtime must copy from the broker's error envelope onto the\n * rejection it throws out of {@link DBClient.txPlan}.\n *\n * `slot` is the whole point: on a guard failure the server answers with the\n * INDEX of the expectation that did not hold, never with an error message of its\n * own, and this SDK maps that index back to the `Error` the callback handed to\n * `.expectOne(…)`. Without `slot` a guard failure degrades to a generic 409.\n */\nexport interface TxPlanRejection {\n status?: number;\n /** `tx_plan_invalid` | `tx_guard_failed` | `tx_ref_unresolved` | a pg class. */\n error_code?: string;\n /** Present only for `tx_guard_failed`: the client-side error table index. */\n slot?: number;\n /** Present on a database error: which op failed. */\n op?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Handle types\n// ---------------------------------------------------------------------------\n\ndeclare const refBrand: unique symbol;\ndeclare const rowBrand: unique symbol;\ndeclare const rowsBrand: unique symbol;\n\n/**\n * Makes a handle a compile error to `await`.\n *\n * `then` is declared as a CALLABLE member whose signature is not `PromiseLike`,\n * which is precisely the shape TypeScript rejects: `await handle` is TS1320 and\n * `async () => handle` is TS1058. A non-callable `then` would not do it — the\n * compiler simply ignores those.\n */\nexport interface NotAwaitable {\n /** Not a promise. Nothing here has run yet; there is nothing to await. */\n then(doNotAwaitAPlanHandle: \"a transaction plan is built synchronously\"): never;\n}\n\n/**\n * A value the SERVER will produce, standing in for a column of a row this plan\n * writes or reads.\n *\n * Legal uses: write it into a later operation's `values`/`set`/`where`, or\n * return it from the callback (it is replaced by the real value before\n * `transaction()` resolves).\n *\n * Illegal, and caught: `await`, `String(ref)`, `` `${ref}` ``, `ref + 1`,\n * `JSON.stringify(ref)`, burying it inside a jsonb object.\n *\n * Illegal, and NOT caught: `if (ref)`. See the truthiness note at the top.\n */\nexport interface Ref<T> extends NotAwaitable {\n readonly [refBrand]: T;\n}\n\n/** The brand carried by a single-row handle, and the seam `Materialized` reads\n * to turn `return st` into the whole row. */\nexport interface TxRowHandle<Row> extends NotAwaitable {\n readonly [rowBrand]: Row;\n}\n\n/**\n * A row this plan is known to produce exactly one of. Every property is a\n * {@link Ref}; returning the handle itself yields the whole row.\n *\n * Only `.expectOne(err)` produces one — which is the design: a row you can read\n * fields from is a row whose absence you have already answered for.\n */\nexport type TxRow<Row> = { readonly [K in keyof Row]: Ref<Row[K]> } & TxRowHandle<Row>;\n\n/**\n * The result of one operation, before any expectation is declared about it.\n *\n * Deliberately not a row and not a list: an operation's row count is not known\n * until the server runs it, so the only thing that can be said about it here is\n * an EXPECTATION. Declaring one is also the only way to get a readable row.\n *\n * At most one expectation per operation — the wire carries one guard per op, and\n * a second call throws rather than silently dropping the first.\n */\nexport interface TxRows<Row> extends NotAwaitable {\n readonly [rowsBrand]: Row;\n /**\n * Require exactly one row, and read it. On any other count the server rolls\n * the whole transaction back and this `error` is thrown to the caller.\n *\n * This is the only way to reach a row's fields, and the only shape a `$ref`\n * may point at.\n */\n expectOne(error: Error): TxRow<Row>;\n /** Require zero rows (e.g. \"this membership must not already exist\"). */\n expectNone(error: Error): void;\n /** Require at least `n` rows. */\n expectAtLeast(n: number, error: Error): void;\n /** Require at most `n` rows. */\n expectAtMost(n: number, error: Error): void;\n}\n\n/** `now()` — the server's clock, usable wherever a value is. */\nexport interface TxNow extends NotAwaitable {\n readonly $expr: { fn: \"now\" };\n}\n\n/** `inc(n)` / `dec(n)` — read the column's CURRENT value and write it back\n * changed. Only meaningful in an update's `set`, which is where the types allow\n * it and where the server allows it. */\nexport interface TxColumnExpr extends NotAwaitable {\n readonly $expr: { fn: \"inc\" | \"dec\"; by: number | string };\n}\n\n/**\n * Resolve a callback's return type against what actually comes back: every\n * {@link Ref} becomes its value, every {@link TxRow} becomes its row, and\n * anything else keeps its shape.\n *\n * A {@link TxRows} resolves to an explanatory string type rather than a row\n * list: it has no single answer to give, and saying so in the type is louder\n * than a runtime throw.\n */\nexport type Materialized<T> = T extends Ref<infer U>\n ? U\n : T extends TxRowHandle<infer R>\n ? R\n : T extends TxRows<unknown>\n ? \"a TxRows cannot leave the transaction callback — read a row with .expectOne(err) first\"\n : T extends Date\n ? T\n : T extends object\n ? { [K in keyof T]: Materialized<T[K]> }\n : T;\n\n// ---------------------------------------------------------------------------\n// Author-facing value types\n// ---------------------------------------------------------------------------\n\n/** A value written by an INSERT: a literal, an earlier row's field, or `now()`.\n * `inc`/`dec` are absent on purpose — they read a current value, and an inserted\n * row has none. */\nexport type TxInsertValue<V> = V | Ref<V> | TxNow;\n\n/** A value written by an UPDATE's `set`: everything an insert takes, plus the\n * read-modify-write expressions. */\n/**\n * TEK KURAL: ifade tutamağı yalnız sayısal-benzeri kolonlarda.\n *\n * Bu tip KOŞULSUZDU ve doğrudan yolun `SetValue<V>`'si koşulluydu, yani aynı\n * nesne için İKİ tip kuralı vardı: `tx.tables.todos.updateWhere({id}, { done:\n * increment(1) })` (boolean kolon!) DERLENİYOR, `updateMany`'nin aynısı derleme\n * hatası veriyordu. Bu run'ın kapatmak için var olduğu şey \"aynı iş için iki\n * uyumsuz yazım\"dı; tip kuralı ikinci yazımın kendisi olmuştu (gözcü I6/I-1).\n */\nexport type TxSetValue<V> =\n | V\n | Ref<V>\n | TxNow\n | (NonNullable<V> extends number | string ? TxColumnExpr : never);\n\n/** An insert payload: the table's insert shape, with refs and `now()` allowed. */\nexport type TxInsertShape<Insert> = { [K in keyof Insert]: TxInsertValue<Insert[K]> };\n\n/** An update's `set`: any subset of the insert shape, with expressions allowed. */\nexport type TxSetShape<Insert> = { [K in keyof Insert]?: TxSetValue<Insert[K]> };\n\n/**\n * A filter. Every entry is an equality test and they are AND-ed; a `null`\n * becomes `IS NULL`, which is what makes `{ accepted_at: null }` a usable\n * \"not yet accepted\" guard rather than a clause that matches nothing.\n */\n/**\n * Plan filtresinin tipi — `WhereFilter<Row>` ile AYNI sözlük, artı `Ref`.\n *\n * Eskiden yalnız eşitlikti (`Row[K] | Ref<Row[K]>`), ve iki şeye mal oluyordu:\n * FR-014'ün amiral deseni (`{ balance: { gte: amount } }`) `$transaction`\n * İÇİNDE yazılamıyordu — koşullu bir yazmayı plana koyamayan yazar `$query`'ye\n * düşüyordu — ve motor tarafında tip atlandığında aynı nesne SESSİZCE parametre\n * olarak bağlanıyordu.\n *\n * `Ref` fazladan üye ve öyle kalmalı: bir plan filtresi ÖNCEKİ bir işlemin\n * döndürdüğü değere bakabilir, `findMany` bakamaz — plan dışında böyle bir\n * \"önceki işlem\" yok.\n */\ntype TxWhereField<Row, K extends keyof Row> = WhereOpWith<\n Row[K],\n // `Ref` KOLON REFERANSININ YANINDA duruyor, `V`'nin içinde DEĞİL: `V`'ye\n // eklenseydi `TextOps<V>`'nin `V extends string` sorusu HAYIR olur ve\n // `contains`/`startsWith` sessizce kaybolurdu (ölçüldü).\n ColRefOf<Row, Row[K]> | Ref<Row[K]>\n>;\n\nexport type TxWhere<Row, Rels = unknown> = {\n [K in keyof Row]?: TxWhereField<Row, K>;\n} & {\n // Düz op'lardaki `WhereFilter` ile AYNI: dal bir `sqlFragment` de olabilir.\n // İki filtre dilinin bir dalda ayrışması, \"tek dil\" iddiasını tam da bileşim\n // anında boşa çıkarırdı — ve motor plan yolunda da fragment'i derliyor\n // (ölçüldü: `SELECT t.* FROM \"crew\" t WHERE true AND (((a > 1)) AND (…))`).\n OR?: (TxWhere<Row, Rels> | SqlFragment)[];\n AND?: (TxWhere<Row, Rels> | SqlFragment)[];\n NOT?: TxWhere<Row, Rels> | SqlFragment;\n} & HasOnly<Rels>;\n\n/** Options for a plan `select`. */\nexport interface TxSelectOptions {\n /** Cap the rows read. */\n limit?: number;\n /** Take a real `FOR UPDATE` row lock for the rest of the transaction. */\n lock?: \"update\";\n}\n\n/** One table, as the plan sees it. */\nexport interface TxTable<Row, Insert, Rels = unknown> {\n /** Insert one row. Returns a handle — call `.expectOne(err)` to read fields. */\n insert(values: TxInsertShape<Insert>): TxRows<Row>;\n /**\n * Insert many rows in ONE statement. Every row must set the same columns\n * (a row that omits one would silently take the column's default).\n *\n * An empty list writes nothing and sends nothing.\n */\n /**\n * Insert many rows in ONE statement, optionally choosing what a collision does.\n *\n * Without `opts` this is a plain multi-row INSERT and a collision aborts the\n * transaction — the behaviour every call had before the option existed.\n *\n * `action: \"ignore\"` emits `ON CONFLICT DO NOTHING`, which is how \"insert the\n * ones that are new\" becomes one round-trip instead of one per row with a\n * 23505 caught around each. **The returned rows are the ones actually\n * INSERTED**: a row that collided is skipped, so it is absent from the result\n * — Postgres does not return what it did not write.\n *\n * `action: \"update\"` emits `ON CONFLICT DO UPDATE`, setting every non-conflict\n * column from the incoming row, and every row comes back.\n */\n insertMany(\n rows: readonly TxInsertShape<Insert>[],\n opts?: {\n onConflict: readonly Extract<keyof Row, string>[];\n action?: \"ignore\" | \"update\";\n },\n ): TxRows<Row>;\n /**\n * Satırı yaz, `onConflict` kolonlarında çakışırsa üzerine yaz — planın\n * savepoint'i içinde, `Database.<şema>.<tablo>.put()` ile AYNI anlamda.\n *\n * Adı bilerek aynı: aynı iş için transaction içinde ve dışında iki farklı\n * yazım, bu run'ın kapatmak için var olduğu şeydir (P1). TEL şekli\n * (`op: \"upsert\"`) değişmedi — o iç sözleşme, yazarın gördüğü ad değil.\n *\n * Bir operasyon olmasının sebebi: alternatifi burada yazılamaz — başarısız\n * bir insert tüm transaction'ı abort eder, yani \"dene, sonra geri düş\" iki\n * plan adımı olamaz.\n */\n put(\n values: TxInsertShape<Insert>,\n options: { onConflict: readonly Extract<keyof Row, string>[] },\n ): TxRows<Row>;\n /**\n * Update every row matching `where`. The filter comes first because it is the\n * dangerous half: an update whose `where` you got wrong rewrites rows you\n * never looked at. The server refuses an update with no `where` at all.\n */\n updateWhere(where: TxWhere<Row, Rels>, set: TxSetShape<Insert>): TxRows<Row>;\n /** Delete every row matching `where`. The server refuses an unfiltered delete. */\n deleteWhere(where: TxWhere<Row, Rels>): TxRows<Row>;\n /** Read rows, optionally locking them for the rest of the transaction. */\n select(where?: TxWhere<Row, Rels>, options?: TxSelectOptions): TxRows<Row>;\n}\n\n/**\n * The handle a transaction callback receives.\n *\n * It carries tables and nothing else: no `query`, no `findById`, no `asService`.\n * A read whose value the plan does not write belongs OUTSIDE the transaction,\n * where it costs one round trip and can be branched on like an ordinary value.\n */\nexport interface TxPlanHandle<TTables> {\n /**\n * @deprecated `tx.public` kullanın. Bu ad public'in takma adı olarak DURUYOR\n * (göç notu onu öğretiyor ve her mevcut çağrı onu kullanıyor), ama ARTIK\n * ÖĞRETİLMİYOR: doğrudan yüzeyde `Database.tables` FR-001 ile kaldırıldı, ve\n * plan yüzeyinin onu öğretmeye devam etmesi yazarı bir yüzeyde çalışıp\n * diğerinde derlenmeyen bir yazıma alıştırıyordu (gözcü M-6).\n */\n tables: TTables;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — expressions\n// ---------------------------------------------------------------------------\n\n/** Internal marker read by the serialiser. */\nconst EXPR = Symbol.for(\"palbase.tx.expr\");\nconst REF = Symbol.for(\"palbase.tx.ref\");\nconst ROW = Symbol.for(\"palbase.tx.row\");\nconst ROWS = Symbol.for(\"palbase.tx.rows\");\n\ninterface RefDescriptor {\n op: number;\n field: string;\n}\n\n/** Property reads that must not silently produce a value on a plan handle.\n * `then` is the one that matters for correctness (it makes `await` a compile\n * error AND stops a handle from being adopted by a promise); the rest turn a\n * silent wrong answer — `\"[object Object]\"`, `NaN`, `{}` — into a thrown one. */\nconst TRAPPED_PROPS: readonly (string | symbol)[] = [\n \"then\",\n \"valueOf\",\n \"toString\",\n \"toJSON\",\n Symbol.toPrimitive,\n];\n\nfunction trap(prop: string | symbol, what: string, hint: string): never {\n const name = typeof prop === \"symbol\" ? prop.description ?? String(prop) : prop;\n throw new TxRefError(\n `${what} was used as a value (via \\`${name}\\`). Nothing in a transaction ` +\n `callback has run yet, so there is no value to read. ${hint}`,\n );\n}\n\n/** The server's `now()`. */\nexport function now(): TxNow {\n return makeExpr({ fn: \"now\" }) as TxNow;\n}\n\n/** Add `by` to the column's current value. Only valid in an update's `set`. */\nexport function increment(by: number | string): TxColumnExpr {\n assertAmount(by, \"increment\");\n return makeExpr({ fn: \"inc\", by }) as TxColumnExpr;\n}\n\n/**\n * `increment`'in eski adı. AYNI fabrikadır — iki uygulama değil, iki ad.\n *\n * @deprecated `increment()` kullanın; bu ad geriye dönük uyumluluk için duruyor.\n */\nexport const inc = increment;\n\n/** Subtract `by` from the column's current value. Only valid in an update's `set`. */\nexport function decrement(by: number | string): TxColumnExpr {\n assertAmount(by, \"decrement\");\n return makeExpr({ fn: \"dec\", by }) as TxColumnExpr;\n}\n\n/**\n * `decrement`'in eski adı. AYNI fabrikadır.\n *\n * @deprecated `decrement()` kullanın.\n */\nexport const dec = decrement;\n\n/**\n * Miktarın taşınabilir olduğunu doğrular.\n *\n * String kabul edilir ve KASITLIDIR (D-007): `numeric` bir kolonda miktar JS\n * `number`'a uğrarsa 0.1 + 0.2 orada 0.30000000000000004'tür ve para hesabı\n * sessizce kayar. String hem burada hem `renderValue`'da bound parametre olarak\n * taşınır — Postgres onu tam ondalık olarak okur.\n */\nfunction assertAmount(by: number | string, fn: string): void {\n if (typeof by === \"string\") {\n // Metin SQL'e girmiyor (bound parametre), ama şekli yine de doğrulanır:\n // \"abc\" bind edilirse hata Postgres'ten gelir, çağıranın diliyle değil.\n if (!/^-?\\d+(\\.\\d+)?$/.test(by)) {\n throw new TxPlanError(\n `${fn}() ondalık bir sayı metni bekliyor, \"${by}\" aldı — kabul edilen biçim: \"12\", \"-12\", \"12.50\"`,\n );\n }\n } else if (typeof by !== \"number\" || !Number.isFinite(by)) {\n // JSON has no NaN/Infinity: they would serialise to `null` and the server\n // would reject the plan with an unhelpful decode error.\n throw new TxPlanError(`${fn}() needs a finite number, got ${String(by)}`);\n }\n // NEGATİF MİKTAR REDDEDİLİR — ve bu şekil kontrolünden çok daha fazlası.\n // `decrement(\"-5\")` `SET c = c - $1` derliyordu, `$1 = -5`, yani beş EKLİYORDU.\n // FR-014'ün amiral deseninde (`where: { balance: { gte: amount } }`) miktar\n // istek gövdesinden geliyorsa `balance >= -5` her zaman doğru: hesap\n // KREDİLENDİRİLİR ve çağrı bunu 1 satırla \"başarı\" diye raporlar. Guard\n // okunduğunda işaret kontrol edilmiş gibi duruyordu (gözcü I9, ölçüldü).\n const negative = typeof by === \"string\" ? by.trimStart().startsWith(\"-\") : by < 0;\n if (negative) {\n const other = fn === \"increment\" ? \"decrement\" : \"increment\";\n throw new TxPlanError(\n `${fn}() negatif miktar almaz (\"${String(by)}\"). Ters yön için ${other}() kullanın — ` +\n `işaretin miktarda saklanması, yönü okuyan hiçbir kod tarafından görülmezdi.`,\n );\n }\n}\n\n/**\n * Bir değer `increment()`/`decrement()` ürünü mü? Öyleyse tel şekli.\n *\n * DOĞRUDAN yol (`updateMany`) da bu ifadeyi anlamak zorunda: aynı nesnenin iki\n * yerde çalışması, \"kolona ekle\"nin tek yazımı olmasının şartı (P1).\n */\nexport function columnExprOf(v: unknown): TxWireExpr[\"$expr\"] | null {\n return exprOf(v);\n}\n\nfunction makeExpr(expr: TxWireExpr[\"$expr\"]): unknown {\n return new Proxy(\n { [EXPR]: expr } as Record<string | symbol, unknown>,\n {\n get(target, prop) {\n if (prop === EXPR) return target[EXPR];\n if (TRAPPED_PROPS.includes(prop)) {\n trap(prop, \"A plan expression\", \"Write it into an operation instead.\");\n }\n return undefined;\n },\n },\n );\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — refs and row handles\n// ---------------------------------------------------------------------------\n\nfunction makeRef(op: number, field: string): unknown {\n const target: Record<string | symbol, unknown> = { [REF]: { op, field } satisfies RefDescriptor };\n return new Proxy(target, {\n get(t, prop) {\n if (prop === REF) return t[REF];\n if (TRAPPED_PROPS.includes(prop)) {\n trap(\n prop,\n `\\`${field}\\` of a row this transaction has not written yet`,\n \"Pass it to another operation in the same plan, or return it from the \" +\n \"callback and read it after `transaction()` resolves.\",\n );\n }\n return undefined;\n },\n });\n}\n\nfunction makeRowHandle(op: number): unknown {\n const target: Record<string | symbol, unknown> = { [ROW]: op };\n return new Proxy(target, {\n get(t, prop) {\n if (prop === ROW) return t[ROW];\n if (TRAPPED_PROPS.includes(prop)) {\n trap(\n prop,\n \"A row this transaction has not written yet\",\n \"Read one of its columns to reference it, or return the row from the \" +\n \"callback and read it after `transaction()` resolves.\",\n );\n }\n if (typeof prop === \"symbol\") return undefined;\n return makeRef(op, prop);\n },\n });\n}\n\nfunction refDescriptor(v: unknown): RefDescriptor | null {\n if (typeof v !== \"object\" || v === null) return null;\n const d = (v as Record<symbol, unknown>)[REF];\n return isRefDescriptor(d) ? d : null;\n}\n\nfunction isRefDescriptor(d: unknown): d is RefDescriptor {\n return (\n typeof d === \"object\" &&\n d !== null &&\n typeof (d as RefDescriptor).op === \"number\" &&\n typeof (d as RefDescriptor).field === \"string\"\n );\n}\n\nfunction rowOpIndex(v: unknown): number | null {\n if (typeof v !== \"object\" || v === null) return null;\n const op = (v as Record<symbol, unknown>)[ROW];\n return typeof op === \"number\" ? op : null;\n}\n\nfunction exprOf(v: unknown): TxWireExpr[\"$expr\"] | null {\n if (typeof v !== \"object\" || v === null) return null;\n const e = (v as Record<symbol, unknown>)[EXPR];\n return typeof e === \"object\" && e !== null ? (e as TxWireExpr[\"$expr\"]) : null;\n}\n\nfunction isRowsHandle(v: unknown): boolean {\n return typeof v === \"object\" && v !== null && (v as Record<symbol, unknown>)[ROWS] !== undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — value encoding\n// ---------------------------------------------------------------------------\n\n/**\n * Encode one value of a `values`/`set`/`where` map.\n *\n * The nesting check is not defensive tidiness. The server treats `$ref` as a\n * tagged value only at the TOP of a column's value; a ref buried inside a jsonb\n * payload is just data, and would be stored as the literal object\n * `{\"$ref\":{...}}` — a write that succeeds, commits, and is wrong. So a nested\n * handle is a hard error here, where the line that wrote it is still on the\n * stack.\n */\nfunction encodeValue(value: unknown, column: string, allowColumnExpr: boolean): TxWireValue {\n const ref = refDescriptor(value);\n if (ref) return brandRef({ $ref: { op: ref.op, field: ref.field } }, \"ref\") satisfies TxWireRef;\n\n // `now()` bir KARŞILAŞTIRMA değeri olarak geçer — sunucu saati. Diğer\n // ifadeler (increment/decrement) yazma ifadesidir ve aşağıda adıyla\n // reddediliyor. Düz op yolu ile aynı ayrım, aynı gerekçe.\n if (isNowExpr(value)) return { $expr: { fn: \"now\" } };\n const expr = exprOf(value);\n if (expr) {\n if (expr.fn !== \"now\" && !allowColumnExpr) {\n throw new TxPlanError(\n `\\`${column}\\`: ${expr.fn}() reads the column's current value, so it is ` +\n `only valid in updateWhere(where, set).`,\n );\n }\n return { $expr: expr } satisfies TxWireExpr;\n }\n\n if (rowOpIndex(value) !== null) {\n throw new TxPlanError(\n `\\`${column}\\`: a row handle is not a value. Read the column you meant ` +\n `(e.g. \\`row.id\\`).`,\n );\n }\n if (isRowsHandle(value)) {\n throw new TxPlanError(\n `\\`${column}\\`: an operation result is not a value. Declare an expectation ` +\n `first (\\`.expectOne(err)\\`) and read a column from the row.`,\n );\n }\n\n assertNoNestedHandles(value, column);\n return value;\n}\n\n/**\n * Encode a FİLTRE — `values`/`set` ile AYNI kodlayıcı değil, ve olmaması bir\n * düzeltme.\n *\n * `encodeValue` bir `$ref`'i yalnız kolonun EN ÜSTÜNDE kabul ediyor, çünkü bir\n * insert değerinin İÇİNE gömülü ref sunucuda çözülmez, literal JSON olarak\n * SAKLANIR — \"başarıyla commit olan ve yanlış olan bir yazma\". O kural DEĞER\n * yolu için doğru.\n *\n * FİLTREDE öyle değil: motorun `resolveRefsDeep`'i bir ref'i filtrenin HER\n * yerinde çözüyor — operatörün sağında, `OR`/`AND`/`NOT` dallarının içinde. Ama\n * kodlayıcı hâlâ değer kuralını uyguluyordu, yani üç katman üç farklı cevap\n * veriyordu (gözcü C-2): tip kabul, motor çözüyor, kodlayıcı REDDEDİYOR — ve\n * reddin metni değer-yuvalama vakasını anlatıyor, filtrede olmayan bir şeyi.\n *\n * İFADE TUTAMAĞI ve SATIR TUTAMAĞI filtrede HÂLÂ reddediliyor: onları motor\n * filtrede çözmüyor ve çözmemeli — `increment()` bir yazma ifadesi, bir\n * karşılaştırma değil.\n */\nfunction encodeFilterValue(value: unknown, column: string): unknown {\n const ref = refDescriptor(value);\n if (ref) return brandRef({ $ref: { op: ref.op, field: ref.field } }, \"ref\") satisfies TxWireRef;\n\n const expr = exprOf(value);\n if (expr) {\n throw new TxPlanError(\n `\\`${column}\\`: ${expr.fn}() bir YAZMA ifadesi, karşılaştırma değil — ` +\n `filtrede kullanılamaz. Kolonu bir değerle ya da col() ile karşılaştırın.`,\n );\n }\n if (rowOpIndex(value) !== null) {\n throw new TxPlanError(\n `\\`${column}\\`: a row handle is not a value. Read the column you meant (e.g. \\`row.id\\`).`,\n );\n }\n if (isRowsHandle(value)) {\n throw new TxPlanError(\n `\\`${column}\\`: an operation result is not a value. Declare an expectation ` +\n `first (\\`.expectOne(err)\\`) and read a column from the row.`,\n );\n }\n if (Array.isArray(value)) return value.map((v) => encodeFilterValue(v, column));\n // `col()` ve `sqlFragment` OLDUĞU GİBİ geçer: markaları süreç içinde korunur\n // ve derleyici ikisini de kendi tanıyor.\n if (value !== null && typeof value === \"object\" && !(value instanceof Date) && !isColRef(value) && !isSqlFragment(value)) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[k] = encodeFilterValue(v, column);\n }\n return out;\n }\n return value;\n}\n\n/** Filtre haritası — anahtarlar SIRALI (aynı geri çağrı bayt-özdeş JSON üretsin). */\nfunction encodeFilterMap(map: Record<string, unknown>): Record<string, TxWireValue> {\n const out: Record<string, TxWireValue> = {};\n for (const key of Object.keys(map).sort()) {\n const value = map[key];\n if (value === undefined) continue;\n out[key] = encodeFilterValue(value, key) as TxWireValue;\n }\n return out;\n}\n\nfunction assertNoNestedHandles(value: unknown, column: string): void {\n if (typeof value !== \"object\" || value === null) return;\n if (value instanceof Date) return;\n if (refDescriptor(value) || exprOf(value) || rowOpIndex(value) !== null || isRowsHandle(value)) {\n throw new TxPlanError(\n `\\`${column}\\`: a plan handle is nested inside a value. The server would ` +\n `store it as literal JSON, not resolve it. Put the reference directly in ` +\n `the column.`,\n );\n }\n if (Array.isArray(value)) {\n for (const item of value) assertNoNestedHandles(item, column);\n return;\n }\n for (const item of Object.values(value as Record<string, unknown>)) {\n assertNoNestedHandles(item, column);\n }\n}\n\n/**\n * Encode a column map, dropping `undefined` and emitting keys SORTED.\n *\n * Sorting is what makes the same callback produce byte-identical JSON every\n * time, which is what lets the Go decoder be locked to goldens this SDK emits.\n * Dropping `undefined` mirrors what `JSON.stringify` would do anyway, but does\n * it where the resulting column list is still checkable.\n */\nfunction encodeMap(\n map: Record<string, unknown>,\n allowColumnExpr: boolean,\n): Record<string, TxWireValue> {\n const out: Record<string, TxWireValue> = {};\n for (const key of Object.keys(map).sort()) {\n const value = map[key];\n // AÇIK `undefined` REDDEDİLİR (D-017 kapandı).\n //\n // \"Kolon varsayılanını al\" demenin yolu anahtarı HİÇ KOYMAMAK; bu döngü\n // zaten yalnız var olan anahtarları geziyor, yani o niyet bozulmadan\n // çalışıyor. Ayırt edilen şey başka: anahtarın DURDUĞU ama değerinin\n // `undefined` olduğu hâl — yani `{ title: req.body.title }` gövdede\n // `title` yokken. O ölçülmüş bir olaydı: kolon sessizce yazılmadı ve\n // istek 200 döndü.\n //\n // Doğrudan yol bunu baştan beri adıyla reddediyordu; plan yolu sessizce\n // düşürüyordu. Aynı girdiye zıt iki cevap, \"tek filtre dili, tek cevap\"\n // iddiasını yazma tarafında boşa çıkarıyordu.\n if (value === undefined) {\n throw new TxPlanError(\n `${key} değeri undefined — bu bir yazma değeri değil. Anahtar duruyor ` +\n `ama değeri yok, yani kolon sessizce YAZILMAZDI (istek gövdesinden ` +\n `gelen bir alanın eksik olması bu şekilde görünür). Kolon ` +\n `varsayılanını istiyorsanız anahtarı hiç koymayın; NULL yazmak ` +\n `istiyorsanız açıkça null yazın.`,\n );\n }\n out[key] = encodeValue(value, key, allowColumnExpr);\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — the builder\n// ---------------------------------------------------------------------------\n\n/** How many rows an op can produce, as far as the CLIENT can tell before it\n * runs. Only the empty `insertMany` is knowable, and knowing it is what lets a\n * doomed expectation fail on the line that declared it. */\nconst SKIPPED_OP = -1;\n\nclass TxRowsImpl<Row> implements TxRows<Row> {\n // Present so `isRowsHandle` recognises the object; never read for its value.\n readonly [ROWS] = true;\n // Phantom: the type brand that stops a bare object passing as a TxRows. Never\n // present at run time, which is what `declare` says.\n declare readonly [rowsBrand]: Row;\n private guarded = false;\n\n constructor(\n private readonly builder: TxPlanBuilder,\n private readonly opIndex: number,\n private readonly what: string,\n ) {}\n\n // The type-level `await` guard made real: TS rejects `await rows` at compile\n // time, and reaching this means someone called `.then(...)` by hand.\n then(): never {\n throw new TxRefError(\n `${this.what} cannot be awaited: a transaction callback builds a plan, it ` +\n `does not run statements. Remove the \\`await\\`.`,\n );\n }\n\n expectOne(error: Error): TxRow<Row> {\n this.declareGuard(\"one\", 1, error);\n if (this.opIndex === SKIPPED_OP) throw error;\n return makeRowHandle(this.opIndex) as TxRow<Row>;\n }\n\n expectNone(error: Error): void {\n this.declareGuard(\"none\", 0, error);\n }\n\n expectAtLeast(n: number, error: Error): void {\n assertGuardCount(n, \"expectAtLeast\");\n this.declareGuard(\"atLeast\", n, error);\n if (this.opIndex === SKIPPED_OP && n > 0) throw error;\n }\n\n expectAtMost(n: number, error: Error): void {\n assertGuardCount(n, \"expectAtMost\");\n this.declareGuard(\"atMost\", n, error);\n }\n\n private declareGuard(kind: TxWireGuard[\"kind\"], n: number, error: Error): void {\n if (!(error instanceof Error)) {\n // The error object is the ONLY thing that describes this failure to the\n // caller — the server sends back an index, never a message.\n throw new TxPlanError(\n `${this.what}: an expectation needs the Error to throw when it does not ` +\n `hold (e.g. \\`.expect…(new Conflict(\"already accepted\"))\\`).`,\n );\n }\n if (this.guarded) {\n throw new TxPlanError(\n `${this.what} already has an expectation. One operation carries one ` +\n `expectation; declare the second one on its own operation.`,\n );\n }\n this.guarded = true;\n if (this.opIndex === SKIPPED_OP) return;\n this.builder.attachGuard(this.opIndex, kind, n, error);\n }\n}\n\nfunction assertGuardCount(n: number, fn: string): void {\n if (!Number.isInteger(n) || n < 0) {\n throw new TxPlanError(`${fn}(n) needs a non-negative integer, got ${String(n)}`);\n }\n}\n\n/** Bounds mirrored from the plan executor so an over-sized plan is named here rather\n * than rejected as an opaque 400 after it has crossed the network. */\nconst MAX_OPS = 1000;\nconst MAX_ROWS = 5000;\n\n/**\n * Accumulates ops and the client-side error table while the callback runs.\n *\n * Exported for the runtime and for tests that need the serialised plan without\n * a server; author code never sees it.\n */\nexport class TxPlanBuilder {\n private readonly ops: TxWireOp[] = [];\n /** Errors handed to expectations, indexed by the `slot` the server echoes. */\n private readonly slots: Error[] = [];\n\n /** The table surface handed to the callback. Untyped here; the public\n * `transaction()` signatures put the schema types on top. */\n table(name: string): TxTable<Record<string, unknown>, Record<string, unknown>> {\n return {\n insert: (values) => {\n const encoded = encodeMap(values as Record<string, unknown>, false);\n if (Object.keys(encoded).length === 0) {\n throw new TxPlanError(`${name}.insert() needs at least one column`);\n }\n return this.push({ op: \"insert\", table: name, values: encoded }, `${name}.insert()`);\n },\n\n put: (values, options) => {\n const encoded = encodeMap(values as Record<string, unknown>, false);\n if (Object.keys(encoded).length === 0) {\n throw new TxPlanError(`${name}.put() needs at least one column`);\n }\n if (options.onConflict.length === 0) {\n throw new TxPlanError(`${name}.put() needs at least one onConflict column`);\n }\n return this.push(\n { op: \"upsert\", table: name, values: encoded, onConflict: options.onConflict },\n `${name}.upsert()`,\n );\n },\n\n insertMany: (rows, opts) => {\n if (rows.length === 0) {\n // Nothing to write. Emitting an op would be a guaranteed 400 (the\n // server requires rows), and refusing outright would punish the\n // ordinary \"filtered every candidate out\" case.\n return new TxRowsImpl(this, SKIPPED_OP, `${name}.insertMany()`);\n }\n if (rows.length > MAX_ROWS) {\n throw new TxPlanError(\n `${name}.insertMany() has ${rows.length} rows; the limit is ${MAX_ROWS}. ` +\n `Split the write across requests.`,\n );\n }\n const encoded = rows.map((row) => encodeMap(row as Record<string, unknown>, false));\n assertUniformRows(encoded, name);\n if (opts !== undefined && opts.onConflict.length === 0) {\n throw new TxPlanError(\n `${name}.insertMany() was given a conflict action with no onConflict ` +\n `columns. Postgres matches a collision on columns, so name them.`,\n );\n }\n return this.push(\n {\n op: \"insertMany\",\n table: name,\n rows: encoded,\n // Omitted entirely when no options were given, so the op a plain\n // insertMany produces is byte-identical to the one it produced\n // before this option existed.\n ...(opts !== undefined\n ? { onConflict: opts.onConflict, action: opts.action ?? \"ignore\" }\n : {}),\n },\n `${name}.insertMany()`,\n );\n },\n\n updateWhere: (where, set) => {\n const encodedWhere = encodeFilterMap(where as Record<string, unknown>);\n const encodedSet = encodeMap(set as Record<string, unknown>, true);\n if (Object.keys(encodedWhere).length === 0) {\n throw new TxPlanError(\n `${name}.updateWhere() needs a filter. An update with no filter ` +\n `rewrites the whole table.`,\n );\n }\n if (Object.keys(encodedSet).length === 0) {\n throw new TxPlanError(`${name}.updateWhere() needs at least one column to set`);\n }\n return this.push(\n { op: \"update\", table: name, set: encodedSet, where: encodedWhere },\n `${name}.updateWhere()`,\n );\n },\n\n deleteWhere: (where) => {\n const encodedWhere = encodeFilterMap(where as Record<string, unknown>);\n if (Object.keys(encodedWhere).length === 0) {\n throw new TxPlanError(\n `${name}.deleteWhere() needs a filter. A delete with no filter empties ` +\n `the table.`,\n );\n }\n return this.push(\n { op: \"delete\", table: name, where: encodedWhere },\n `${name}.deleteWhere()`,\n );\n },\n\n select: (where, options) => {\n const op: TxWireOp = { op: \"select\", table: name };\n const encodedWhere = encodeFilterMap((where ?? {}) as Record<string, unknown>);\n if (Object.keys(encodedWhere).length > 0) op.where = encodedWhere;\n if (options?.limit !== undefined) {\n if (!Number.isInteger(options.limit) || options.limit < 0) {\n throw new TxPlanError(\n `${name}.select(): limit needs a non-negative integer, got ${String(options.limit)}`,\n );\n }\n op.limit = options.limit;\n }\n if (options?.lock !== undefined) op.lock = options.lock;\n return this.push(op, `${name}.select()`);\n },\n };\n }\n\n private push(op: TxWireOp, what: string): TxRows<Record<string, unknown>> {\n if (this.ops.length >= MAX_OPS) {\n throw new TxPlanError(\n `this transaction has ${MAX_OPS} operations, which is the limit. Use ` +\n `insertMany() for bulk writes, or split the work across requests.`,\n );\n }\n const index = this.ops.length;\n this.ops.push(op);\n return new TxRowsImpl(this, index, what);\n }\n\n /** Attach an expectation to an op and record its error in the slot table. */\n attachGuard(opIndex: number, kind: TxWireGuard[\"kind\"], n: number, error: Error): void {\n const op = this.ops[opIndex];\n // Unreachable: the index came from `push`. A loud failure beats a silently\n // unguarded write if that ever stops being true.\n if (!op) throw new TxPlanError(`internal: expectation on unknown operation ${opIndex}`);\n const slot = this.slots.length;\n this.slots.push(error);\n op.guard = { kind, n, slot };\n }\n\n /** The serialisable plan. Empty when the callback described no writes. */\n body(): TxPlanBody {\n return { ops: this.ops };\n }\n\n /** The error the server's `slot` selects, or `null` when it names one this\n * plan never declared (a server/client disagreement, not a tenant error). */\n errorForSlot(slot: number): Error | null {\n return this.slots[slot] ?? null;\n }\n}\n\nfunction assertUniformRows(rows: Record<string, TxWireValue>[], table: string): void {\n const first = rows[0];\n if (!first) return;\n const want = Object.keys(first);\n const wantKey = want.join(\",\");\n for (let i = 1; i < rows.length; i++) {\n const got = Object.keys(rows[i] as Record<string, TxWireValue>);\n if (got.join(\",\") !== wantKey) {\n // One statement, one column list. A ragged row would take the DB default\n // for the column it omitted — a write that succeeds and is wrong.\n throw new TxPlanError(\n `${table}.insertMany(): every row must set the same columns. Row 0 sets ` +\n `[${want.join(\", \")}] but row ${i} sets [${got.join(\", \")}]. ` +\n `(A property set to \\`undefined\\` counts as absent — use \\`null\\`.)`,\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — materialisation\n// ---------------------------------------------------------------------------\n\n/**\n * Replace every handle in the callback's return value with what the server\n * actually produced.\n *\n * Walks arrays and PLAIN objects only. Class instances (a Date, a Zod schema, a\n * domain object) are returned untouched — recursing into them would rebuild them\n * as bare objects, and nothing inside one can be a handle that this SDK created.\n */\nexport function materializeResult(value: unknown, results: TxPlanOpResult[]): unknown {\n const ref = refDescriptor(value);\n if (ref) {\n const row = rowOf(results, ref.op, `\\`${ref.field}\\``);\n if (!(ref.field in row)) {\n throw new TxPlanError(\n `the transaction's operation ${ref.op} returned no column \\`${ref.field}\\`.`,\n );\n }\n return row[ref.field];\n }\n\n const rowOp = rowOpIndex(value);\n if (rowOp !== null) return rowOf(results, rowOp, \"a row\");\n\n if (isRowsHandle(value)) {\n throw new TxPlanError(\n \"an operation result cannot be returned from a transaction callback: its \" +\n \"row count is not known until the plan runs. Declare an expectation \" +\n \"(`.expectOne(err)`) and return the row, or a column of it.\",\n );\n }\n\n if (Array.isArray(value)) return value.map((item) => materializeResult(item, results));\n\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value)) out[key] = materializeResult(item, results);\n return out;\n }\n\n return value;\n}\n\nfunction rowOf(results: TxPlanOpResult[], opIndex: number, what: string): Record<string, unknown> {\n const result = results[opIndex];\n if (!result) {\n throw new TxPlanError(\n `the transaction returned no result for operation ${opIndex}, so ${what} ` +\n `cannot be read.`,\n );\n }\n const row = result.rows[0];\n if (!row) {\n // Unreachable through the public API: a handle only exists behind an\n // `expectOne`, and the server rolls back rather than answering 200 with a\n // guard unmet. Loud, because the alternative is `undefined` in tenant data.\n throw new TxPlanError(\n `the transaction's operation ${opIndex} returned no row, so ${what} cannot ` +\n `be read.`,\n );\n }\n return row;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime — the driver\n// ---------------------------------------------------------------------------\n\n/** What {@link runTxPlan} needs from the runtime: one call, one transaction. */\nexport interface TxPlanTransport {\n txPlan(plan: TxPlanBody): Promise<TxPlanResponse>;\n}\n\n/**\n * Build the plan, send it, and resolve the callback's return value.\n *\n * A callback that throws never reaches the network: there is nothing to roll\n * back because nothing was sent. A callback that describes no writes also skips\n * the round trip entirely.\n *\n * The RETURN type is `unknown` here on purpose. `Materialized<T>` is a compile-\n * time rewrite of the callback's return type, and it belongs on the public\n * `transaction()` signatures where `T` is inferred from the author's callback;\n * threading it through this driver as well would mean inferring `T` twice, from\n * two different positions, and reconciling them. The public wrappers narrow\n * once, at the seam where the value types were erased anyway.\n */\nexport async function runTxPlan<THandle>(\n transport: TxPlanTransport,\n // TUTAMAĞIN TAMAMI, yalnız `tables` DEĞİL. Tutamak artık şema yüzeyini de\n // taşıyor (`tx.public.x`, `tx.<şema>.x`), ve onu BURADA `{ tables }` diye\n // yeniden kurmak o yüzeyi sessizce düşürürdü.\n handle: THandle,\n builder: TxPlanBuilder,\n fn: (tx: THandle) => unknown,\n): Promise<unknown> {\n const returned = fn(handle);\n const body = builder.body();\n if (body.ops.length === 0) {\n return materializeResult(returned, []);\n }\n\n let response: TxPlanResponse;\n try {\n response = await transport.txPlan(body);\n } catch (err) {\n throw translateRejection(err, builder);\n }\n return materializeResult(returned, response.results);\n}\n\n/**\n * Turn the broker's rejection back into the tenant's own error.\n *\n * The server never sees the `Error` an expectation was given — only its slot\n * index — so this is the only place the intended error can be produced. A\n * rejection this SDK does not recognise passes through untouched: inventing an\n * error for it would hide the real failure.\n */\nfunction translateRejection(err: unknown, builder: TxPlanBuilder): unknown {\n if (typeof err !== \"object\" || err === null) return err;\n const rejection = err as TxPlanRejection;\n if (rejection.error_code !== \"tx_guard_failed\" || typeof rejection.slot !== \"number\") {\n return err;\n }\n return builder.errorForSlot(rejection.slot) ?? err;\n}\n","import type { DBClient, DBOps } from \"../../endpoint.js\";\n// The SAME refusals the engine applies. Without these the fake accepted every\n// call the driver path had just started rejecting, and the scaffold points\n// authors at this fake to test their services — so the test went green and\n// production threw. Measured against the published 24.1.0.\nimport { assertUsableFilter, assertUsableWriteValues, assertNoExpressionHandles, isColRef, isSqlFragment, isNowExpr, isColumnExpr } from \"../../db/input-guards.js\";\n\n/**\n * `sqlFragment` sahte veritabanında ÇALIŞTIRILAMAZ — ve sessizce yok sayılamaz.\n *\n * Fake'in bir SQL değerlendiricisi yok. Fragment'i görmezden gelmek, filtreyi\n * hiç uygulamamak demektir: test TÜM satırları görür, üretim ise süzülmüş\n * satırları. Yazarın testi o gün yeşil, üretim yanlış olur — bu dosyanın var\n * olma sebebi tam olarak o sınıf hata. O yüzden adıyla reddediliyor, ve hata\n * çalışan bir alternatif söylüyor (P6).\n */\nfunction refuseFragment(caller: string, table: string, where: unknown): void {\n if (isSqlFragment(where)) {\n throw new Error(\n `${caller}(${table}): fakeDatabase bir sqlFragment'i değerlendiremez — sahte depo SQL çalıştırmaz. ` +\n `Filtreyi tipli filtre diliyle kurun (gt/gte/lt/lte/neq/in/contains/isNull, OR/AND/NOT), ` +\n `ya da bu testi gerçek bir Postgres'e karşı yazın.`,\n );\n }\n // İÇ İÇE de reddedilir. Yalnız ÜST DÜZEYE bakmak, { OR: [ sqlFragment tag, … ] }\n // filtresini fake'te SESSİZCE boş sonuca çeviriyordu; motor onu derliyor\n // (W2-B/C5, ölçüldü). Reddin de bileşim dallarını dolaşması gerekiyor.\n if (where === null || typeof where !== \"object\") return;\n for (const [k, v] of Object.entries(where as Record<string, unknown>)) {\n if (k === \"OR\" || k === \"AND\") {\n for (const branch of (Array.isArray(v) ? v : [])) refuseFragment(caller, table, branch);\n } else if (k === \"NOT\") {\n refuseFragment(caller, table, v);\n }\n }\n}\n\n/**\n * Sayaç aritmetiği — motorun döndürdüğü ALANDA.\n *\n * Postgres `numeric` kolonu STRING döndürür ve toplamayı tam yapar. Fake\n * `Number()` ile hesaplıyordu; ölçülen sonuçlar: `\"0.10\" + \"0.20\"` →\n * `0.30000000000000004`, `\"12345678901234567890\" + 1` →\n * `12345678901234567000`, ve satırın tipi string'den number'a KAYIYORDU.\n * D-007'nin (string miktar) var olma sebebi tam olarak bu kayıptı; fake onu\n * geri getiriyordu (inceleme I-4/I-3).\n *\n * `null` + n = `null`: Postgres'te de öyle, satır değişmez.\n */\nfunction addDecimal(cell: unknown, by: number | string, sign: 1 | -1): unknown {\n if (cell === null || cell === undefined) return null;\n if (typeof cell === \"number\" && typeof by === \"number\") return cell + sign * by;\n const a = String(cell);\n const b = String(by);\n const parse = (x: string): { unit: bigint; scale: number } | null => {\n const m = /^([+-]?)(\\d*)(?:\\.(\\d*))?$/.exec(x.trim());\n if (m === null || (m[2] === \"\" && (m[3] ?? \"\") === \"\")) return null;\n const frac = m[3] ?? \"\";\n const unit = BigInt(`${m[1] === \"-\" ? \"-\" : \"\"}${m[2] === \"\" ? \"0\" : m[2]}${frac}`);\n return { unit, scale: frac.length };\n };\n const pa = parse(a);\n const pb = parse(b);\n if (pa === null || pb === null) {\n // Motor bu durumda Postgres'e sorar ve `operator does not exist:\n // text + integer` alır. Sessizce NaN yazmak yerine ADIYLA reddediyoruz.\n throw new Error(\n `fakeDatabase: increment()/decrement() sayısal olmayan bir değere uygulandı (\"${a}\") — ` +\n `Postgres bunu \"operator does not exist\" ile reddeder.`,\n );\n }\n const scale = Math.max(pa.scale, pb.scale);\n const lift = (v: { unit: bigint; scale: number }): bigint =>\n v.unit * 10n ** BigInt(scale - v.scale);\n const total = lift(pa) + BigInt(sign) * lift(pb);\n if (scale === 0) return typeof cell === \"number\" ? Number(total) : total.toString();\n const neg = total < 0n;\n const digits = (neg ? -total : total).toString().padStart(scale + 1, \"0\");\n const out = `${neg ? \"-\" : \"\"}${digits.slice(0, -scale)}.${digits.slice(-scale)}`;\n return typeof cell === \"number\" ? Number(out) : out;\n}\nimport { columnExprOf } from \"../../db/tx-plan.js\";\nimport type {\n TxPlanBody,\n TxPlanOpResult,\n TxPlanRejection,\n TxPlanResponse,\n TxWireGuard,\n TxWireOp,\n TxWireValue,\n} from \"../../db/tx-plan.js\";\n\n/** Tracked records for assertions. */\ninterface TrackedRecords {\n inserted: Map<string, Record<string, unknown>[]>;\n updated: Map<string, Record<string, unknown>[]>;\n deleted: Map<string, string[]>;\n}\n\n/** Mock DB client with tracking and seed data support. */\n/**\n * TEK EŞLEŞTİRİCİ — `findMany`, `updateMany`, `deleteMany` ve `count` bunu\n * kullanır.\n *\n * Motorda tek bir `compileWhereBare` var; fake'te DÖRT ayrı eşleştirici vardı\n * ve üçü yalnız katı eşitliğe bakıyordu. Ölçülen sonuç (inceleme C3/C6/I2):\n * `updateMany({ id, balance: { gte: \"5.00\" } }, …)` fake'te HİÇBİR satır\n * eşleştirmiyordu, yani FR-014'ün doc'unun ÖĞRETTİĞİ \"yetersiz bakiye\" deseni\n * fake'e karşı HER ZAMAN başarısız dala düşüyor — yazar başarı yolunu hiç test\n * edemiyor, üretimde para gerçekten çekiliyor.\n */\nfunction rowMatchesFilter(\n caller: string,\n table: string,\n row: Record<string, unknown>,\n f: Record<string, unknown>,\n): boolean {\n return Object.entries(f).every(([k, c]) => {\n if (k === \"OR\") return (c as Record<string, unknown>[]).some((b) => rowMatchesFilter(caller, table, row, b));\n if (k === \"AND\") return (c as Record<string, unknown>[]).every((b) => rowMatchesFilter(caller, table, row, b));\n if (k === \"NOT\") return !rowMatchesFilter(caller, table, row, c as Record<string, unknown>);\n // `has` ilişki GRAFİĞİ ister ve sahte deponun grafiği YOK — tablolar bir\n // Map'te, aralarındaki yabancı anahtarlar hiçbir yerde. Sessizce yok saymak\n // filtreyi hiç uygulamamak olurdu: test TÜM satırları görür, üretim\n // süzülmüş satırları. Bu dosyanın var olma sebebi tam olarak o sınıf.\n if (k === \"has\") {\n throw new Error(\n `${caller}(${table}): fakeDatabase bir \\`has\\` filtresini çözemez — ilişki grafiği ` +\n `bildirimden türetiliyor ve sahte deponun bildirimi yok. Bu testi gerçek bir Postgres'e ` +\n `karşı yazın, ya da ilişkiyi filtrede AÇIKÇA kurun (önce ilişki tablosunu okuyup ` +\n `{ id: { in: [...] } } ile süzün).`,\n );\n }\n return matchesCell(caller, table, row, k, c);\n });\n}\n\n/**\n * SQL'in üç değerli mantığı: NULL taşıyan karşılaştırma UNKNOWN'dır, yani satır\n * EŞLEŞMEZ. Fake `===` kullanıyordu ve iki NULL kolonu EŞİT sayıyordu — motorun\n * her yerde uyguladığı FR-006 doktrininin tersi (inceleme I3, ölçüldü).\n */\nfunction cmp(a: unknown, b: unknown, op: string): boolean {\n if (a === null || a === undefined || b === null || b === undefined) return false;\n const l = a instanceof Date ? a.getTime() : a;\n const r = b instanceof Date ? b.getTime() : b;\n switch (op) {\n case \"eq\": return l === r;\n case \"neq\": return l !== r;\n case \"gt\": return (l as number) > (r as number);\n case \"gte\": return (l as number) >= (r as number);\n case \"lt\": return (l as number) < (r as number);\n case \"lte\": return (l as number) <= (r as number);\n default: return false;\n }\n}\n\nfunction matchesCell(\n caller: string,\n table: string,\n row: Record<string, unknown>,\n key: string,\n cond: unknown,\n): boolean {\n // Kolon-kolon karşılaştırma (FR-011) — motorla PARİTE. Fake `col()`'u\n // tanımasaydı `{ $col: \"x\" }` nesnesini DEĞER sanıp eşitlik kurar, hiçbir\n // satır dönmez ve yazarın testi sessizce boş sonuca geçerdi.\n if (isColRef(cond)) return cmp(row[key], row[cond.$col], \"eq\");\n // `now()` — motorla PARİTE. Tutamak bir Proxy, yani `Object.entries` BOŞ\n // döner ve `.every()` boş listede TRUE'dur: ayırt edilmezse koşul HER SATIRLA\n // eşleşirdi. Yani \"süresi geçmemişler\" filtresi testte süresi geçenleri de\n // döndürür, test yeşil kalır ve üretimde davranış AYRIŞIRDI.\n if (isNowExpr(cond)) return cmp(row[key], new Date().toISOString(), \"eq\");\n if (cond !== null && typeof cond === \"object\" && !Array.isArray(cond)) {\n return Object.entries(cond as Record<string, unknown>).every(([op, v]) => {\n const cell = row[key];\n if (isNowExpr(v)) {\n if (![\"neq\", \"gt\", \"gte\", \"lt\", \"lte\"].includes(op)) {\n throw new Error(`${caller}(${table}): where.${key}.${op} now() ile kullanılamaz`);\n }\n return cmp(cell, new Date().toISOString(), op);\n }\n if (isColumnExpr(v)) {\n throw new Error(\n `${caller}(${table}): where.${key}.${op} bir YAZMA ifadesi aldı (increment/decrement) — ` +\n `karşılaştırma değeri değil. Sunucu saati için now() kullanın.`,\n );\n }\n if (isColRef(v)) {\n if (![\"neq\", \"gt\", \"gte\", \"lt\", \"lte\"].includes(op)) {\n throw new Error(`${caller}(${table}): where.${key}.${op} col() ile kullanılamaz`);\n }\n return cmp(cell, row[v.$col], op);\n }\n switch (op) {\n case \"in\":\n if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${key}.in bir dizi olmalı`);\n if (v.some(isColRef)) {\n throw new Error(\n `${caller}(${table}): where.${key}.in col() ile kullanılamaz — kolon karşılaştırması için gt/gte/lt/lte/neq kullanın`,\n );\n }\n return v.includes(cell);\n case \"neq\": case \"gt\": case \"gte\": case \"lt\": case \"lte\":\n return cmp(cell, v, op);\n // K1 metin operatörleri (FR-005) ve null testi (FR-006). Bunlar BURADA\n // da olmak zorunda: guard onları KABUL ettiği anda fake sessizce yanlış\n // cevap verir ve yazarın testi, üretimde farklı davranan koda karşı\n // yeşil verirdi (input-guards.ts'in uyardığı tam sınıf).\n case \"isNull\":\n if (typeof v !== \"boolean\") throw new Error(`${caller}(${table}): where.${key}.isNull bir boolean olmalı`);\n return v ? cell == null : cell != null;\n case \"contains\": case \"icontains\": case \"startsWith\": case \"endsWith\": {\n if (typeof v !== \"string\") throw new Error(`${caller}(${table}): where.${key}.${op} bir string olmalı`);\n if (typeof cell !== \"string\") return false;\n if (op === \"contains\") return cell.includes(v);\n if (op === \"icontains\") return cell.toLowerCase().includes(v.toLowerCase());\n if (op === \"startsWith\") return cell.startsWith(v);\n return cell.endsWith(v);\n }\n default:\n throw new Error(`${caller}(${table}): where.${key} bilinmeyen operatör \"${op}\"`);\n }\n });\n }\n return row[key] === cond;\n}\n\nexport interface MockDBClient extends DBClient {\n /** Get records inserted into a table. */\n inserted(table: string): Record<string, unknown>[];\n /** Get records updated in a table. */\n updated(table: string): Record<string, unknown>[];\n /** Get IDs deleted from a table. */\n deleted(table: string): string[];\n /** Pre-seed data into a table for findById/findMany. */\n seed(table: string, data: Record<string, unknown>[]): void;\n}\n\n/** Create a mock DB client with in-memory tracking. */\nexport function createMockDB(): MockDBClient {\n const store = new Map<string, Record<string, unknown>[]>();\n const tracked: TrackedRecords = {\n inserted: new Map(),\n updated: new Map(),\n deleted: new Map(),\n };\n\n function rowsOf(table: string): Record<string, unknown>[] {\n let rows = store.get(table);\n if (!rows) {\n rows = [];\n store.set(table, rows);\n }\n return rows;\n }\n\n function track(\n map: Map<string, Record<string, unknown>[]>,\n table: string,\n row: Record<string, unknown>,\n ): void {\n const list = map.get(table);\n if (list) list.push(row);\n else map.set(table, [row]);\n }\n\n/**\n * EKLEME yolunda değerleri motorun yaptığı gibi çöz — `now()` sunucu saati,\n * sayaç ifadesi adıyla ret.\n *\n * Fake bunu bilmeseydi `assertNoExpressionHandles` `now()`'ı reddeder, test\n * kırmızı olur ve yazar ÇALIŞAN bir çağrıyı bozuk sanırdı. Sürüm 28'de motor\n * beş ekleme kurucusunun hepsinde `now()` derliyor.\n */\nfunction resolveInsertValues(\n caller: string,\n table: string,\n data: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(data)) {\n const expr = columnExprOf(v);\n if (expr !== null && expr.fn === \"now\") {\n out[k] = new Date().toISOString();\n continue;\n }\n if (expr !== null) {\n throw new Error(\n `${caller}(${table}): \"${k}\" ${expr.fn === \"inc\" ? \"increment()\" : \"decrement()\"} aldı — ` +\n `satır HENÜZ YOK, yani \"kolonun şu anki değeri\" diye bir şey yok.`,\n );\n }\n out[k] = v;\n }\n return out;\n}\n\n // Build the op surface first (the six string-keyed ops). `txPlan` below\n // interprets a whole plan against the SAME in-memory store and tracking maps,\n // so a transaction's writes are visible to later assertions exactly as a\n // direct write would be.\n const ops: DBOps = {\n // The bulk ops and count run against the SAME in-memory store the direct\n // ops write to, so a test that writes three rows and counts them gets 3 —\n // a mock that answered 0 would make the surface look broken in exactly the\n // tests meant to prove it works.\n async updateMany(table: string, where: Record<string, unknown>, set: Record<string, unknown>) {\n if (Object.keys(where).length === 0) throw new Error(`updateMany(${table}): boş filtre`);\n assertUsableFilter(\"updateMany\", table, where);\n refuseFragment(\"updateMany\", table, where);\n assertUsableWriteValues(\"updateMany\", table, Object.keys(set), set);\n const hit = (store.get(table) ?? []).filter((r) =>\n rowMatchesFilter(\"updateMany\", table, r, where),\n );\n // increment()/decrement() (FR-012) — motorla PARİTE. Fake ifadeyi DEĞER\n // sanıp yazsaydı, sayaç kolonu bir proxy nesnesine dönerdi ve yazarın\n // testi \"artış oldu\" diye değil, sessizce bozuk veriyle geçerdi.\n for (const row of hit) {\n for (const [k, v] of Object.entries(set)) {\n const expr = columnExprOf(v);\n if (expr !== null && expr.fn === \"now\") {\n // Motor `SET col = now()` derliyor; fake'in karşılığı bir zaman damgası.\n row[k] = new Date();\n continue;\n }\n if (expr !== null) {\n row[k] = addDecimal(row[k], expr.by as number | string, expr.fn === \"inc\" ? 1 : -1);\n continue;\n }\n row[k] = v;\n }\n }\n return hit;\n },\n async deleteMany(table: string, where: Record<string, unknown>) {\n if (Object.keys(where).length === 0) throw new Error(`deleteMany(${table}): boş filtre`);\n assertUsableFilter(\"deleteMany\", table, where);\n refuseFragment(\"deleteMany\", table, where);\n const list = store.get(table) ?? [];\n const keep = list.filter((r) => !rowMatchesFilter(\"deleteMany\", table, r, where));\n store.set(table, keep);\n return list.length - keep.length;\n },\n async count(table: string, where: Record<string, unknown> = {}) {\n assertUsableFilter(\"count\", table, where);\n // `refuseFragment` burada ATLANMIŞTI: fragment'li count fake'te sessizce\n // 0 döndürüyordu, motor gerçek sayıyı (W2-B/C4).\n refuseFragment(\"count\", table, where);\n return (store.get(table) ?? []).filter((r) =>\n rowMatchesFilter(\"count\", table, r, where),\n ).length;\n },\n async search(_table: string, _params?: Record<string, unknown>) {\n return [];\n },\n async facets(_table: string, _params?: Record<string, unknown>) {\n return {};\n },\n async similar() {\n return [];\n },\n async recommend() {\n return [];\n },\n async supersede(_table: string, _id: string, row: Record<string, unknown>) {\n return { id: crypto.randomUUID(), ...row };\n },\n async query(_sql: string, _params?: unknown[]) {\n return [];\n },\n\n async insert(table: string, raw: Record<string, unknown>) {\n const data = resolveInsertValues(\"insert\", table, raw);\n assertUsableWriteValues(\"insert\", table, Object.keys(data), data);\n // Motorla PARİTE: ifade tutamağı bu yolda değer değildir. Fake onu\n // satıra YAZIYORDU; satır artık JSON'a bile çevrilemiyordu.\n assertNoExpressionHandles(\"insert\", table, Object.keys(data), data);\n const record = { id: crypto.randomUUID(), ...data };\n rowsOf(table).push(record);\n track(tracked.inserted, table, record);\n return record;\n },\n\n /**\n * `insertMany` — motorla PARİTE, ANAHTAR KÜMESİ kuralı dahil.\n *\n * Fake bu kuralı bilmeseydi farklı şekilli satırları kabul eder, test yeşil\n * kalır, üretimde motor adıyla reddederdi — yani fake, sürüm atlamayı\n * kolaylaştırmak yerine gizlerdi.\n */\n async insertMany(table: string, rows: readonly Record<string, unknown>[]) {\n if (rows.length === 0) return [];\n const cols = Object.keys(rows[0]!);\n for (let i = 1; i < rows.length; i++) {\n const missing = cols.filter((c) => !(c in rows[i]!));\n const extra = Object.keys(rows[i]!).filter((k) => !cols.includes(k));\n if (missing.length > 0 || extra.length > 0) {\n throw new Error(\n `insertMany(${table}): ${i}. satırın kolonları ilk satırla aynı değil` +\n (missing.length > 0 ? ` (eksik: ${missing.join(\", \")})` : \"\") +\n (extra.length > 0 ? ` (fazla: ${extra.join(\", \")})` : \"\") +\n `. Eksikler için null yazın, ya da farklı şekilli satırları ayrı çağrılarda ekleyin.`,\n );\n }\n }\n const out: Record<string, unknown>[] = [];\n for (const rawRow of rows) {\n const data = resolveInsertValues(\"insertMany\", table, rawRow);\n assertUsableWriteValues(\"insertMany\", table, cols, data);\n assertNoExpressionHandles(\"insertMany\", table, cols, data);\n const record = { id: crypto.randomUUID(), ...data };\n rowsOf(table).push(record);\n track(tracked.inserted, table, record);\n out.push(record);\n }\n return out;\n },\n\n /**\n * `claim` — motorla PARİTE (FR-033).\n *\n * Fake bunu sunmasaydı `claim` ile yazılmış bir servis, iskelenin yazarlara\n * önerdiği test yolunda `undefined is not a function` verirdi; sunup da\n * FARKLI davransaydı (ör. hep `inserted: true`) idempotency testi üretimde\n * çalışmayan koda karşı yeşil olurdu.\n *\n * Anahtar ZATEN VARSA var olan satır dönüyor ve hiçbir şey yazılmıyor —\n * motorun 23505 dalının aynısı.\n */\n /**\n * `lockRows` — sahte depoda kilit YOKTUR, ama çağrı da patlamamalı.\n *\n * Motorla parite burada \"aynı SQL\" değil, \"aynı SÖZLEŞME\": boş liste\n * no-op, tekrar edenler tekilleşir, ve PK'sı olmayan tablo adıyla\n * reddedilir. Kilidin kendisi tek işlemli bir sahte depoda anlamsız —\n * ama sözleşmeyi bozan bir çağrı burada da hata almalı.\n */\n async lockRows(table: string, ids: readonly string[]) {\n if (ids.length === 0) return;\n const rows = store.get(table) ?? [];\n const unique = [...new Set(ids)].sort();\n const missing = unique.filter((id) => !rows.some((r) => r[\"id\"] === id));\n if (missing.length > 0 && rows.length > 0) {\n // Sessiz geçmek, testin \"kilitledim\" sanmasına yol açardı.\n throw new Error(\n `lockRows(${table}): şu id'ler yok: ${missing.join(\", \")} — kilitlenecek satır bulunamadı.`,\n );\n }\n },\n\n /** Sahte depoda kilit yok; sözleşme (çağrı patlamaz) korunuyor. */\n async advisoryXactLock(_key: string) {\n return undefined;\n },\n\n async claim(\n table: string,\n unique: Record<string, unknown>,\n extra: Record<string, unknown> = {},\n ) {\n const keyCols = Object.keys(unique);\n if (keyCols.length === 0) {\n throw new Error(\n `claim(${table}): benzersiz alan verilmedi. claim, bir anahtarı sahiplenmektir; ` +\n `anahtar yoksa sahiplenecek bir şey de yok — insert(${table}, …) kullanın.`,\n );\n }\n assertUsableWriteValues(\"claim\", table, keyCols, unique);\n const existing = (store.get(table) ?? []).find((r) =>\n keyCols.every((c) => r[c] === unique[c]),\n );\n if (existing) return { inserted: false, row: existing };\n const record = { id: crypto.randomUUID(), ...unique, ...extra };\n rowsOf(table).push(record);\n track(tracked.inserted, table, record);\n return { inserted: true, row: record };\n },\n\n // Same semantics the engine's SQL has: match on the conflict columns, update\n // everything else, and return the resulting row either way.\n async put(\n table: string,\n rawData: Record<string, unknown>,\n opts: { onConflict: readonly string[] },\n ) {\n if (opts.onConflict.length === 0) {\n throw new Error(`put into ${table}: onConflict en az bir kolon adı ister`);\n }\n const data = resolveInsertValues(\"put\", table, rawData);\n assertUsableWriteValues(\"upsert\", table, Object.keys(data), data);\n // Motorla PARİTE: ifade tutamağı bu yolda değer değildir. Fake onu\n // satıra YAZIYORDU; satır artık JSON'a bile çevrilemiyordu.\n assertNoExpressionHandles(\"upsert\", table, Object.keys(data), data);\n const rows = rowsOf(table);\n const existing = rows.find((r) => opts.onConflict.every((c) => r[c] === data[c]));\n if (existing) {\n for (const [k, v] of Object.entries(data)) {\n if (!opts.onConflict.includes(k)) existing[k] = v;\n }\n return existing;\n }\n const record = { id: crypto.randomUUID(), ...data };\n rows.push(record);\n track(tracked.inserted, table, record);\n return record;\n },\n\n async update(table: string, id: string, data: Record<string, unknown>) {\n assertUsableWriteValues(\"update\", table, Object.keys(data), data);\n const rows = store.get(table) ?? [];\n const idx = rows.findIndex((r) => r[\"id\"] === id);\n // 28'de motor `update(id)` ile `updateMany`'nin SET kurucusunu PAYLAŞIYOR,\n // yani `now()` ve sayaç ifadeleri burada da geçerli. Fake eskisi gibi\n // reddetseydi, ÇALIŞAN bir çağrı testte kırmızı olurdu — fake'in var olma\n // amacının tam tersi.\n const current = idx >= 0 ? rows[idx]! : {};\n const applied: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(data)) {\n const expr = columnExprOf(v);\n if (expr !== null && expr.fn === \"now\") {\n applied[k] = new Date();\n continue;\n }\n if (expr !== null) {\n applied[k] = addDecimal(current[k], expr.by, expr.fn === \"inc\" ? 1 : -1);\n continue;\n }\n applied[k] = v;\n }\n assertNoExpressionHandles(\"update\", table, Object.keys(applied), applied);\n const updated = idx >= 0\n ? { ...rows[idx], ...applied }\n : { id, ...applied };\n if (idx >= 0) {\n rows[idx] = updated;\n }\n track(tracked.updated, table, updated);\n return updated;\n },\n\n async delete(table: string, id: string) {\n const rows = store.get(table) ?? [];\n const idx = rows.findIndex((r) => r[\"id\"] === id);\n if (idx >= 0) rows.splice(idx, 1);\n const list = tracked.deleted.get(table);\n if (list) list.push(id);\n else tracked.deleted.set(table, [id]);\n },\n\n async findById(table: string, id: string) {\n const rows = store.get(table) ?? [];\n return rows.find((r) => r[\"id\"] === id) ?? null;\n },\n\n // The SAME filter language the engine compiles to SQL: a plain value is\n // equality, an object is an operator set. A fake that understood less would\n // pass a service test that the live database then fails — which is the one\n // thing a stand-in must never do.\n async findMany(\n table: string,\n query?: Record<string, unknown>,\n opts?: {\n orderBy?:\n | { column: string; direction?: \"asc\" | \"desc\"; nulls?: \"first\" | \"last\" }\n | { column: string; direction?: \"asc\" | \"desc\"; nulls?: \"first\" | \"last\" }[];\n select?: string[];\n limit?: number;\n },\n ) {\n assertUsableFilter(\"findMany\", table, query);\n refuseFragment(\"findMany\", table, query);\n const rows = store.get(table) ?? [];\n let out = query\n ? rows.filter((row) => rowMatchesFilter(\"findMany\", table, row, query))\n : [...rows];\n // Çoklu sıralama ve NULL yeri — motorla PARİTE (FR-008). Tek obje de\n // kabul edilir; liste hâline getirilip aynı yoldan geçer.\n const orderSpecs = opts?.orderBy === undefined\n ? []\n : Array.isArray(opts.orderBy) ? opts.orderBy : [opts.orderBy];\n if (orderSpecs.length > 0) {\n out = [...out].sort((a, b) => {\n for (const o of orderSpecs) {\n const dir = o.direction === \"desc\" ? -1 : 1;\n const x = a[o.column];\n const y = b[o.column];\n const xNull = x === null || x === undefined;\n const yNull = y === null || y === undefined;\n if (xNull || yNull) {\n if (xNull && yNull) continue;\n // Verilmezse Postgres varsayılanı: ASC'de NULLS LAST, DESC'te FIRST.\n const nullsFirst = o.nulls === undefined ? dir === -1 : o.nulls === \"first\";\n return (xNull ? 1 : -1) * (nullsFirst ? -1 : 1);\n }\n if (x === y) continue;\n return ((x as never) < (y as never) ? -1 : 1) * dir;\n }\n return 0;\n });\n }\n const page = opts?.limit === undefined ? out : out.slice(0, opts.limit);\n // Projeksiyon (FR-009) — motorla PARİTE. Fake tam satır döndürürse, `select`\n // ile yazılmış bir kod sahte veritabanında seçilmemiş kolonu okur ve GEÇER;\n // gerçek motorda o kolon SQL'e hiç girmediği için `undefined` olur.\n const cols = opts?.select;\n if (cols === undefined || cols.length === 0) return page;\n return page.map((row) => Object.fromEntries(cols.map((c) => [c, row[c]])));\n },\n };\n\n /**\n * Interpret a whole plan, atomically.\n *\n * The rollback is the point. A test that asserts \"the second write failed, so\n * the first one is not there\" must be able to FAIL — a mock that applied ops\n * and left them applied would pass that test while the real broker rolled the\n * transaction back, or the other way round. So the store and the tracking maps\n * are snapshotted, and any failure restores both before rejecting.\n *\n * The rejection carries the same envelope fields the runtime copies off the\n * broker's response (`error_code`, `slot`), because the SDK maps `slot` back\n * to the caller's own Error — a mock that rejected with a bare Error would\n * make every guard in every tenant test look like a generic failure.\n */\n async function txPlan(plan: TxPlanBody): Promise<TxPlanResponse> {\n const snapshot = new Map<string, Record<string, unknown>[]>();\n for (const [table, rows] of store) snapshot.set(table, [...rows]);\n const trackedSnapshot: TrackedRecords = {\n inserted: cloneTracked(tracked.inserted),\n updated: cloneTracked(tracked.updated),\n deleted: new Map([...tracked.deleted].map(([k, v]) => [k, [...v]])),\n };\n\n const results: TxPlanOpResult[] = [];\n try {\n for (const op of plan.ops) {\n const result = applyOp(op, results);\n results.push(result);\n const failure = guardFailure(op.guard, result.rows.length);\n if (failure) throw failure;\n }\n } catch (err) {\n store.clear();\n for (const [table, rows] of snapshot) store.set(table, rows);\n tracked.inserted = trackedSnapshot.inserted;\n tracked.updated = trackedSnapshot.updated;\n tracked.deleted = trackedSnapshot.deleted;\n throw err;\n }\n return { results };\n }\n\n function applyOp(op: TxWireOp, results: TxPlanOpResult[]): TxPlanOpResult {\n switch (op.op) {\n case \"upsert\": {\n const values = resolveMap(op.values ?? {}, results, null);\n const conflict = op.onConflict ?? [];\n const rows = rowsOf(op.table);\n const hit = rows.find((r) => conflict.every((c) => r[c] === values[c]));\n if (hit) {\n for (const [key, value] of Object.entries(values)) {\n if (!conflict.includes(key)) hit[key] = value;\n }\n return { rows: [hit], rows_affected: 1 };\n }\n const created = { id: crypto.randomUUID(), ...values };\n rows.push(created);\n track(tracked.inserted, op.table, created);\n return { rows: [created], rows_affected: 1 };\n }\n case \"insert\": {\n const record = { id: crypto.randomUUID(), ...resolveMap(op.values ?? {}, results, null) };\n rowsOf(op.table).push(record);\n track(tracked.inserted, op.table, record);\n return { rows: [record], rows_affected: 1 };\n }\n case \"insertMany\": {\n const written = (op.rows ?? []).map((row) => {\n const record = { id: crypto.randomUUID(), ...resolveMap(row, results, null) };\n rowsOf(op.table).push(record);\n track(tracked.inserted, op.table, record);\n return record;\n });\n return { rows: written, rows_affected: written.length };\n }\n case \"update\": {\n const rows = rowsOf(op.table);\n const where = resolveMap(op.where ?? {}, results, null);\n const written: Record<string, unknown>[] = [];\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n if (!row || !matches(row, where)) continue;\n const next = { ...row, ...resolveMap(op.set ?? {}, results, row) };\n rows[i] = next;\n track(tracked.updated, op.table, next);\n written.push(next);\n }\n return { rows: written, rows_affected: written.length };\n }\n case \"delete\": {\n const rows = rowsOf(op.table);\n const where = resolveMap(op.where ?? {}, results, null);\n const removed = rows.filter((row) => matches(row, where));\n for (const row of removed) {\n rows.splice(rows.indexOf(row), 1);\n const id = row[\"id\"];\n const list = tracked.deleted.get(op.table);\n const key = typeof id === \"string\" ? id : String(id);\n if (list) list.push(key);\n else tracked.deleted.set(op.table, [key]);\n }\n return { rows: removed, rows_affected: removed.length };\n }\n case \"select\": {\n const where = resolveMap(op.where ?? {}, results, null);\n let found = rowsOf(op.table).filter((row) => matches(row, where));\n if (op.limit !== undefined) found = found.slice(0, op.limit);\n return { rows: found, rows_affected: found.length };\n }\n }\n }\n\n const client: MockDBClient = {\n ...ops,\n\n // No real savepoint in memory: the fake runs the callback against the SAME\n // store. An assertion about rollback here would be asserting the fake.\n attempt: <T,>(fn: (tx: DBOps) => Promise<T>): Promise<T> => fn(ops),\n\n txPlan,\n\n // In tests there is no real DB role; `asService()` returns the same\n // in-memory client so RLS-bypass code paths still hit the same store and\n // tracking maps. The omitted `asService` matches the contract (no\n // double-bypass), so callers can't recurse.\n asService(): Omit<DBClient, \"asService\"> {\n return client;\n },\n\n inserted(table: string) {\n return tracked.inserted.get(table) ?? [];\n },\n\n updated(table: string) {\n return tracked.updated.get(table) ?? [];\n },\n\n deleted(table: string) {\n return tracked.deleted.get(table) ?? [];\n },\n\n seed(table: string, data: Record<string, unknown>[]) {\n store.set(table, [...data]);\n },\n };\n\n return client;\n}\n\nfunction cloneTracked(\n map: Map<string, Record<string, unknown>[]>,\n): Map<string, Record<string, unknown>[]> {\n return new Map([...map].map(([k, v]) => [k, [...v]]));\n}\n\n/** Resolve one plan value: a `$ref` into an earlier result, a `$expr`, or a\n * literal. `current` is the row being updated, which is what `inc`/`dec` read. */\nfunction resolveValue(\n value: TxWireValue,\n results: TxPlanOpResult[],\n current: Record<string, unknown> | null,\n column: string,\n): unknown {\n if (typeof value !== \"object\" || value === null) return value;\n const tagged = value as { $ref?: { op: number; field: string }; $expr?: Record<string, unknown> };\n\n if (tagged.$ref) {\n const row = results[tagged.$ref.op]?.rows[0];\n if (!row) {\n throw txRejection(409, \"tx_ref_unresolved\", {\n message: `operation ${tagged.$ref.op} produced no row to reference`,\n });\n }\n return row[tagged.$ref.field];\n }\n\n if (tagged.$expr) {\n const fn = tagged.$expr[\"fn\"];\n if (fn === \"now\") return new Date().toISOString();\n const by = Number(tagged.$expr[\"by\"]);\n const base = Number(current?.[column] ?? 0);\n return fn === \"dec\" ? base - by : base + by;\n }\n\n return value;\n}\n\nfunction resolveMap(\n map: Record<string, TxWireValue>,\n results: TxPlanOpResult[],\n current: Record<string, unknown> | null,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(map)) {\n out[key] = resolveValue(value, results, current, key);\n }\n return out;\n}\n\n/** Equality filter, with `null` meaning IS NULL — the broker's rule, so a\n * `{ accepted_at: null }` guard behaves the same in a test as in production. */\nfunction matches(row: Record<string, unknown>, where: Record<string, unknown>): boolean {\n return Object.entries(where).every(([key, value]) =>\n value === null ? row[key] === null || row[key] === undefined : row[key] === value,\n );\n}\n\nfunction guardFailure(guard: TxWireGuard | undefined, count: number): unknown {\n if (!guard) return null;\n const ok =\n guard.kind === \"one\"\n ? count === 1\n : guard.kind === \"none\"\n ? count === 0\n : guard.kind === \"atLeast\"\n ? count >= guard.n\n : count <= guard.n;\n if (ok) return null;\n return txRejection(409, \"tx_guard_failed\", {\n slot: guard.slot,\n message: `expected ${guard.kind} ${guard.n} row(s), got ${count}`,\n });\n}\n\n/** Build a rejection shaped like the one the runtime throws for a broker error:\n * an Error carrying the envelope's `status`/`error_code`/`slot`. */\nfunction txRejection(\n status: number,\n code: string,\n extra: { slot?: number; message: string },\n): Error & TxPlanRejection {\n const err = new Error(extra.message) as Error & TxPlanRejection;\n err.status = status;\n err.error_code = code;\n if (extra.slot !== undefined) err.slot = extra.slot;\n return err;\n}\n","/**\n * `fakeDatabase()` — the in-memory `Database` a SERVICE-LAYER test runs against.\n *\n * WHY IT IS PUBLIC. The scaffold's own AGENTS.md tells authors to \"test the\n * service layer… the test passes a stand-in\", and the SDK shipped no stand-in to\n * pass. So every project wrote its own: a measured customer run carried two — a\n * hand-written `MembershipDb` interface for one service, and a bare `{ query }`\n * object in the tests of another. Both are guesses at this SDK's own surface,\n * and both stop compiling the moment the surface grows.\n *\n * The engine already had exactly this object; it just lived under `__tests__/`\n * where only this package could reach it.\n *\n * WHAT IT IS NOT. It does not interpret SQL. `query()` records what it was asked\n * and answers no rows, because a fake that parsed SQL would be a second, worse\n * Postgres — and a test that passed against it would prove nothing about the\n * real one. Assert on `queries` when the SQL is the thing under test, and put\n * anything that depends on what SQL RETURNS in a live test (`palbase test`).\n */\nimport { createMockDB } from \"../__tests__/helpers/mock-db.js\";\nimport type { DBClient } from \"../endpoint.js\";\n\n/** One `Database.$query(...)` call, as the service made it. */\nexport interface RecordedQuery {\n sql: string;\n params: unknown[];\n}\n\n/** What a service-layer test is handed. */\nexport interface FakeDatabase {\n /** Pass this where the service expects `Database`. */\n db: DBClient;\n /** Every `query()` call, in order. */\n queries: readonly RecordedQuery[];\n /** Put rows in a table before the code under test runs. */\n seed(table: string, rows: Record<string, unknown>[]): void;\n /** Rows inserted into a table, for asserting a write happened. */\n inserted(table: string): Record<string, unknown>[];\n /** Rows updated in a table. */\n updated(table: string): Record<string, unknown>[];\n /** Ids deleted from a table. */\n deleted(table: string): string[];\n}\n\nexport function fakeDatabase(): FakeDatabase {\n const mock = createMockDB();\n const queries: RecordedQuery[] = [];\n\n // The recorder wraps `query` and leaves every other op alone, so the fake's\n // behaviour is the engine's mock plus one observation.\n const db: DBClient = Object.assign(Object.create(Object.getPrototypeOf(mock) as object) as DBClient, mock, {\n query: async (sql: string, params: unknown[] = []) => {\n queries.push({ sql, params });\n return mock.query(sql, params);\n },\n });\n\n return {\n db,\n queries,\n seed: (table, rows) => mock.seed(table, rows),\n inserted: (table) => mock.inserted(table),\n updated: (table) => mock.updated(table),\n deleted: (table) => mock.deleted(table),\n };\n}\n"],"mappings":"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","names":["TestApiError","Error","status","error","data","body","method","path","envelope","code","String","error_description","name","required","value","envName","isLocalTarget","baseUrl","hostname","URL","secondsUntilExpiry","token","split","claims","JSON","parse","Buffer","from","toString","exp","Math","floor","Date","now","createTestApi","config","replace","apiKey","local","candidateToken","doFetch","fetch","requests","bearer","call","opts","headers","apikey","authorization","undefined","startedAt","res","stringify","text","parsed","safeParse","push","ms","ok","left","abs","get","post","patch","put","delete","query","signInAs","identity","identities","declared","Object","keys","length","join","accessToken","id","email","signIn","credentials","attempt","extra","result","e","refusal","challenge","asPowChallenge","solvePowChallenge","access_token","user","signOut","asAnonymous","parseIdentities","raw","configured","api","Proxy","_target","prop","process","env","PALBASE_TEST_BASE_URL","PALBASE_TEST_API_KEY","PALBASE_TEST_CANDIDATE","PALBASE_TEST_IDENTITIES","Reflect","isolated","over","Map","local","make","c","has","get","hit","undefined","meta","Reflect","getMetadata","inst","map","d","set","api","with","t","v","REF_BRAND","Symbol","for","brandOf","v","undefined","Object","hasOwn","REF_BRAND","isColRef","v","brandOf","$col","isSqlFragment","v","brandOf","f","$sql","undefined","Array","isArray","text","values","TX_EXPR","Symbol","for","isNowExpr","isColumnExpr","e","fn","assertNoExpressionHandles","caller","table","cols","data","c","Error","isColRef","KNOWN_OPS","Set","REFUSED_OPS","eq","assertUsableFilter","where","COMPOSITES","col","cond","Object","entries","has","branches","b","rel","inner","length","op","some","x","assertUsableWriteValues","TxRefError","Error","message","name","TxPlanError","EXPR","Symbol","for","REF","ROW","ROWS","TRAPPED_PROPS","toPrimitive","trap","prop","what","hint","description","String","columnExprOf","v","exprOf","makeRef","op","field","target","REF","Proxy","get","t","prop","TRAPPED_PROPS","includes","trap","undefined","makeRowHandle","ROW","exprOf","v","e","EXPR","SKIPPED_OP","TxRowsImpl","ROWS","guarded","builder","opIndex","what","then","TxRefError","expectOne","error","declareGuard","makeRowHandle","expectNone","expectAtLeast","n","assertGuardCount","expectAtMost","kind","Error","TxPlanError","attachGuard","fn","Number","isInteger","String","refuseFragment","caller","table","where","isSqlFragment","Error","k","v","Object","entries","branch","Array","isArray","addDecimal","cell","by","sign","undefined","a","String","b","parse","x","m","exec","trim","frac","unit","BigInt","scale","length","pa","pb","Math","max","lift","total","Number","toString","neg","digits","padStart","out","slice","rowMatchesFilter","row","f","every","c","some","matchesCell","cmp","op","l","Date","getTime","r","key","cond","isColRef","$col","isNowExpr","toISOString","includes","isColumnExpr","toLowerCase","startsWith","endsWith","createMockDB","store","Map","tracked","inserted","updated","deleted","rowsOf","rows","get","set","track","map","list","push","resolveInsertValues","data","expr","columnExprOf","fn","ops","updateMany","keys","assertUsableFilter","assertUsableWriteValues","hit","filter","deleteMany","keep","count","search","_table","_params","facets","similar","recommend","supersede","_id","id","crypto","randomUUID","query","_sql","insert","raw","assertNoExpressionHandles","record","insertMany","cols","i","missing","extra","join","rawRow","lockRows","ids","unique","Set","sort","advisoryXactLock","_key","claim","keyCols","existing","find","put","rawData","opts","onConflict","update","idx","findIndex","current","applied","delete","splice","findById","findMany","orderSpecs","orderBy","o","dir","direction","column","y","xNull","yNull","nullsFirst","nulls","page","limit","select","fromEntries","txPlan","plan","snapshot","trackedSnapshot","cloneTracked","results","result","applyOp","failure","guardFailure","guard","err","clear","values","resolveMap","conflict","value","rows_affected","created","written","matches","next","removed","indexOf","found","client","attempt","asService","seed","resolveValue","tagged","$ref","txRejection","message","field","$expr","base","ok","kind","n","slot","status","code","error_code","fakeDatabase","mock","createMockDB","queries","db","Object","assign","create","getPrototypeOf","query","sql","params","push","seed","table","rows","inserted","updated","deleted"]}
|