@cipherstash/stack 0.19.0 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +596 -0
- package/README.md +376 -276
- package/dist/adapter-kit.cjs +1002 -0
- package/dist/adapter-kit.cjs.map +1 -0
- package/dist/adapter-kit.d.cts +120 -0
- package/dist/adapter-kit.d.ts +120 -0
- package/dist/adapter-kit.js +122 -0
- package/dist/adapter-kit.js.map +1 -0
- package/dist/base-operation-AOAIvsSB.d.cts +32 -0
- package/dist/base-operation-FXEzUXIq.d.ts +32 -0
- package/dist/{chunk-4AVL4VZD.js → chunk-3B5ZX3IS.js} +3 -1
- package/dist/chunk-3B5ZX3IS.js.map +1 -0
- package/dist/{chunk-U66S7VIF.js → chunk-6SGN52W6.js} +166 -107
- package/dist/chunk-6SGN52W6.js.map +1 -0
- package/dist/chunk-7333ZC6L.js +48 -0
- package/dist/chunk-7333ZC6L.js.map +1 -0
- package/dist/{chunk-MP3SSDNN.js → chunk-CLM7E4I6.js} +15 -15
- package/dist/{chunk-MP3SSDNN.js.map → chunk-CLM7E4I6.js.map} +1 -1
- package/dist/{chunk-OFQ555AX.js → chunk-IDKP6ABU.js} +2 -2
- package/dist/{chunk-36AA7IBJ.js → chunk-L7ISHSG7.js} +53 -20
- package/dist/chunk-L7ISHSG7.js.map +1 -0
- package/dist/{chunk-LBMC4D6D.js → chunk-NVKK7UDN.js} +1 -1
- package/dist/chunk-NVKK7UDN.js.map +1 -0
- package/dist/chunk-X3JRXEIB.js +98 -0
- package/dist/chunk-X3JRXEIB.js.map +1 -0
- package/dist/client.cjs +29 -12
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +3 -2
- package/dist/client.d.ts +3 -2
- package/dist/client.js +2 -2
- package/dist/{table-CIH7jZ2h.d.ts → columns-0lbT9stl.d.ts} +157 -138
- package/dist/{table-DihEAlxG.d.cts → columns-Bxv7Oo9o.d.cts} +157 -138
- package/dist/dynamodb/index.d.cts +3 -2
- package/dist/dynamodb/index.d.ts +3 -2
- package/dist/encryption/index.cjs +54 -16
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +807 -6
- package/dist/encryption/index.d.ts +807 -6
- package/dist/encryption/index.js +6 -6
- package/dist/encryption/v3.cjs +1078 -973
- package/dist/encryption/v3.cjs.map +1 -1
- package/dist/encryption/v3.d.cts +15 -12
- package/dist/encryption/v3.d.ts +15 -12
- package/dist/encryption/v3.js +50 -22
- package/dist/encryption/v3.js.map +1 -1
- package/dist/eql/v3/index.cjs +130 -79
- package/dist/eql/v3/index.cjs.map +1 -1
- package/dist/eql/v3/index.d.cts +115 -13
- package/dist/eql/v3/index.d.ts +115 -13
- package/dist/eql/v3/index.js +16 -6
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +5 -5
- package/dist/errors/index.d.ts +5 -5
- package/dist/errors/index.js +1 -1
- package/dist/identity/index.cjs.map +1 -1
- package/dist/identity/index.js +2 -2
- package/dist/index-BquA71_Y.d.ts +24 -0
- package/dist/index-fhWTOV0K.d.cts +24 -0
- package/dist/index.cjs +79 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -17
- package/dist/index.d.ts +5 -17
- package/dist/index.js +6 -6
- package/dist/schema/index.cjs +29 -12
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.cts +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +2 -2
- package/dist/{types-public-CpS5KjwX.d.ts → types-public-QMjYNfQO.d.cts} +765 -726
- package/dist/{types-public-CpS5KjwX.d.cts → types-public-QMjYNfQO.d.ts} +765 -726
- package/dist/types-public.cjs.map +1 -1
- package/dist/types-public.d.cts +1 -1
- package/dist/types-public.d.ts +1 -1
- package/dist/types-public.js +1 -1
- package/dist/wasm-inline.d.ts +779 -405
- package/dist/wasm-inline.js +606 -299
- package/dist/wasm-inline.js.map +1 -1
- package/package.json +25 -53
- package/dist/chunk-36AA7IBJ.js.map +0 -1
- package/dist/chunk-4AVL4VZD.js.map +0 -1
- package/dist/chunk-IADZCZEA.js +0 -23
- package/dist/chunk-IADZCZEA.js.map +0 -1
- package/dist/chunk-IBSK6P33.js +0 -209
- package/dist/chunk-IBSK6P33.js.map +0 -1
- package/dist/chunk-LBMC4D6D.js.map +0 -1
- package/dist/chunk-U66S7VIF.js.map +0 -1
- package/dist/client-DSGHBN-g.d.cts +0 -834
- package/dist/client-DfCrlHXh.d.ts +0 -834
- package/dist/drizzle/index.cjs +0 -5617
- package/dist/drizzle/index.cjs.map +0 -1
- package/dist/drizzle/index.d.cts +0 -358
- package/dist/drizzle/index.d.ts +0 -358
- package/dist/drizzle/index.js +0 -1220
- package/dist/drizzle/index.js.map +0 -1
- package/dist/supabase/index.cjs +0 -5951
- package/dist/supabase/index.cjs.map +0 -1
- package/dist/supabase/index.d.cts +0 -223
- package/dist/supabase/index.d.ts +0 -223
- package/dist/supabase/index.js +0 -1217
- package/dist/supabase/index.js.map +0 -1
- /package/dist/{chunk-OFQ555AX.js.map → chunk-IDKP6ABU.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapter-kit.ts","../src/encryption/helpers/index.ts","../src/schema/match-defaults.ts","../src/eql/v3/columns.ts","../src/eql/v3/types.ts","../src/eql/v3/domain-registry.ts","../src/eql/v3/selector-path.ts","../../../node_modules/.pnpm/evlog@1.11.0_next@15.5.18_react-dom@19.2.3_react@19.2.3__react@19.2.3__react@19.2.3/node_modules/evlog/src/utils.ts","../../../node_modules/.pnpm/evlog@1.11.0_next@15.5.18_react-dom@19.2.3_react@19.2.3__react@19.2.3__react@19.2.3/node_modules/evlog/src/logger.ts","../src/utils/logger/index.ts"],"sourcesContent":["/**\n * Internal support surface for FIRST-PARTY adapter packages\n * (`@cipherstash/stack-supabase`, `@cipherstash/stack-drizzle`).\n *\n * These symbols were previously reached through deep `@/` internal imports while\n * the Supabase and Drizzle adapters lived INSIDE this package. Splitting them into\n * their own packages (see issue #627) means those imports must resolve across a\n * package boundary — so exactly the symbols the adapters consume are re-exported\n * here, from one narrow, versioned entry point, rather than leaking six internal\n * module paths into the public surface.\n *\n * NOT a general-purpose public API. This is the seam between core and the\n * first-party adapters; treat it as `@internal`-adjacent. Anything an END USER\n * should reach has a dedicated subpath (`./schema`, `./eql/v3`, `./types`,\n * `./identity`, `./errors`). If a symbol here graduates to genuine public use,\n * promote it to one of those instead of widening this file's contract.\n *\n * The full member list is small on purpose; every addition is an audit decision.\n */\n\n// Model → encrypted-PG-composite helpers used by the v2 Supabase mutation path.\nexport {\n bulkModelsToEncryptedPgComposites,\n modelToEncryptedPgComposites,\n} from './encryption/helpers/index.js'\n\n// Audit config carried by chainable operations.\nexport type { AuditConfig } from './encryption/operations/base-operation.js'\n\n// v3 column model + the date-like cast set the Supabase builder uses to\n// reconstruct `Date` values from PostgREST select aliases.\nexport {\n type AnyEncryptedV3Column,\n DATE_LIKE_CASTS,\n EncryptedV3Column,\n} from './eql/v3/columns.js'\n\n// Domain registry: the Supabase adapter classifies introspected columns by their\n// Postgres domain and rebuilds each column's encryption config from it.\nexport {\n DOMAIN_REGISTRY,\n factoryForDomain,\n stripDomainSchema,\n} from './eql/v3/domain-registry.js'\n\n// Shared JSONPath-selector path handling (parse/validate, needle-document\n// reconstruction, scalar-leaf guard) for encrypted-JSON querying — used by the\n// Drizzle selector operators (#651) and the Supabase selector filters (#650) so\n// the validation rules cannot drift between adapters.\nexport {\n jsonPathOf,\n parseSelectorSegments,\n reconstructSelectorDocument,\n unsupportedLeafReason,\n} from './eql/v3/selector-path.js'\n\n// Shared match-index guard (short-needle rejection), reused by both adapters.\nexport { matchNeedleError } from './schema/match-defaults.js'\n// Shared structured logger (adapters log rejections through the same instance).\nexport { logger } from './utils/logger/index.js'\n","import type {\n Encrypted as CipherStashEncrypted,\n EncryptedQuery as CipherStashEncryptedQuery,\n EncryptedV3Query as CipherStashEncryptedV3Query,\n KeysetIdentifier as KeysetIdentifierFfi,\n} from '@cipherstash/protect-ffi'\nimport type { Encrypted, EncryptedQueryResult, KeysetIdentifier } from '@/types'\n\n/**\n * The shape `encryptQuery` / `encryptQueryBulk` can return: a full storage\n * payload (returned for v2 `ste_vec_term` containment queries), a query-only\n * payload with no ciphertext (scalar `unique`/`match`/`ore` lookups and\n * `ste_vec_selector` path queries), or — under `eqlVersion: 3` — the\n * `eql_v3.jsonb_query` containment needle.\n *\n * TODO: duplicated in `@cipherstash/protect` — see\n * `packages/protect/src/helpers/index.ts`. Both copies should be removed once\n * `@cipherstash/protect-ffi` exports a named alias for the `encryptQuery`\n * return type (https://github.com/cipherstash/stack/pull/473).\n */\ntype EncryptedQueryTerm =\n | CipherStashEncrypted\n | CipherStashEncryptedQuery\n | CipherStashEncryptedV3Query\n\nexport type EncryptedPgComposite = {\n data: Encrypted\n}\n\n/**\n * Helper function to transform an encrypted payload into a PostgreSQL composite type.\n * Use this when inserting data via Supabase or similar clients.\n */\nexport function encryptedToPgComposite(obj: Encrypted): EncryptedPgComposite {\n return {\n data: obj,\n }\n}\n\n/**\n * Helper function to transform an encrypted payload into a PostgreSQL composite literal string.\n * Use this when querying with `.eq()` or similar equality operations in Supabase.\n *\n * @example\n * ```typescript\n * const literal = encryptedToCompositeLiteral(encrypted)\n * await supabase.from('table').select().eq('column', literal)\n * ```\n */\nexport function encryptedToCompositeLiteral(obj: EncryptedQueryTerm): string {\n return `(${JSON.stringify(JSON.stringify(obj))})`\n}\n\n/**\n * Helper function to transform an encrypted payload into an escaped PostgreSQL composite literal string.\n * Use this when you need the composite literal format to be escaped as a string value.\n *\n * @example\n * ```typescript\n * const escapedLiteral = encryptedToEscapedCompositeLiteral(encrypted)\n * ```\n */\nexport function encryptedToEscapedCompositeLiteral(\n obj: EncryptedQueryTerm,\n): string {\n return JSON.stringify(encryptedToCompositeLiteral(obj))\n}\n\n/**\n * Format an encrypted result based on the requested return type.\n *\n * - `'composite-literal'` → PostgreSQL composite literal string `(\"json\")`\n * - `'escaped-composite-literal'` → escaped variant `\"(\\\"json\\\")\"`\n * - default (`'eql'` or omitted) → raw encrypted object\n */\nexport function formatEncryptedResult(\n encrypted: EncryptedQueryTerm,\n returnType?: string,\n): EncryptedQueryResult {\n if (returnType === 'composite-literal') {\n return encryptedToCompositeLiteral(encrypted)\n }\n if (returnType === 'escaped-composite-literal') {\n return encryptedToEscapedCompositeLiteral(encrypted)\n }\n return encrypted\n}\n\n/**\n * Helper function to transform a model's encrypted fields into PostgreSQL composite types\n */\nexport function modelToEncryptedPgComposites<T extends Record<string, unknown>>(\n model: T,\n): T {\n const result: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(model)) {\n if (isEncryptedPayload(value)) {\n result[key] = encryptedToPgComposite(value)\n } else {\n result[key] = value\n }\n }\n\n return result as T\n}\n\n/**\n * Helper function to transform multiple models' encrypted fields into PostgreSQL composite types\n */\nexport function bulkModelsToEncryptedPgComposites<\n T extends Record<string, unknown>,\n>(models: T[]): T[] {\n return models.map((model) => modelToEncryptedPgComposites(model))\n}\n\nexport function toFfiKeysetIdentifier(\n keyset: KeysetIdentifier | undefined,\n): KeysetIdentifierFfi | undefined {\n if (!keyset) return undefined\n\n if ('name' in keyset) {\n return { Name: keyset.name }\n }\n\n return { Uuid: keyset.id }\n}\n\n/**\n * Helper function to check if a value is an encrypted payload\n */\nexport function isEncryptedPayload(value: unknown): value is Encrypted {\n if (value === null) return false\n if (typeof value !== 'object') return false\n\n const obj = value as Record<string, unknown>\n\n // Must have version field (number)\n if (!('v' in obj) || typeof obj.v !== 'number') return false\n\n // Must have index field (object)\n if (!('i' in obj) || typeof obj.i !== 'object') return false\n\n // Must have either ciphertext (c) or searchable vector (sv)\n if (!('c' in obj) && !('sv' in obj)) return false\n\n return true\n}\n\nexport {\n buildNestedObject,\n parseJsonbPath,\n toJsonPath,\n} from './jsonb'\n","import type { MatchIndexOpts } from './index'\n\n/**\n * Fully-resolved match-index options: every field present and non-`undefined`.\n *\n * `MatchIndexOpts` (the user-facing tuning input) has all fields optional —\n * each is `.default(...).optional()` in the zod schema, so its inferred type is\n * `T | undefined`. This type pins the BUILT/resolved shape explicitly via\n * `NonNullable<...>`, which states the non-null intent directly and is robust\n * regardless of `Required<>`'s subtle, `exactOptionalPropertyTypes`-dependent\n * stripping semantics.\n */\nexport type BuiltMatchIndexOpts = {\n tokenizer: NonNullable<MatchIndexOpts['tokenizer']>\n token_filters: NonNullable<MatchIndexOpts['token_filters']>\n k: NonNullable<MatchIndexOpts['k']>\n m: NonNullable<MatchIndexOpts['m']>\n include_original: NonNullable<MatchIndexOpts['include_original']>\n}\n\n/**\n * Default match-index parameters — the single source of truth shared by the\n * v2 `freeTextSearch()` builder and the v3 domain builders.\n *\n * `include_original: true` is the v2 builder's historical default rather than\n * the zod-schema default of `false`. It is INERT: protect-ffi accepts the flag\n * and ignores it (measured across 0.24 and 0.29, EQL v2 and v3 — the emitted\n * bloom is trigram-only either way). It is kept here only so the v2 emitted\n * config does not move. The v3 domain builders override it to `false`, the\n * value a substring-search domain wants if ffi ever starts honouring it.\n *\n * In particular it does NOT let a value shorter than the tokenizer's\n * `token_length` be matched: such a value blooms to nothing under either\n * setting, which is exactly the fail-open {@link matchNeedleError} guards.\n *\n * This is a FACTORY (not a shared `const`) so every caller gets fresh, unaliased\n * nested objects (`tokenizer`, `token_filters` and the `{ kind: 'downcase' }`\n * inside it). A shared const would be shallow-copied by `{ ...DEFAULT }`, leaving\n * those nested objects aliased across every column — a caller mutating one built\n * config could then corrupt the defaults used by later columns.\n */\nexport function defaultMatchOpts(): BuiltMatchIndexOpts {\n return {\n tokenizer: { kind: 'ngram', token_length: 3 },\n token_filters: [{ kind: 'downcase' }],\n k: 6,\n m: 2048,\n include_original: true,\n }\n}\n\n/**\n * Deep-clone a built match block (`tokenizer` and `token_filters` are its only\n * nested values) so no emitted config or stored builder state ever aliases\n * another's nested objects — a caller mutating one cannot corrupt the other.\n */\nexport function cloneMatchOpts(opts: BuiltMatchIndexOpts): BuiltMatchIndexOpts {\n return {\n ...opts,\n tokenizer: { ...opts.tokenizer },\n token_filters: opts.token_filters.map((f) => ({ ...f })),\n }\n}\n\n/**\n * Resolve user-supplied `freeTextSearch(opts)` input into a fully-built match\n * block: each provided key replaces its default, omitted keys keep the default\n * (`opts?.x ?? default.x`). The single source of truth for that five-field merge\n * shared by the v2 `freeTextSearch()` builder and the v3 domain builders.\n *\n * The result is deep-cloned ({@link cloneMatchOpts}) so a caller mutating their\n * own `opts` object (or its nested `tokenizer`/`token_filters`) after this call\n * can never leak into the stored builder state or the emitted config — clone-on-\n * write for both builders, not just v3.\n */\nexport function resolveMatchOpts(opts?: MatchIndexOpts): BuiltMatchIndexOpts {\n const defaults = defaultMatchOpts()\n return cloneMatchOpts({\n tokenizer: opts?.tokenizer ?? defaults.tokenizer,\n token_filters: opts?.token_filters ?? defaults.token_filters,\n k: opts?.k ?? defaults.k,\n m: opts?.m ?? defaults.m,\n include_original: opts?.include_original ?? defaults.include_original,\n })\n}\n\n/**\n * The shortest needle a match index can answer, or `undefined` when its\n * tokenizer imposes no floor (`standard` splits on word boundaries, so any\n * non-empty needle yields at least one token).\n *\n * Accepts the loose {@link MatchIndexOpts} because a `ColumnSchema`'s built\n * `indexes.match` is typed from the zod schema, where `tokenizer` is optional.\n * An absent tokenizer resolves to the same default the schema itself applies\n * (`ngram`, `token_length: 3`) rather than skipping the floor — skipping would\n * reintroduce the fail-open this guard exists to close.\n */\nexport function matchNeedleMinLength(opts: MatchIndexOpts): number | undefined {\n const tokenizer = opts.tokenizer ?? defaultMatchOpts().tokenizer\n return tokenizer.kind === 'ngram' ? tokenizer.token_length : undefined\n}\n\n/**\n * Why a needle cannot be answered by this match index, or `undefined` when it\n * can. Callers throw their own error type with this as the reason.\n *\n * A needle that tokenizes to NOTHING has an empty bloom filter, and\n * `stored_bf @> '{}'` is true for every row (\"contains nothing, contained by\n * everything\"). Such a query is unanswerable rather than merely unmatched, so\n * it must fail loudly instead of silently returning the whole table. Two ways\n * to tokenize to nothing:\n *\n * - the empty needle, under ANY tokenizer;\n * - a needle shorter than the ngram tokenizer's `token_length`.\n *\n * The ngram floor counts Unicode CODEPOINTS, because that is what the tokenizer\n * counts. `needle.length` would count UTF-16 code units and wave through an\n * astral-plane needle: `'👍👍'` is 4 code units but only 2 codepoints, yields no\n * trigram, and (measured live) matched every row. Graphemes are the wrong unit\n * in the other direction — NFD `'éé'` is 4 codepoints but 2 grapheme clusters,\n * and does yield trigrams.\n *\n * Currently wired ONLY into the v3 Drizzle adapter (`eql/v3/drizzle/operators`).\n * It lives here, beside the shared match defaults, because v2 builds the same\n * bloom filters and needs the same floor — but v2's `like`/`ilike` path remains\n * unguarded and still fails open. Do not reuse this for v2 without first\n * measuring what its tokenizer actually receives: v2 needles carry SQL wildcards\n * (`'%ada%'`), so the floor may have to apply to the unwrapped term rather than\n * to the string the caller passed.\n */\nexport function matchNeedleError(\n needle: unknown,\n opts: MatchIndexOpts,\n): string | undefined {\n if (typeof needle !== 'string') return undefined\n\n // Codepoints, not UTF-16 code units — see above.\n const length = [...needle].length\n const min = matchNeedleMinLength(opts)\n\n if (length === 0) {\n return `free-text search needs a non-empty search term; an empty term produces no tokens and would match every row.`\n }\n if (min === undefined || length >= min) return undefined\n return `free-text search needs at least ${min} characters (the index tokenizer's token_length), but the search term ${JSON.stringify(needle)} has ${length}. A shorter term produces no tokens and would match every row.`\n}\n","import type { ColumnSchema } from '@/schema'\nimport { defaultMatchOpts } from '@/schema/match-defaults'\n\n/**\n * The query capabilities a v3 concrete domain exposes. These are SDK-facing\n * semantic flags describing what kinds of query terms a column can produce —\n * NOT the raw EQL index keys. They are metadata only and never emitted by\n * `build()`.\n *\n * - `equality`: exact-match lookups (EQL `hm`, or comparison via `ob`).\n * - `orderAndRange`: comparison / range lookups (EQL `op`, or `ob` on `_ord_ore` domains).\n * - `freeTextSearch`: tokenised substring match (EQL `bf`).\n * - `searchableJson`: encrypted-JSONB containment / selector lookups (EQL\n * `ste_vec`). Optional and mutually exclusive with the scalar flags — a\n * `public.eql_v3_json` document is queried by structure, not by scalar term.\n */\nexport type QueryCapabilities = Readonly<{\n equality: boolean\n orderAndRange: boolean\n freeTextSearch: boolean\n searchableJson?: boolean\n}>\n\n/**\n * The `cast_as` kinds whose decrypted plaintext reconstructs to a JS `Date`.\n *\n * SINGLE SOURCE OF TRUTH for the date-like set. Both the type-level\n * {@link PlaintextFromKind} and the runtime `rowReconstructor` (encryption/v3.ts)\n * derive their \"reconstructs to `Date`\" decision from this array, so the next\n * `Date`-backed cast is added in exactly one place — never hand-synced across a\n * type and a runtime guard that could silently drift.\n *\n * (`timestamp` reconstructs to `Date` just like `date`, but its `cast_as` tells\n * the FFI not to truncate the time-of-day.)\n */\nexport const DATE_LIKE_CASTS = ['date', 'timestamp'] as const\n/** A `cast_as` kind that reconstructs to `Date` — see {@link DATE_LIKE_CASTS}. */\nexport type DateLikeCast = (typeof DATE_LIKE_CASTS)[number]\n\n/** The plaintext (TypeScript) kind a v3 domain decrypts to. A subset of the\n * SDK `CastAs` enum, restricted to the scalar kinds v3 domains actually use. */\ntype PlaintextKind =\n | 'string'\n | 'number'\n | 'bigint'\n | 'boolean'\n | 'json'\n | DateLikeCast\n\n/**\n * The full, literal definition of a v3 domain. This is the LOAD-BEARING type:\n * the base column class carries a private field of this type so that every\n * concrete (otherwise-empty) subclass is discriminated by its literal\n * `eqlType`/`castAs`/`capabilities` — TypeScript empty subclasses are NOT\n * nominal, so without this a storage-only `boolean` column would be assignable to\n * a storage-only `date` column and plaintext inference would collapse.\n */\ntype V3DomainDefinition = Readonly<{\n eqlType: `public.${string}`\n castAs: PlaintextKind\n capabilities: QueryCapabilities\n}>\n\n/** Type-level mirror of {@link isQueryableCapabilities}: `false` for a\n * storage-only domain (all capability flags `false`), `true` otherwise. */\ntype QueryableFlag<D extends V3DomainDefinition> = D['capabilities'] extends {\n equality: false\n orderAndRange: false\n freeTextSearch: false\n searchableJson?: false | undefined\n}\n ? false\n : true\n\nconst STORAGE_ONLY = {\n equality: false,\n orderAndRange: false,\n freeTextSearch: false,\n} as const\n\nconst EQUALITY_ONLY = {\n equality: true,\n orderAndRange: false,\n freeTextSearch: false,\n} as const\n\nconst ORDER_AND_RANGE = {\n equality: true,\n orderAndRange: true,\n freeTextSearch: false,\n} as const\n\nconst MATCH_ONLY = {\n equality: false,\n orderAndRange: false,\n freeTextSearch: true,\n} as const\n\nconst TEXT_SEARCH = {\n equality: true,\n orderAndRange: true,\n freeTextSearch: true,\n} as const\n\n/**\n * The concrete EQL v3 domain name for a full-capability text column.\n * Recorded as metadata for future DDL / query-dialect increments; it is\n * intentionally absent from the emitted encrypt config.\n */\nexport const TEXT_SEARCH_EQL_TYPE = 'public.eql_v3_text_search'\n\n// Per-domain literal definitions. Each concrete column subclass is parameterised\n// by `typeof <CONST>`; the literal `eqlType`/`castAs`/`capabilities` on each is\n// what makes the otherwise-empty subclasses nominally distinct (see\n// V3DomainDefinition). Order mirrors eql-bindings `CATALOG` order.\n//\n// Exported for the `types` namespace factory (see ./types); they are internal\n// building blocks and are intentionally NOT re-exported from the public barrel.\nexport const INTEGER = {\n eqlType: 'public.eql_v3_integer',\n castAs: 'number',\n capabilities: STORAGE_ONLY,\n} as const\nexport const INTEGER_EQ = {\n eqlType: 'public.eql_v3_integer_eq',\n castAs: 'number',\n capabilities: EQUALITY_ONLY,\n} as const\nexport const INTEGER_ORD_ORE = {\n eqlType: 'public.eql_v3_integer_ord_ore',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\nexport const INTEGER_ORD = {\n eqlType: 'public.eql_v3_integer_ord',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\n\nexport const SMALLINT = {\n eqlType: 'public.eql_v3_smallint',\n castAs: 'number',\n capabilities: STORAGE_ONLY,\n} as const\nexport const SMALLINT_EQ = {\n eqlType: 'public.eql_v3_smallint_eq',\n castAs: 'number',\n capabilities: EQUALITY_ONLY,\n} as const\nexport const SMALLINT_ORD_ORE = {\n eqlType: 'public.eql_v3_smallint_ord_ore',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\nexport const SMALLINT_ORD = {\n eqlType: 'public.eql_v3_smallint_ord',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\n\n// bigint (int8) domains. Plaintext is a JS `bigint` (always decrypts to\n// `bigint`); bounds are the full i64 range. protect-ffi round-trips an in-range\n// native bigint losslessly, and values outside the signed 64-bit range are\n// rejected client-side by `assertValidNumericValue` before they reach the FFI\n// (`*_ord_ope` variants are out of scope — CIP-3403.)\nexport const BIGINT = {\n eqlType: 'public.eql_v3_bigint',\n castAs: 'bigint',\n capabilities: STORAGE_ONLY,\n} as const\nexport const BIGINT_EQ = {\n eqlType: 'public.eql_v3_bigint_eq',\n castAs: 'bigint',\n capabilities: EQUALITY_ONLY,\n} as const\nexport const BIGINT_ORD_ORE = {\n eqlType: 'public.eql_v3_bigint_ord_ore',\n castAs: 'bigint',\n capabilities: ORDER_AND_RANGE,\n} as const\nexport const BIGINT_ORD = {\n eqlType: 'public.eql_v3_bigint_ord',\n castAs: 'bigint',\n capabilities: ORDER_AND_RANGE,\n} as const\n\nexport const DATE = {\n eqlType: 'public.eql_v3_date',\n castAs: 'date',\n capabilities: STORAGE_ONLY,\n} as const\nexport const DATE_EQ = {\n eqlType: 'public.eql_v3_date_eq',\n castAs: 'date',\n capabilities: EQUALITY_ONLY,\n} as const\nexport const DATE_ORD_ORE = {\n eqlType: 'public.eql_v3_date_ord_ore',\n castAs: 'date',\n capabilities: ORDER_AND_RANGE,\n} as const\nexport const DATE_ORD = {\n eqlType: 'public.eql_v3_date_ord',\n castAs: 'date',\n capabilities: ORDER_AND_RANGE,\n} as const\n\nexport const TIMESTAMP = {\n eqlType: 'public.eql_v3_timestamp',\n castAs: 'timestamp',\n capabilities: STORAGE_ONLY,\n} as const\nexport const TIMESTAMP_EQ = {\n eqlType: 'public.eql_v3_timestamp_eq',\n castAs: 'timestamp',\n capabilities: EQUALITY_ONLY,\n} as const\nexport const TIMESTAMP_ORD_ORE = {\n eqlType: 'public.eql_v3_timestamp_ord_ore',\n castAs: 'timestamp',\n capabilities: ORDER_AND_RANGE,\n} as const\nexport const TIMESTAMP_ORD = {\n eqlType: 'public.eql_v3_timestamp_ord',\n castAs: 'timestamp',\n capabilities: ORDER_AND_RANGE,\n} as const\n\nexport const NUMERIC = {\n eqlType: 'public.eql_v3_numeric',\n castAs: 'number',\n capabilities: STORAGE_ONLY,\n} as const\nexport const NUMERIC_EQ = {\n eqlType: 'public.eql_v3_numeric_eq',\n castAs: 'number',\n capabilities: EQUALITY_ONLY,\n} as const\nexport const NUMERIC_ORD_ORE = {\n eqlType: 'public.eql_v3_numeric_ord_ore',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\nexport const NUMERIC_ORD = {\n eqlType: 'public.eql_v3_numeric_ord',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\n\nexport const TEXT = {\n eqlType: 'public.eql_v3_text',\n castAs: 'string',\n capabilities: STORAGE_ONLY,\n} as const\nexport const TEXT_EQ = {\n eqlType: 'public.eql_v3_text_eq',\n castAs: 'string',\n capabilities: EQUALITY_ONLY,\n} as const\nexport const TEXT_MATCH = {\n eqlType: 'public.eql_v3_text_match',\n castAs: 'string',\n capabilities: MATCH_ONLY,\n} as const\nexport const TEXT_ORD_ORE = {\n eqlType: 'public.eql_v3_text_ord_ore',\n castAs: 'string',\n capabilities: ORDER_AND_RANGE,\n} as const\nexport const TEXT_ORD = {\n eqlType: 'public.eql_v3_text_ord',\n castAs: 'string',\n capabilities: ORDER_AND_RANGE,\n} as const\n\nexport const BOOLEAN = {\n eqlType: 'public.eql_v3_boolean',\n castAs: 'boolean',\n capabilities: STORAGE_ONLY,\n} as const\n\nexport const REAL = {\n eqlType: 'public.eql_v3_real',\n castAs: 'number',\n capabilities: STORAGE_ONLY,\n} as const\nexport const REAL_EQ = {\n eqlType: 'public.eql_v3_real_eq',\n castAs: 'number',\n capabilities: EQUALITY_ONLY,\n} as const\nexport const REAL_ORD_ORE = {\n eqlType: 'public.eql_v3_real_ord_ore',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\nexport const REAL_ORD = {\n eqlType: 'public.eql_v3_real_ord',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\n\nexport const DOUBLE = {\n eqlType: 'public.eql_v3_double',\n castAs: 'number',\n capabilities: STORAGE_ONLY,\n} as const\nexport const DOUBLE_EQ = {\n eqlType: 'public.eql_v3_double_eq',\n castAs: 'number',\n capabilities: EQUALITY_ONLY,\n} as const\nexport const DOUBLE_ORD_ORE = {\n eqlType: 'public.eql_v3_double_ord_ore',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\nexport const DOUBLE_ORD = {\n eqlType: 'public.eql_v3_double_ord',\n castAs: 'number',\n capabilities: ORDER_AND_RANGE,\n} as const\n\n/**\n * Translate a domain's semantic {@link QueryCapabilities} (plus its plaintext\n * `castAs`, which decides how equality is answered, and its ordering flavour)\n * into the concrete EQL index block emitted by `build()`.\n *\n * - `unique` (the `hm` HMAC index) whenever equality is answered via HMAC:\n * equality-only domains of ANY type, AND text order domains. Text equality is\n * HMAC-based — the `public.eql_v3_text_ord` / `public.eql_v3_text_ord_ore` SQL domains\n * REQUIRE `hm` in the stored ciphertext (their `eql_v3.eq_term` extracts it).\n * - ONE ordering index for any order/range domain, selected by the domain's\n * name (eql-3.0.0): plain `_ord` domains (and `text_search`) are OPE-backed —\n * `ope`, the `op` CLLW-OPE term — while `_ord_ore` domains keep block-ORE —\n * `ore`, the `ob` term. The two terms are not cross-comparable, and each\n * domain's SQL CHECK requires exactly its own. For numeric/date order\n * domains the ordering term also answers equality (via the SQL `=`\n * operator), so those emit no `hm`. Text order domains emit BOTH `unique`\n * and the ordering index.\n * - `match` (the `bf` bloom-filter index) for free-text search, deep-cloned from\n * the per-call defaults so no nested object is ever shared across columns, and\n * emitted with `include_original: false` (see below).\n */\nfunction indexesForCapabilities(\n capabilities: QueryCapabilities,\n castAs: PlaintextKind,\n ordering: 'ope' | 'ore',\n): ColumnSchema['indexes'] {\n const indexes: ColumnSchema['indexes'] = {}\n\n // Text equality is always HMAC-based, so a text order domain (`string` +\n // order/range) still needs `unique`; numeric/date order domains answer\n // equality via their ordering term and must NOT emit `unique`.\n if (\n capabilities.equality &&\n (!capabilities.orderAndRange || castAs === 'string')\n ) {\n indexes.unique = { token_filters: [] }\n }\n\n if (capabilities.orderAndRange) {\n indexes[ordering] = {}\n }\n\n if (capabilities.freeTextSearch) {\n // The factory returns fresh, unaliased nested objects per call, so no\n // column's emitted match block ever shares state with another's.\n //\n // `include_original` is overridden off the v2 builder's `true`. protect-ffi\n // ignores the flag entirely (measured across 0.24 and 0.29, EQL v2 and v3:\n // the emitted bloom is trigram-only either way, and a value shorter than\n // `token_length` blooms to nothing regardless), so this is inert today. It\n // is set to the value a substring-search domain actually wants — matching\n // the zod schema's own default — so that a protect-ffi release which starts\n // honouring the flag cannot silently break `contains`.\n indexes.match = { ...defaultMatchOpts(), include_original: false }\n }\n\n if (capabilities.searchableJson) {\n // Encrypted-JSONB index (the protect-ffi config kind is named `ste_vec`).\n // `prefix: 'enabled'` is a sentinel the table's `build()` rewrites to\n // `${tableName}/${columnName}` — the same scheme v2 `searchableJson` uses —\n // so each document's selectors are scoped to their column.\n //\n // `array_index_mode` indexes array elements by identity (`item`) and enables\n // `$[*]` wildcard selectors, but deliberately drops `position`: `@>`\n // containment must be a subset test (jsonb `@>` semantics), so `{roles:['x']}`\n // matches any document whose `roles` array contains `x` REGARDLESS of index.\n // With positional terms emitted (v2 `searchableJson`'s `'all'`), the needle\n // for `x`-at-index-0 would miss a document holding `x` at index 1.\n //\n // `mode: 'compat'` is REQUIRED for eql-3.0.0: `public.eql_v3_json` orders the\n // document's entries by the CLLW-OPE `op` term, so the index must emit `op`\n // (compat) terms. `'standard'` emits v2's CLLW-ORE `oc` terms, which the v3\n // domain rejects at encrypt time.\n indexes.ste_vec = {\n prefix: 'enabled',\n array_index_mode: { item: true, wildcard: true, position: false },\n mode: 'compat',\n }\n }\n\n return indexes\n}\n\n/**\n * The ordering flavour a domain's name pins (eql-3.0.0): `_ord_ore` domains\n * are block-ORE (`ore` index, `ob` term); every other ordering domain —\n * `_ord` and `text_search` — is CLLW-OPE (`ope` index, `op` term).\n */\nfunction orderingForEqlType(eqlType: string): 'ope' | 'ore' {\n return eqlType.endsWith('_ord_ore') ? 'ore' : 'ope'\n}\n\n/** Whether a domain's capabilities make it queryable at all (any flag set). */\nfunction isQueryableCapabilities(capabilities: QueryCapabilities): boolean {\n return (\n capabilities.equality ||\n capabilities.orderAndRange ||\n capabilities.freeTextSearch ||\n (capabilities.searchableJson ?? false)\n )\n}\n\n/**\n * Shared base for every v3 concrete domain column. Parameterised by the FULL\n * literal {@link V3DomainDefinition} (not by capabilities alone): the private\n * `definition` field carries the literal `eqlType`/`castAs`/`capabilities`, so\n * two otherwise-empty subclasses (e.g. `EncryptedBooleanColumn` and\n * `EncryptedDateColumn`, both storage-only) are NOT mutually assignable. This\n * nominality is what keeps plaintext inference precise.\n */\nexport class EncryptedV3Column<D extends V3DomainDefinition> {\n constructor(\n private readonly columnName: string,\n private readonly definition: D,\n ) {}\n\n getName(): string {\n return this.columnName\n }\n\n /** The concrete EQL v3 domain name. Metadata only; not emitted by `build()`. */\n getEqlType(): D['eqlType'] {\n return this.definition.eqlType\n }\n\n /** The semantic query capabilities this domain exposes. Metadata only. */\n getQueryCapabilities(): D['capabilities'] {\n return this.definition.capabilities\n }\n\n /** `true` when this domain can produce at least one kind of query term. */\n isQueryable(): QueryableFlag<D> {\n return isQueryableCapabilities(\n this.definition.capabilities,\n ) as QueryableFlag<D>\n }\n\n /** Emit the encrypt-config column: `cast_as` plus capability-derived indexes. */\n build(): ColumnSchema {\n return {\n cast_as: this.definition.castAs,\n indexes: indexesForCapabilities(\n this.definition.capabilities,\n this.definition.castAs,\n orderingForEqlType(this.definition.eqlType),\n ),\n }\n }\n}\n\nconst TEXT_SEARCH_DOMAIN = {\n eqlType: TEXT_SEARCH_EQL_TYPE,\n castAs: 'string',\n capabilities: TEXT_SEARCH,\n} as const\n\n/**\n * Builder for a `public.eql_v3_text_search` column.\n *\n * The concrete type inherently enables equality + order/range + free-text\n * match — there are no capability-enabling or tuning methods. The match index\n * is always emitted with the default configuration.\n *\n * NOTE — querying: a `text_search` column emits all three indexes (`unique`,\n * `ope`, `match`), and the shared index-inference picks them by fixed priority\n * `unique > match > ordering`. So `encryptQuery(value, { column, table })` with NO\n * explicit `queryType` builds an EQUALITY term (via `unique`), NOT a free-text\n * match — a substring like `'joh'` then matches nothing. To run a free-text\n * match query you MUST pass `queryType: 'freeTextSearch'`:\n *\n * ```typescript\n * // equality (default): exact value only\n * client.encryptQuery('john@example.com', { column: users.email, table: users })\n * // free-text match: substring/token search\n * client.encryptQuery('joh', { column: users.email, table: users, queryType: 'freeTextSearch' })\n * ```\n */\nexport class EncryptedTextSearchColumn extends EncryptedV3Column<\n typeof TEXT_SEARCH_DOMAIN\n> {\n constructor(columnName: string) {\n super(columnName, TEXT_SEARCH_DOMAIN)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Concrete domain columns\n//\n// Every concrete class is an empty subclass parameterised by its literal domain\n// definition (see EncryptedV3Column). The `types` namespace (see ./types)\n// constructs these with the SAME literal constant so the instance's private\n// `definition` field carries full literal type data — that is what keeps\n// distinct domains nominally incompatible.\n// ---------------------------------------------------------------------------\n\n// integer\nexport class EncryptedIntegerColumn extends EncryptedV3Column<typeof INTEGER> {}\nexport class EncryptedIntegerEqColumn extends EncryptedV3Column<\n typeof INTEGER_EQ\n> {}\nexport class EncryptedIntegerOrdOreColumn extends EncryptedV3Column<\n typeof INTEGER_ORD_ORE\n> {}\nexport class EncryptedIntegerOrdColumn extends EncryptedV3Column<\n typeof INTEGER_ORD\n> {}\n\n// smallint\nexport class EncryptedSmallintColumn extends EncryptedV3Column<\n typeof SMALLINT\n> {}\nexport class EncryptedSmallintEqColumn extends EncryptedV3Column<\n typeof SMALLINT_EQ\n> {}\nexport class EncryptedSmallintOrdOreColumn extends EncryptedV3Column<\n typeof SMALLINT_ORD_ORE\n> {}\nexport class EncryptedSmallintOrdColumn extends EncryptedV3Column<\n typeof SMALLINT_ORD\n> {}\n\n// bigint (int8) — plaintext is a JS `bigint`, round-tripped losslessly by the\n// native protect-ffi boundary (see the BIGINT domain definitions above).\nexport class EncryptedBigintColumn extends EncryptedV3Column<typeof BIGINT> {}\nexport class EncryptedBigintEqColumn extends EncryptedV3Column<\n typeof BIGINT_EQ\n> {}\nexport class EncryptedBigintOrdOreColumn extends EncryptedV3Column<\n typeof BIGINT_ORD_ORE\n> {}\nexport class EncryptedBigintOrdColumn extends EncryptedV3Column<\n typeof BIGINT_ORD\n> {}\n\n// date\nexport class EncryptedDateColumn extends EncryptedV3Column<typeof DATE> {}\nexport class EncryptedDateEqColumn extends EncryptedV3Column<typeof DATE_EQ> {}\nexport class EncryptedDateOrdOreColumn extends EncryptedV3Column<\n typeof DATE_ORD_ORE\n> {}\nexport class EncryptedDateOrdColumn extends EncryptedV3Column<\n typeof DATE_ORD\n> {}\n\n// timestamp\nexport class EncryptedTimestampColumn extends EncryptedV3Column<\n typeof TIMESTAMP\n> {}\nexport class EncryptedTimestampEqColumn extends EncryptedV3Column<\n typeof TIMESTAMP_EQ\n> {}\nexport class EncryptedTimestampOrdOreColumn extends EncryptedV3Column<\n typeof TIMESTAMP_ORD_ORE\n> {}\nexport class EncryptedTimestampOrdColumn extends EncryptedV3Column<\n typeof TIMESTAMP_ORD\n> {}\n\n// numeric\nexport class EncryptedNumericColumn extends EncryptedV3Column<typeof NUMERIC> {}\nexport class EncryptedNumericEqColumn extends EncryptedV3Column<\n typeof NUMERIC_EQ\n> {}\nexport class EncryptedNumericOrdOreColumn extends EncryptedV3Column<\n typeof NUMERIC_ORD_ORE\n> {}\nexport class EncryptedNumericOrdColumn extends EncryptedV3Column<\n typeof NUMERIC_ORD\n> {}\n\n// text (text_search is defined above with its match-tuning override)\nexport class EncryptedTextColumn extends EncryptedV3Column<typeof TEXT> {}\nexport class EncryptedTextEqColumn extends EncryptedV3Column<typeof TEXT_EQ> {}\nexport class EncryptedTextMatchColumn extends EncryptedV3Column<\n typeof TEXT_MATCH\n> {}\nexport class EncryptedTextOrdOreColumn extends EncryptedV3Column<\n typeof TEXT_ORD_ORE\n> {}\nexport class EncryptedTextOrdColumn extends EncryptedV3Column<\n typeof TEXT_ORD\n> {}\n\n// boolean\nexport class EncryptedBooleanColumn extends EncryptedV3Column<typeof BOOLEAN> {}\n\n// real\nexport class EncryptedRealColumn extends EncryptedV3Column<typeof REAL> {}\nexport class EncryptedRealEqColumn extends EncryptedV3Column<typeof REAL_EQ> {}\nexport class EncryptedRealOrdOreColumn extends EncryptedV3Column<\n typeof REAL_ORD_ORE\n> {}\nexport class EncryptedRealOrdColumn extends EncryptedV3Column<\n typeof REAL_ORD\n> {}\n\n// double\nexport class EncryptedDoubleColumn extends EncryptedV3Column<typeof DOUBLE> {}\nexport class EncryptedDoubleEqColumn extends EncryptedV3Column<\n typeof DOUBLE_EQ\n> {}\nexport class EncryptedDoubleOrdOreColumn extends EncryptedV3Column<\n typeof DOUBLE_ORD_ORE\n> {}\nexport class EncryptedDoubleOrdColumn extends EncryptedV3Column<\n typeof DOUBLE_ORD\n> {}\n\n// json\nconst JSON_DOMAIN = {\n eqlType: 'public.eql_v3_json',\n castAs: 'json',\n capabilities: {\n equality: false,\n orderAndRange: false,\n freeTextSearch: false,\n searchableJson: true,\n },\n} as const\n\n/**\n * Builder for a `public.eql_v3_json` column — an encrypted JSONB document\n * (a {@link JsonDocument}: object, array, or null). It round-trips through\n * `encrypt`/`decrypt`, and containment queries use the encrypted-JSONB index\n * emitted by `build()`. (The stored payload is protect-ffi's `SteVecDocument`,\n * and the config index kind is `ste_vec` — both protect-ffi names.)\n */\nexport class EncryptedJsonColumn extends EncryptedV3Column<typeof JSON_DOMAIN> {\n constructor(columnName: string) {\n super(columnName, JSON_DOMAIN)\n }\n}\n\n/**\n * Union of every v3 concrete column type. Used as the value type for v3 table\n * columns so a table may mix any generated domains.\n */\nexport type AnyEncryptedV3Column =\n | EncryptedIntegerColumn\n | EncryptedIntegerEqColumn\n | EncryptedIntegerOrdOreColumn\n | EncryptedIntegerOrdColumn\n | EncryptedSmallintColumn\n | EncryptedSmallintEqColumn\n | EncryptedSmallintOrdOreColumn\n | EncryptedSmallintOrdColumn\n | EncryptedBigintColumn\n | EncryptedBigintEqColumn\n | EncryptedBigintOrdOreColumn\n | EncryptedBigintOrdColumn\n | EncryptedDateColumn\n | EncryptedDateEqColumn\n | EncryptedDateOrdOreColumn\n | EncryptedDateOrdColumn\n | EncryptedTimestampColumn\n | EncryptedTimestampEqColumn\n | EncryptedTimestampOrdOreColumn\n | EncryptedTimestampOrdColumn\n | EncryptedNumericColumn\n | EncryptedNumericEqColumn\n | EncryptedNumericOrdOreColumn\n | EncryptedNumericOrdColumn\n | EncryptedTextColumn\n | EncryptedTextEqColumn\n | EncryptedTextMatchColumn\n | EncryptedTextOrdOreColumn\n | EncryptedTextOrdColumn\n | EncryptedTextSearchColumn\n | EncryptedBooleanColumn\n | EncryptedRealColumn\n | EncryptedRealEqColumn\n | EncryptedRealOrdOreColumn\n | EncryptedRealOrdColumn\n | EncryptedDoubleColumn\n | EncryptedDoubleEqColumn\n | EncryptedDoubleOrdOreColumn\n | EncryptedDoubleOrdColumn\n | EncryptedJsonColumn\n\n/**\n * A factory that builds a concrete v3 column for a given DB column name.\n *\n * Declared here rather than beside `DOMAIN_REGISTRY` so that `./types` can\n * constrain its own export against it without importing from `./domain-registry`,\n * which imports `./types` back as a value.\n */\nexport type V3ColumnFactory = (name: string) => AnyEncryptedV3Column\n\n/**\n * Shape of v3 table columns: every value is a v3 concrete column builder.\n * (Nested fields are deferred to later increments.)\n */\nexport type EncryptedV3TableColumn = {\n [key: string]: AnyEncryptedV3Column\n}\n\n/** A JSON value at any depth. Used for the values NESTED inside a document. */\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue }\n\n/**\n * The plaintext a `public.eql_v3_json` column encrypts and reconstructs: a JSON\n * DOCUMENT — an object, an array, or `null`. NOT a bare top-level scalar:\n * protect-ffi's `json` cast rejects a top-level string/number/boolean\n * (\"Cannot convert … to Json\"), because a scalar belongs in a scalar domain\n * (`types.TextEq`, `types.IntegerEq`, …). Nested scalars are ordinary\n * {@link JsonValue}s and are fully supported.\n */\nexport type JsonDocument = null | JsonValue[] | { [key: string]: JsonValue }\n\n/** Map a domain's {@link PlaintextKind} to its TypeScript plaintext type. */\ntype PlaintextFromKind<K extends PlaintextKind> = K extends 'string'\n ? string\n : K extends 'number'\n ? number\n : K extends 'bigint'\n ? bigint\n : K extends 'boolean'\n ? boolean\n : K extends DateLikeCast\n ? Date\n : K extends 'json'\n ? JsonDocument\n : never\n\n/**\n * The plaintext type for a single v3 column, read from the literal domain\n * definition carried on the base class's private field. This is stable across\n * empty subclasses that share the same base generic — a subclass-name\n * conditional would collapse because those subclasses are structurally\n * assignable to one another.\n */\nexport type PlaintextForColumn<C> =\n C extends EncryptedV3Column<infer D> ? PlaintextFromKind<D['castAs']> : never\n\n/**\n * The user-facing `queryType` names a v3 column supports, derived 1:1 from its\n * capability flags. Resolves to `never` for a storage-only column (all flags\n * `false`) and for any non-v3 value. The names mirror the {@link QueryCapabilities}\n * keys, each of which is also a {@link import('@/types').QueryTypeName} member.\n */\nexport type QueryTypesForColumn<C> =\n C extends EncryptedV3Column<infer D>\n ?\n | (D['capabilities']['equality'] extends true ? 'equality' : never)\n | (D['capabilities']['orderAndRange'] extends true\n ? 'orderAndRange'\n : never)\n | (D['capabilities']['freeTextSearch'] extends true\n ? 'freeTextSearch'\n : never)\n // The flag is optional (absent means \"not a JSON document column\"), so\n // only a literal `true` contributes. Without this arm a `types.Json`\n // column resolved to `never`, and every typed adapter key set derived\n // from this type excluded encrypted-JSON columns entirely (#650).\n | (D['capabilities']['searchableJson'] extends true\n ? 'searchableJson'\n : never)\n : never\n\n/**\n * The concrete EQL v3 type string for a single column, read from the literal\n * domain definition carried on the base class's private field (mirrors\n * {@link PlaintextForColumn}). Distributes over a union of columns, so\n * `EqlTypeForColumn<AnyEncryptedV3Column>` yields the union of every domain's\n * `eqlType` — the canonical, source-of-truth key set for a type-driven test\n * matrix keyed by domain.\n */\nexport type EqlTypeForColumn<C> =\n C extends EncryptedV3Column<infer D> ? D['eqlType'] : never\n","import {\n BIGINT,\n BIGINT_EQ,\n BIGINT_ORD,\n BIGINT_ORD_ORE,\n BOOLEAN,\n DATE,\n DATE_EQ,\n DATE_ORD,\n DATE_ORD_ORE,\n DOUBLE,\n DOUBLE_EQ,\n DOUBLE_ORD,\n DOUBLE_ORD_ORE,\n EncryptedBigintColumn,\n EncryptedBigintEqColumn,\n EncryptedBigintOrdColumn,\n EncryptedBigintOrdOreColumn,\n EncryptedBooleanColumn,\n EncryptedDateColumn,\n EncryptedDateEqColumn,\n EncryptedDateOrdColumn,\n EncryptedDateOrdOreColumn,\n EncryptedDoubleColumn,\n EncryptedDoubleEqColumn,\n EncryptedDoubleOrdColumn,\n EncryptedDoubleOrdOreColumn,\n EncryptedIntegerColumn,\n EncryptedIntegerEqColumn,\n EncryptedIntegerOrdColumn,\n EncryptedIntegerOrdOreColumn,\n EncryptedJsonColumn,\n EncryptedNumericColumn,\n EncryptedNumericEqColumn,\n EncryptedNumericOrdColumn,\n EncryptedNumericOrdOreColumn,\n EncryptedRealColumn,\n EncryptedRealEqColumn,\n EncryptedRealOrdColumn,\n EncryptedRealOrdOreColumn,\n EncryptedSmallintColumn,\n EncryptedSmallintEqColumn,\n EncryptedSmallintOrdColumn,\n EncryptedSmallintOrdOreColumn,\n EncryptedTextColumn,\n EncryptedTextEqColumn,\n EncryptedTextMatchColumn,\n EncryptedTextOrdColumn,\n EncryptedTextOrdOreColumn,\n EncryptedTextSearchColumn,\n EncryptedTimestampColumn,\n EncryptedTimestampEqColumn,\n EncryptedTimestampOrdColumn,\n EncryptedTimestampOrdOreColumn,\n INTEGER,\n INTEGER_EQ,\n INTEGER_ORD,\n INTEGER_ORD_ORE,\n NUMERIC,\n NUMERIC_EQ,\n NUMERIC_ORD,\n NUMERIC_ORD_ORE,\n REAL,\n REAL_EQ,\n REAL_ORD,\n REAL_ORD_ORE,\n SMALLINT,\n SMALLINT_EQ,\n SMALLINT_ORD,\n SMALLINT_ORD_ORE,\n TEXT,\n TEXT_EQ,\n TEXT_MATCH,\n TEXT_ORD,\n TEXT_ORD_ORE,\n TIMESTAMP,\n TIMESTAMP_EQ,\n TIMESTAMP_ORD,\n TIMESTAMP_ORD_ORE,\n type V3ColumnFactory,\n} from './columns'\n\n/**\n * The v3 column-type namespace. Each member is a public, semantic factory name\n * for a concrete EQL v3 column. Most members mirror the underlying domain name;\n * JS-friendly scalar names map to Postgres concrete domains, e.g.\n * `types.IntegerOrd` builds a `public.eql_v3_integer_ord` column and `types.Boolean`\n * builds a `public.eql_v3_boolean` column.\n *\n * If these factory names stop matching the emitted capability/domain surface,\n * the stack package is out of sync with the underlying EQL build. That is a\n * version mismatch, not a harmless naming drift.\n *\n * Each factory returns the CONCRETE column class instance (never the widened\n * `AnyEncryptedV3Column`) so per-column plaintext / query-capability inference\n * stays precise.\n *\n * ```ts\n * import { encryptedTable, types } from '@cipherstash/stack/eql/v3'\n *\n * const events = encryptedTable('events', {\n * actor: types.TextEq('actor'), // equality\n * weight: types.IntegerOrd('weight'), // order + range\n * createdAt: types.Timestamp('created_at'), // storage only\n * })\n * ```\n *\n * `types.TextSearch` is fully described by its type — like every other domain,\n * it has no capability-bearing or tuning chain. Its match index is always\n * emitted with the default configuration; run a free-text query by passing\n * `queryType: 'freeTextSearch'` to `encryptQuery`.\n */\nexport const types = {\n // integer\n Integer: (name: string) => new EncryptedIntegerColumn(name, INTEGER),\n IntegerEq: (name: string) => new EncryptedIntegerEqColumn(name, INTEGER_EQ),\n IntegerOrdOre: (name: string) =>\n new EncryptedIntegerOrdOreColumn(name, INTEGER_ORD_ORE),\n IntegerOrd: (name: string) =>\n new EncryptedIntegerOrdColumn(name, INTEGER_ORD),\n\n // smallint\n Smallint: (name: string) => new EncryptedSmallintColumn(name, SMALLINT),\n SmallintEq: (name: string) =>\n new EncryptedSmallintEqColumn(name, SMALLINT_EQ),\n SmallintOrdOre: (name: string) =>\n new EncryptedSmallintOrdOreColumn(name, SMALLINT_ORD_ORE),\n SmallintOrd: (name: string) =>\n new EncryptedSmallintOrdColumn(name, SMALLINT_ORD),\n\n // bigint (int8) — plaintext is a JS `bigint`, round-tripped losslessly by\n // the native protect-ffi boundary (see ./columns)\n Bigint: (name: string) => new EncryptedBigintColumn(name, BIGINT),\n BigintEq: (name: string) => new EncryptedBigintEqColumn(name, BIGINT_EQ),\n BigintOrdOre: (name: string) =>\n new EncryptedBigintOrdOreColumn(name, BIGINT_ORD_ORE),\n BigintOrd: (name: string) => new EncryptedBigintOrdColumn(name, BIGINT_ORD),\n\n // date\n Date: (name: string) => new EncryptedDateColumn(name, DATE),\n DateEq: (name: string) => new EncryptedDateEqColumn(name, DATE_EQ),\n DateOrdOre: (name: string) =>\n new EncryptedDateOrdOreColumn(name, DATE_ORD_ORE),\n DateOrd: (name: string) => new EncryptedDateOrdColumn(name, DATE_ORD),\n\n // timestamp\n Timestamp: (name: string) => new EncryptedTimestampColumn(name, TIMESTAMP),\n TimestampEq: (name: string) =>\n new EncryptedTimestampEqColumn(name, TIMESTAMP_EQ),\n TimestampOrdOre: (name: string) =>\n new EncryptedTimestampOrdOreColumn(name, TIMESTAMP_ORD_ORE),\n TimestampOrd: (name: string) =>\n new EncryptedTimestampOrdColumn(name, TIMESTAMP_ORD),\n\n // numeric\n Numeric: (name: string) => new EncryptedNumericColumn(name, NUMERIC),\n NumericEq: (name: string) => new EncryptedNumericEqColumn(name, NUMERIC_EQ),\n NumericOrdOre: (name: string) =>\n new EncryptedNumericOrdOreColumn(name, NUMERIC_ORD_ORE),\n NumericOrd: (name: string) =>\n new EncryptedNumericOrdColumn(name, NUMERIC_ORD),\n\n // text\n Text: (name: string) => new EncryptedTextColumn(name, TEXT),\n TextEq: (name: string) => new EncryptedTextEqColumn(name, TEXT_EQ),\n TextMatch: (name: string) => new EncryptedTextMatchColumn(name, TEXT_MATCH),\n TextOrdOre: (name: string) =>\n new EncryptedTextOrdOreColumn(name, TEXT_ORD_ORE),\n TextOrd: (name: string) => new EncryptedTextOrdColumn(name, TEXT_ORD),\n TextSearch: (name: string) => new EncryptedTextSearchColumn(name),\n\n // boolean\n Boolean: (name: string) => new EncryptedBooleanColumn(name, BOOLEAN),\n\n // real\n Real: (name: string) => new EncryptedRealColumn(name, REAL),\n RealEq: (name: string) => new EncryptedRealEqColumn(name, REAL_EQ),\n RealOrdOre: (name: string) =>\n new EncryptedRealOrdOreColumn(name, REAL_ORD_ORE),\n RealOrd: (name: string) => new EncryptedRealOrdColumn(name, REAL_ORD),\n\n // double\n Double: (name: string) => new EncryptedDoubleColumn(name, DOUBLE),\n DoubleEq: (name: string) => new EncryptedDoubleEqColumn(name, DOUBLE_EQ),\n DoubleOrdOre: (name: string) =>\n new EncryptedDoubleOrdOreColumn(name, DOUBLE_ORD_ORE),\n DoubleOrd: (name: string) => new EncryptedDoubleOrdColumn(name, DOUBLE_ORD),\n\n // json (encrypted JSONB document, ste_vec containment)\n Json: (name: string) => new EncryptedJsonColumn(name),\n // `satisfies` is load-bearing, not decoration: `DOMAIN_REGISTRY` derives itself\n // by calling every value here at module load. A non-factory export would throw\n // during module evaluation and take the supabase introspect/schema-build/verify\n // path down with it. This turns that into a compile error at the offending line.\n // `as const` applies first, so literal key inference is preserved.\n} as const satisfies Record<string, V3ColumnFactory>\n","import type { V3ColumnFactory } from './columns'\nimport { types } from './types'\n\nexport type { V3ColumnFactory } from './columns'\n\n/**\n * Unqualified Postgres `domain_name` → the `eql/v3/types.ts` factory that\n * builds that domain's column.\n *\n * DERIVED, not hand-listed. Every factory already carries its domain in the\n * constant it passes to the column constructor, so the key is recoverable from\n * the factory itself — and `types` and this registry cannot drift. Adding a\n * domain to `types` is enough; forgetting an entry here is no longer possible.\n *\n * The keys are an external contract (they are the `information_schema` query\n * parameter in `../../supabase/introspect.ts`). Because they are derived from\n * `getEqlType()`, no test that also derives them can detect a corrupted domain\n * constant — `eql-v3-domain-registry.test.ts` pins them against a hand-written\n * literal list for exactly that reason.\n *\n * `factory('_probe')` runs at module load, so a non-factory value in `types`\n * would throw here and take the importing modules (`../../supabase/introspect.ts`,\n * `schema-builder.ts`, `verify.ts`) down with it. `types` is declared\n * `satisfies Record<string, V3ColumnFactory>` so that mistake is a compile error\n * at its source instead. Do not reintroduce a cast on `Object.values` below: it\n * would silence exactly the check that keeps this loop total.\n */\n// NULL PROTOTYPE — load-bearing. A plain object literal inherits from\n// Object.prototype, so `DOMAIN_REGISTRY['constructor']` returns a *function*\n// and passes a truthiness check. A column whose domain is named `constructor`,\n// `toString`, `valueOf` or `__proto__` would then be treated as a modelled EQL\n// domain and \"synthesized\" from Object.prototype.constructor. `factoryForDomain`\n// additionally guards with `Object.hasOwn`; both are kept — belt and braces,\n// because a future refactor that drops the null prototype must not silently\n// reopen the hole. It also makes the `Object.hasOwn` collision check below\n// exact: on a plain object `'constructor' in registry` is spuriously true.\nexport const DOMAIN_REGISTRY: Record<string, V3ColumnFactory> = (() => {\n const registry = Object.create(null) as Record<string, V3ColumnFactory>\n for (const factory of Object.values(types)) {\n // Probe name only: the constructors store their arguments and nothing else,\n // so building one column per domain at module load is free of side effects.\n const key = stripDomainSchema(factory('_probe').getEqlType())\n if (Object.hasOwn(registry, key)) {\n throw new Error(`duplicate EQL v3 domain key: ${key}`)\n }\n registry[key] = factory\n }\n return registry\n})()\n\n/** Strip a leading `public.` schema qualifier from a qualified `eqlType`. */\nexport function stripDomainSchema(eqlType: string): string {\n return eqlType.startsWith('public.')\n ? eqlType.slice('public.'.length)\n : eqlType\n}\n\n/**\n * Look up the factory for an unqualified domain name, or `undefined`.\n *\n * `Object.hasOwn` is required, not decorative: without it a domain named\n * `constructor` / `toString` / `valueOf` / `__proto__` resolves to an inherited\n * `Object.prototype` member and violates the \"unknown domain = plaintext\" rule.\n */\nexport function factoryForDomain(\n domainName: string,\n): V3ColumnFactory | undefined {\n return Object.hasOwn(DOMAIN_REGISTRY, domainName)\n ? DOMAIN_REGISTRY[domainName]\n : undefined\n}\n","/**\n * Shared JSONPath-selector path handling for the first-party adapters\n * (`@cipherstash/stack-drizzle`, `@cipherstash/stack-supabase`), exposed via\n * `@cipherstash/stack/adapter-kit`.\n *\n * Both adapters express \"compare the value at `$.a.b`\" over an encrypted\n * `eql_v3_json` column, and both need the same three pieces: parse + validate\n * the dot-notation path, reconstruct the `{ a: { b: value } }` needle document\n * whose ste_vec entry at the path carries the comparison terms, and reject\n * non-scalar leaves up front. Originally private to the Drizzle v3 operators\n * (#651); moved here when the Supabase adapter grew the same querying (#650) so\n * the validation rules cannot drift between adapters.\n */\n\n/**\n * Object keys that are prototype-pollution vectors — rejected outright (mirrors\n * core's `FORBIDDEN_KEYS`), so a selector can never address them.\n */\nconst FORBIDDEN_SEGMENTS: ReadonlySet<string> = new Set([\n '__proto__',\n 'prototype',\n 'constructor',\n])\n\n/**\n * Parse a dot-notation JSONPath into its object-key segments. Rejects, each with\n * a clear message: array-index/wildcard syntax (v1 is object-keys-only), the\n * empty/root path, malformed paths (`..`, stray/leading/trailing dots, so we\n * never silently query a *different* path), and prototype-pollution keys.\n * `'$.a.b'` / `' a.b '` → `['a', 'b']`.\n */\nexport function parseSelectorSegments(path: string): string[] {\n const trimmed = path.trim()\n let body = trimmed.startsWith('$') ? trimmed.slice(1) : trimmed\n if (body.startsWith('.')) body = body.slice(1)\n if (body === '') {\n throw new Error(\n `JSON selector path \"${path}\" addresses no field — use e.g. \"$.a\" or \"$.a.b\".`,\n )\n }\n if (/[[\\]*]/.test(body)) {\n throw new Error(\n `JSON selector path \"${path}\" uses array/wildcard syntax, which is not yet supported — use dot-notation object keys (e.g. \"$.a.b\").`,\n )\n }\n const segments = body.split('.')\n for (const segment of segments) {\n if (segment === '') {\n throw new Error(\n `JSON selector path \"${path}\" is malformed (empty segment / \"..\" / stray dot) — use dot-notation object keys (e.g. \"$.a.b\").`,\n )\n }\n // Leading/trailing whitespace in a segment is almost certainly spacing\n // around a dot ('$.user .role'), which would silently address a DIFFERENT\n // key ('user '). Interior whitespace stays legal — 'a b' is a valid JSON\n // key a caller may genuinely have.\n if (segment !== segment.trim()) {\n throw new Error(\n `JSON selector path \"${path}\" has whitespace at a segment boundary (\"${segment}\") — it would address a different key. Remove the spacing around the dot.`,\n )\n }\n if (FORBIDDEN_SEGMENTS.has(segment)) {\n throw new Error(\n `JSON selector path \"${path}\" addresses the forbidden key \"${segment}\".`,\n )\n }\n }\n return segments\n}\n\n/** `$`-rooted JSONPath for `encryptQuery`'s selector needle. */\nexport function jsonPathOf(segments: string[]): string {\n return `$.${segments.join('.')}`\n}\n\n/**\n * A selector compares a single scalar LEAF. Returns a reason string when `value`\n * is unsupported — a non-scalar (object/array → that's `contains`), or a boolean\n * under an ordering operator (no ordering term) — else `null`. Callers raise it\n * as their adapter's operator error with column context, so a bad value is an\n * actionable SDK error rather than a deferred, opaque DB failure.\n */\nexport function unsupportedLeafReason(\n value: unknown,\n ordering: boolean,\n): string | null {\n // Explicit null arm: `typeof null === 'object'`, so without it a null leaf\n // would get the actively-wrong \"got an object — use contains()\" steer.\n if (value == null) {\n return 'a selector compares a non-null scalar leaf, but got null/undefined — SQL NULL never equals anything; use is(column, null) for null checks.'\n }\n const isScalar =\n value instanceof Date ||\n typeof value === 'number' ||\n typeof value === 'bigint' ||\n typeof value === 'string' ||\n typeof value === 'boolean'\n if (!isScalar) {\n return `a selector compares a scalar leaf, but got ${Array.isArray(value) ? 'an array' : 'an object'} — use contains() for sub-object matching.`\n }\n if (ordering && typeof value === 'boolean') {\n return 'a boolean leaf has no ordering — use eq/ne (or contains()).'\n }\n return null\n}\n\n/**\n * Nest `value` under the segments: `['a','b']` → `{ a: { b: value } }`. The\n * storage-needle document whose ste_vec entry at the path supplies the\n * ciphertext-bearing comparison entry.\n */\nexport function reconstructSelectorDocument(\n segments: string[],\n value: unknown,\n): Record<string, unknown> {\n // Null-prototype objects: a segment like `__proto__` must become an OWN key,\n // not invoke the prototype setter (which would drop it and mis-serialize the\n // needle). JSON.stringify ignores the [[Prototype]], so this serializes fine.\n const root: Record<string, unknown> = Object.create(null)\n let cursor = root\n segments.forEach((segment, index) => {\n if (index === segments.length - 1) {\n cursor[segment] = value\n } else {\n const next: Record<string, unknown> = Object.create(null)\n cursor[segment] = next\n cursor = next\n }\n })\n return root\n}\n","import type { EnvironmentContext, LogLevel } from './types'\n\nexport function formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${Math.round(ms)}ms`\n }\n return `${(ms / 1000).toFixed(2)}s`\n}\n\nexport function isServer(): boolean {\n return typeof window === 'undefined'\n}\n\nexport function isClient(): boolean {\n return typeof window !== 'undefined'\n}\n\nexport function isDev(): boolean {\n if (typeof process !== 'undefined') {\n return process.env.NODE_ENV !== 'production'\n }\n if (typeof window !== 'undefined') {\n return true\n }\n return false\n}\n\nexport function detectEnvironment(): Partial<EnvironmentContext> {\n const env = typeof process !== 'undefined' ? process.env : {}\n const defaultEnvironment = isDev() ? 'development' : 'production'\n\n return {\n environment: env.NODE_ENV || defaultEnvironment,\n service: env.SERVICE_NAME || 'app',\n version: env.APP_VERSION,\n commitHash: env.COMMIT_SHA\n || env.GITHUB_SHA\n || env.VERCEL_GIT_COMMIT_SHA\n || env.CF_PAGES_COMMIT_SHA,\n region: env.VERCEL_REGION\n || env.AWS_REGION\n || env.FLY_REGION\n || env.CF_REGION,\n }\n}\n\nexport function getConsoleMethod(level: LogLevel): LogLevel {\n return level\n}\n\nexport const colors = {\n reset: '\\x1B[0m',\n bold: '\\x1B[1m',\n dim: '\\x1B[2m',\n red: '\\x1B[31m',\n green: '\\x1B[32m',\n yellow: '\\x1B[33m',\n blue: '\\x1B[34m',\n magenta: '\\x1B[35m',\n cyan: '\\x1B[36m',\n white: '\\x1B[37m',\n gray: '\\x1B[90m',\n} as const\n\nexport function getLevelColor(level: string): string {\n switch (level) {\n case 'error':\n return colors.red\n case 'warn':\n return colors.yellow\n case 'info':\n return colors.cyan\n case 'debug':\n return colors.gray\n default:\n return colors.white\n }\n}\n\n/** Headers that should never be passed to hooks for security */\nexport const SENSITIVE_HEADERS = [\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'x-auth-token',\n 'proxy-authorization',\n]\n\nexport function filterSafeHeaders(headers: Record<string, string>): Record<string, string> {\n const safeHeaders: Record<string, string> = {}\n\n for (const [key, value] of Object.entries(headers)) {\n if (!SENSITIVE_HEADERS.includes(key.toLowerCase())) {\n safeHeaders[key] = value\n }\n }\n\n return safeHeaders\n}\n\n/**\n * Match a path against a glob pattern.\n * Supports * (any chars except /) and ** (any chars including /).\n */\nexport function matchesPattern(path: string, pattern: string): boolean {\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // Escape special regex chars except * and ?\n .replace(/\\*\\*/g, '{{GLOBSTAR}}') // Temp placeholder for **\n .replace(/\\*/g, '[^/]*') // * matches anything except /\n .replace(/{{GLOBSTAR}}/g, '.*') // ** matches anything including /\n .replace(/\\?/g, '[^/]') // ? matches single char except /\n\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(path)\n}\n","import type { DrainContext, EnvironmentContext, FieldContext, Log, LogLevel, LoggerConfig, RequestLogger, RequestLoggerOptions, SamplingConfig, TailSamplingContext, WideEvent } from './types'\nimport { colors, detectEnvironment, formatDuration, getConsoleMethod, getLevelColor, isDev, matchesPattern } from './utils'\n\nfunction isPlainObject(val: unknown): val is Record<string, unknown> {\n return val !== null && typeof val === 'object' && !Array.isArray(val)\n}\n\nfunction deepDefaults(base: Record<string, unknown>, defaults: Record<string, unknown>): Record<string, unknown> {\n const result = { ...base }\n for (const key in defaults) {\n const baseVal = result[key]\n const defaultVal = defaults[key]\n if (baseVal === undefined || baseVal === null) {\n result[key] = defaultVal\n } else if (isPlainObject(baseVal) && isPlainObject(defaultVal)) {\n result[key] = deepDefaults(baseVal, defaultVal)\n }\n }\n return result\n}\n\nlet globalEnv: EnvironmentContext = {\n service: 'app',\n environment: 'development',\n}\n\nlet globalPretty = isDev()\nlet globalSampling: SamplingConfig = {}\nlet globalStringify = true\nlet globalDrain: ((ctx: DrainContext) => void | Promise<void>) | undefined\nlet globalEnabled = true\n\n/**\n * Initialize the logger with configuration.\n * Call this once at application startup.\n */\nexport function initLogger(config: LoggerConfig = {}): void {\n globalEnabled = config.enabled ?? true\n const detected = detectEnvironment()\n\n globalEnv = {\n service: config.env?.service ?? detected.service ?? 'app',\n environment: config.env?.environment ?? detected.environment ?? 'development',\n version: config.env?.version ?? detected.version,\n commitHash: config.env?.commitHash ?? detected.commitHash,\n region: config.env?.region ?? detected.region,\n }\n\n globalPretty = config.pretty ?? isDev()\n globalSampling = config.sampling ?? {}\n globalStringify = config.stringify ?? true\n globalDrain = config.drain\n}\n\n/**\n * Check if logging is globally enabled.\n */\nexport function isEnabled(): boolean {\n return globalEnabled\n}\n\n/**\n * Determine if a log at the given level should be emitted based on sampling config.\n * Error level defaults to 100% (always logged) unless explicitly configured otherwise.\n */\nfunction shouldSample(level: LogLevel): boolean {\n const { rates } = globalSampling\n if (!rates) {\n return true // No sampling configured, log everything\n }\n\n // Error defaults to 100% unless explicitly set\n const percentage = level === 'error' && rates.error === undefined\n ? 100\n : rates[level] ?? 100\n\n // 0% = never log, 100% = always log\n if (percentage <= 0) return false\n if (percentage >= 100) return true\n\n return Math.random() * 100 < percentage\n}\n\n/**\n * Evaluate tail sampling conditions to determine if a log should be force-kept.\n * Returns true if ANY condition matches (OR logic).\n */\nexport function shouldKeep(ctx: TailSamplingContext): boolean {\n const { keep } = globalSampling\n if (!keep?.length) return false\n\n return keep.some((condition) => {\n if (condition.status !== undefined && ctx.status !== undefined && ctx.status >= condition.status) {\n return true\n }\n if (condition.duration !== undefined && ctx.duration !== undefined && ctx.duration >= condition.duration) {\n return true\n }\n if (condition.path && ctx.path && matchesPattern(ctx.path, condition.path)) {\n return true\n }\n return false\n })\n}\n\nfunction emitWideEvent(level: LogLevel, event: Record<string, unknown>, skipSamplingCheck = false): WideEvent | null {\n if (!globalEnabled) return null\n\n if (!skipSamplingCheck && !shouldSample(level)) {\n return null\n }\n\n const formatted: WideEvent = {\n timestamp: new Date().toISOString(),\n level,\n ...globalEnv,\n ...event,\n }\n\n if (globalPretty) {\n prettyPrintWideEvent(formatted)\n } else if (globalStringify) {\n console[getConsoleMethod(level)](JSON.stringify(formatted))\n } else {\n console[getConsoleMethod(level)](formatted)\n }\n\n if (globalDrain) {\n Promise.resolve(globalDrain({ event: formatted })).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n }\n\n return formatted\n}\n\nfunction emitTaggedLog(level: LogLevel, tag: string, message: string): void {\n if (!globalEnabled) return\n\n if (globalPretty) {\n if (!shouldSample(level)) {\n return\n }\n const color = getLevelColor(level)\n const timestamp = new Date().toISOString().slice(11, 23)\n console.log(`${colors.dim}${timestamp}${colors.reset} ${color}[${tag}]${colors.reset} ${message}`)\n return\n }\n emitWideEvent(level, { tag, message })\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return String(value)\n }\n if (typeof value === 'object') {\n // Flatten object to key=value pairs\n const pairs: string[] = []\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v !== undefined && v !== null) {\n if (typeof v === 'object') {\n // For nested objects, show as JSON\n pairs.push(`${k}=${JSON.stringify(v)}`)\n } else {\n pairs.push(`${k}=${v}`)\n }\n }\n }\n return pairs.join(' ')\n }\n return String(value)\n}\n\nfunction prettyPrintWideEvent(event: Record<string, unknown>): void {\n const { timestamp, level, service, environment, version, ...rest } = event\n const levelColor = getLevelColor(level as string)\n const ts = (timestamp as string).slice(11, 23)\n\n let header = `${colors.dim}${ts}${colors.reset} ${levelColor}${(level as string).toUpperCase()}${colors.reset}`\n header += ` ${colors.cyan}[${service}]${colors.reset}`\n\n if (rest.method && rest.path) {\n header += ` ${rest.method} ${rest.path}`\n delete rest.method\n delete rest.path\n }\n\n if (rest.status) {\n const statusColor = (rest.status as number) >= 400 ? colors.red : colors.green\n header += ` ${statusColor}${rest.status}${colors.reset}`\n delete rest.status\n }\n\n if (rest.duration) {\n header += ` ${colors.dim}in ${rest.duration}${colors.reset}`\n delete rest.duration\n }\n\n console.log(header)\n\n const entries = Object.entries(rest).filter(([_, v]) => v !== undefined)\n const lastIndex = entries.length - 1\n\n entries.forEach(([key, value], index) => {\n const isLast = index === lastIndex\n const prefix = isLast ? '└─' : '├─'\n const formatted = formatValue(value)\n console.log(` ${colors.dim}${prefix}${colors.reset} ${colors.cyan}${key}:${colors.reset} ${formatted}`)\n })\n}\n\nfunction createLogMethod(level: LogLevel) {\n return function logMethod(tagOrEvent: string | Record<string, unknown>, message?: string): void {\n if (typeof tagOrEvent === 'string' && message !== undefined) {\n emitTaggedLog(level, tagOrEvent, message)\n } else if (typeof tagOrEvent === 'object') {\n emitWideEvent(level, tagOrEvent)\n } else {\n emitTaggedLog(level, 'log', String(tagOrEvent))\n }\n }\n}\n\n/**\n * Simple logging API - as easy as console.log\n *\n * @example\n * ```ts\n * log.info('auth', 'User logged in')\n * log.error({ action: 'payment', error: 'failed' })\n * ```\n */\nconst _log: Log = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n warn: createLogMethod('warn'),\n debug: createLogMethod('debug'),\n}\n\nexport { _log as log }\n\nconst noopLogger: RequestLogger = {\n set() {},\n error() {},\n info() {},\n warn() {},\n emit() {\n return null\n },\n getContext() {\n return {}\n },\n}\n\n/**\n * Create a request-scoped logger for building wide events.\n *\n * @example\n * ```ts\n * const log = createRequestLogger({ method: 'POST', path: '/checkout' })\n * log.set({ user: { id: '123' } })\n * log.set({ cart: { items: 3 } })\n * log.emit()\n * ```\n */\nexport function createRequestLogger<T extends object = Record<string, unknown>>(options: RequestLoggerOptions = {}): RequestLogger<T> {\n if (!globalEnabled) return noopLogger as RequestLogger<T>\n\n const startTime = Date.now()\n let context: Record<string, unknown> = {\n method: options.method,\n path: options.path,\n requestId: options.requestId,\n }\n let hasError = false\n let hasWarn = false\n\n function addRequestLog(level: 'info' | 'warn', message: string): void {\n const entry = {\n level,\n message,\n timestamp: new Date().toISOString(),\n }\n\n const requestLogs = Array.isArray(context.requestLogs)\n ? [...context.requestLogs, entry]\n : [entry]\n\n context = {\n ...context,\n requestLogs,\n }\n }\n\n return {\n set(data: FieldContext<T>): void {\n context = deepDefaults(data as Record<string, unknown>, context) as Record<string, unknown>\n },\n\n error(error: Error | string, errorContext?: FieldContext<T>): void {\n hasError = true\n const err = typeof error === 'string' ? new Error(error) : error\n\n const errorData = {\n ...(errorContext as Record<string, unknown>),\n error: {\n name: err.name,\n message: err.message,\n stack: err.stack,\n ...('status' in err && { status: (err as Record<string, unknown>).status }),\n ...('statusText' in err && { statusText: (err as Record<string, unknown>).statusText }),\n ...('statusCode' in err && { statusCode: (err as Record<string, unknown>).statusCode }),\n ...('statusMessage' in err && { statusMessage: (err as Record<string, unknown>).statusMessage }),\n ...('data' in err && { data: (err as Record<string, unknown>).data }),\n ...('cause' in err && { cause: (err as unknown as Record<string, unknown>).cause }),\n },\n }\n context = deepDefaults(errorData, context) as Record<string, unknown>\n },\n\n info(message: string, infoContext?: FieldContext<T>): void {\n addRequestLog('info', message)\n if (infoContext) {\n const { requestLogs: _, ...rest } = infoContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n warn(message: string, warnContext?: FieldContext<T>): void {\n hasWarn = true\n addRequestLog('warn', message)\n if (warnContext) {\n const { requestLogs: _, ...rest } = warnContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n emit(overrides?: FieldContext<T> & { _forceKeep?: boolean }): WideEvent | null {\n const durationMs = Date.now() - startTime\n const duration = formatDuration(durationMs)\n const level: LogLevel = hasError ? 'error' : hasWarn ? 'warn' : 'info'\n\n // Extract _forceKeep from overrides (set by evlog:emit:keep hook)\n const { _forceKeep, ...restOverrides } = (overrides ?? {}) as Record<string, unknown> & { _forceKeep?: boolean }\n\n // Build tail sampling context\n const tailCtx: TailSamplingContext = {\n status: (context.status ?? restOverrides.status) as number | undefined,\n duration: durationMs,\n path: context.path as string | undefined,\n method: context.method as string | undefined,\n context: { ...context, ...restOverrides },\n }\n\n // Tail sampling: force keep if hook or built-in conditions match\n const forceKeep = _forceKeep || shouldKeep(tailCtx)\n\n // Apply head sampling only if not force-kept\n if (!forceKeep && !shouldSample(level)) {\n return null\n }\n\n return emitWideEvent(level, {\n ...context,\n ...restOverrides,\n duration,\n }, true)\n },\n\n getContext(): FieldContext<T> & Record<string, unknown> {\n return { ...context }\n },\n }\n}\n\n/**\n * Get the current environment context.\n */\nexport function getEnvironment(): EnvironmentContext {\n return { ...globalEnv }\n}\n","import { createRequestLogger, initLogger } from 'evlog'\n\n/**\n * Log level for the Stack logger.\n *\n * Configured via the `STASH_STACK_LOG` environment variable.\n *\n * - `'error'` — Only errors (default when `STASH_STACK_LOG` is not set).\n * - `'info'` — Info and errors.\n * - `'debug'` — Debug, info, and errors.\n */\nexport type LogLevel = 'debug' | 'info' | 'error'\n\nconst validLevels: readonly LogLevel[] = ['debug', 'info', 'error'] as const\n\nfunction levelFromEnv(): LogLevel {\n const env = process.env.STASH_STACK_LOG\n if (env && validLevels.includes(env as LogLevel)) return env as LogLevel\n return 'error'\n}\n\nfunction samplingRatesForLevel(level: LogLevel): Record<string, number> {\n // evlog uses sampling rates: 100 = always emit, 0 = never emit\n switch (level) {\n case 'debug':\n return { debug: 100, info: 100, warn: 100, error: 100 }\n case 'info':\n return { debug: 0, info: 100, warn: 100, error: 100 }\n case 'error':\n default:\n return { debug: 0, info: 0, warn: 0, error: 100 }\n }\n}\n\nlet initialized = false\n\n/**\n * Initialize the Stack logger.\n *\n * The log level is read from the `STASH_STACK_LOG` environment variable.\n * When the variable is not set, the default is `'error'` (errors only).\n *\n * @internal\n */\nexport function initStackLogger(): void {\n if (initialized) return\n initialized = true\n\n const level = levelFromEnv()\n const rates = samplingRatesForLevel(level)\n\n initLogger({\n env: { service: '@cipherstash/stack' },\n enabled: true,\n sampling: { rates },\n })\n}\n\n// Auto-init with defaults on first import\ninitStackLogger()\n\nexport { createRequestLogger }\n\n// Stringify only the first arg (the message string); drop subsequent args\n// which may contain sensitive objects (e.g. encryptConfig, plaintext).\nfunction safeMessage(args: unknown[]): string {\n return typeof args[0] === 'string' ? args[0] : ''\n}\n\n// Logger for simple one-off logs used across Stack interfaces.\nexport const logger = {\n debug(...args: unknown[]) {\n const log = createRequestLogger()\n log.set({\n level: 'debug',\n source: '@cipherstash/stack',\n message: safeMessage(args),\n })\n log.emit()\n },\n info(...args: unknown[]) {\n const log = createRequestLogger()\n log.set({ source: '@cipherstash/stack' })\n log.info(safeMessage(args))\n log.emit()\n },\n warn(...args: unknown[]) {\n const log = createRequestLogger()\n log.warn(safeMessage(args))\n log.emit()\n },\n error(...args: unknown[]) {\n const log = createRequestLogger()\n log.error(safeMessage(args))\n log.emit()\n },\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export { b as bulkModelsToEncryptedPgComposites, m as modelToEncryptedPgComposites } from './index-fhWTOV0K.cjs';
|
|
2
|
+
export { A as AuditConfig } from './base-operation-AOAIvsSB.cjs';
|
|
3
|
+
import { Z as V3ColumnFactory } from './columns-Bxv7Oo9o.cjs';
|
|
4
|
+
export { A as AnyEncryptedV3Column, D as DATE_LIKE_CASTS, Q as EncryptedV3Column } from './columns-Bxv7Oo9o.cjs';
|
|
5
|
+
import { M as MatchIndexOpts } from './types-public-QMjYNfQO.cjs';
|
|
6
|
+
import '@byteslice/result';
|
|
7
|
+
import './errors/index.cjs';
|
|
8
|
+
import '@cipherstash/protect-ffi';
|
|
9
|
+
import 'zod';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Unqualified Postgres `domain_name` → the `eql/v3/types.ts` factory that
|
|
13
|
+
* builds that domain's column.
|
|
14
|
+
*
|
|
15
|
+
* DERIVED, not hand-listed. Every factory already carries its domain in the
|
|
16
|
+
* constant it passes to the column constructor, so the key is recoverable from
|
|
17
|
+
* the factory itself — and `types` and this registry cannot drift. Adding a
|
|
18
|
+
* domain to `types` is enough; forgetting an entry here is no longer possible.
|
|
19
|
+
*
|
|
20
|
+
* The keys are an external contract (they are the `information_schema` query
|
|
21
|
+
* parameter in `../../supabase/introspect.ts`). Because they are derived from
|
|
22
|
+
* `getEqlType()`, no test that also derives them can detect a corrupted domain
|
|
23
|
+
* constant — `eql-v3-domain-registry.test.ts` pins them against a hand-written
|
|
24
|
+
* literal list for exactly that reason.
|
|
25
|
+
*
|
|
26
|
+
* `factory('_probe')` runs at module load, so a non-factory value in `types`
|
|
27
|
+
* would throw here and take the importing modules (`../../supabase/introspect.ts`,
|
|
28
|
+
* `schema-builder.ts`, `verify.ts`) down with it. `types` is declared
|
|
29
|
+
* `satisfies Record<string, V3ColumnFactory>` so that mistake is a compile error
|
|
30
|
+
* at its source instead. Do not reintroduce a cast on `Object.values` below: it
|
|
31
|
+
* would silence exactly the check that keeps this loop total.
|
|
32
|
+
*/
|
|
33
|
+
declare const DOMAIN_REGISTRY: Record<string, V3ColumnFactory>;
|
|
34
|
+
/** Strip a leading `public.` schema qualifier from a qualified `eqlType`. */
|
|
35
|
+
declare function stripDomainSchema(eqlType: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Look up the factory for an unqualified domain name, or `undefined`.
|
|
38
|
+
*
|
|
39
|
+
* `Object.hasOwn` is required, not decorative: without it a domain named
|
|
40
|
+
* `constructor` / `toString` / `valueOf` / `__proto__` resolves to an inherited
|
|
41
|
+
* `Object.prototype` member and violates the "unknown domain = plaintext" rule.
|
|
42
|
+
*/
|
|
43
|
+
declare function factoryForDomain(domainName: string): V3ColumnFactory | undefined;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Shared JSONPath-selector path handling for the first-party adapters
|
|
47
|
+
* (`@cipherstash/stack-drizzle`, `@cipherstash/stack-supabase`), exposed via
|
|
48
|
+
* `@cipherstash/stack/adapter-kit`.
|
|
49
|
+
*
|
|
50
|
+
* Both adapters express "compare the value at `$.a.b`" over an encrypted
|
|
51
|
+
* `eql_v3_json` column, and both need the same three pieces: parse + validate
|
|
52
|
+
* the dot-notation path, reconstruct the `{ a: { b: value } }` needle document
|
|
53
|
+
* whose ste_vec entry at the path carries the comparison terms, and reject
|
|
54
|
+
* non-scalar leaves up front. Originally private to the Drizzle v3 operators
|
|
55
|
+
* (#651); moved here when the Supabase adapter grew the same querying (#650) so
|
|
56
|
+
* the validation rules cannot drift between adapters.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* Parse a dot-notation JSONPath into its object-key segments. Rejects, each with
|
|
60
|
+
* a clear message: array-index/wildcard syntax (v1 is object-keys-only), the
|
|
61
|
+
* empty/root path, malformed paths (`..`, stray/leading/trailing dots, so we
|
|
62
|
+
* never silently query a *different* path), and prototype-pollution keys.
|
|
63
|
+
* `'$.a.b'` / `' a.b '` → `['a', 'b']`.
|
|
64
|
+
*/
|
|
65
|
+
declare function parseSelectorSegments(path: string): string[];
|
|
66
|
+
/** `$`-rooted JSONPath for `encryptQuery`'s selector needle. */
|
|
67
|
+
declare function jsonPathOf(segments: string[]): string;
|
|
68
|
+
/**
|
|
69
|
+
* A selector compares a single scalar LEAF. Returns a reason string when `value`
|
|
70
|
+
* is unsupported — a non-scalar (object/array → that's `contains`), or a boolean
|
|
71
|
+
* under an ordering operator (no ordering term) — else `null`. Callers raise it
|
|
72
|
+
* as their adapter's operator error with column context, so a bad value is an
|
|
73
|
+
* actionable SDK error rather than a deferred, opaque DB failure.
|
|
74
|
+
*/
|
|
75
|
+
declare function unsupportedLeafReason(value: unknown, ordering: boolean): string | null;
|
|
76
|
+
/**
|
|
77
|
+
* Nest `value` under the segments: `['a','b']` → `{ a: { b: value } }`. The
|
|
78
|
+
* storage-needle document whose ste_vec entry at the path supplies the
|
|
79
|
+
* ciphertext-bearing comparison entry.
|
|
80
|
+
*/
|
|
81
|
+
declare function reconstructSelectorDocument(segments: string[], value: unknown): Record<string, unknown>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Why a needle cannot be answered by this match index, or `undefined` when it
|
|
85
|
+
* can. Callers throw their own error type with this as the reason.
|
|
86
|
+
*
|
|
87
|
+
* A needle that tokenizes to NOTHING has an empty bloom filter, and
|
|
88
|
+
* `stored_bf @> '{}'` is true for every row ("contains nothing, contained by
|
|
89
|
+
* everything"). Such a query is unanswerable rather than merely unmatched, so
|
|
90
|
+
* it must fail loudly instead of silently returning the whole table. Two ways
|
|
91
|
+
* to tokenize to nothing:
|
|
92
|
+
*
|
|
93
|
+
* - the empty needle, under ANY tokenizer;
|
|
94
|
+
* - a needle shorter than the ngram tokenizer's `token_length`.
|
|
95
|
+
*
|
|
96
|
+
* The ngram floor counts Unicode CODEPOINTS, because that is what the tokenizer
|
|
97
|
+
* counts. `needle.length` would count UTF-16 code units and wave through an
|
|
98
|
+
* astral-plane needle: `'👍👍'` is 4 code units but only 2 codepoints, yields no
|
|
99
|
+
* trigram, and (measured live) matched every row. Graphemes are the wrong unit
|
|
100
|
+
* in the other direction — NFD `'éé'` is 4 codepoints but 2 grapheme clusters,
|
|
101
|
+
* and does yield trigrams.
|
|
102
|
+
*
|
|
103
|
+
* Currently wired ONLY into the v3 Drizzle adapter (`eql/v3/drizzle/operators`).
|
|
104
|
+
* It lives here, beside the shared match defaults, because v2 builds the same
|
|
105
|
+
* bloom filters and needs the same floor — but v2's `like`/`ilike` path remains
|
|
106
|
+
* unguarded and still fails open. Do not reuse this for v2 without first
|
|
107
|
+
* measuring what its tokenizer actually receives: v2 needles carry SQL wildcards
|
|
108
|
+
* (`'%ada%'`), so the floor may have to apply to the unwrapped term rather than
|
|
109
|
+
* to the string the caller passed.
|
|
110
|
+
*/
|
|
111
|
+
declare function matchNeedleError(needle: unknown, opts: MatchIndexOpts): string | undefined;
|
|
112
|
+
|
|
113
|
+
declare const logger: {
|
|
114
|
+
debug(...args: unknown[]): void;
|
|
115
|
+
info(...args: unknown[]): void;
|
|
116
|
+
warn(...args: unknown[]): void;
|
|
117
|
+
error(...args: unknown[]): void;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export { DOMAIN_REGISTRY, factoryForDomain, jsonPathOf, logger, matchNeedleError, parseSelectorSegments, reconstructSelectorDocument, stripDomainSchema, unsupportedLeafReason };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export { b as bulkModelsToEncryptedPgComposites, m as modelToEncryptedPgComposites } from './index-BquA71_Y.js';
|
|
2
|
+
export { A as AuditConfig } from './base-operation-FXEzUXIq.js';
|
|
3
|
+
import { Z as V3ColumnFactory } from './columns-0lbT9stl.js';
|
|
4
|
+
export { A as AnyEncryptedV3Column, D as DATE_LIKE_CASTS, Q as EncryptedV3Column } from './columns-0lbT9stl.js';
|
|
5
|
+
import { M as MatchIndexOpts } from './types-public-QMjYNfQO.js';
|
|
6
|
+
import '@byteslice/result';
|
|
7
|
+
import './errors/index.js';
|
|
8
|
+
import '@cipherstash/protect-ffi';
|
|
9
|
+
import 'zod';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Unqualified Postgres `domain_name` → the `eql/v3/types.ts` factory that
|
|
13
|
+
* builds that domain's column.
|
|
14
|
+
*
|
|
15
|
+
* DERIVED, not hand-listed. Every factory already carries its domain in the
|
|
16
|
+
* constant it passes to the column constructor, so the key is recoverable from
|
|
17
|
+
* the factory itself — and `types` and this registry cannot drift. Adding a
|
|
18
|
+
* domain to `types` is enough; forgetting an entry here is no longer possible.
|
|
19
|
+
*
|
|
20
|
+
* The keys are an external contract (they are the `information_schema` query
|
|
21
|
+
* parameter in `../../supabase/introspect.ts`). Because they are derived from
|
|
22
|
+
* `getEqlType()`, no test that also derives them can detect a corrupted domain
|
|
23
|
+
* constant — `eql-v3-domain-registry.test.ts` pins them against a hand-written
|
|
24
|
+
* literal list for exactly that reason.
|
|
25
|
+
*
|
|
26
|
+
* `factory('_probe')` runs at module load, so a non-factory value in `types`
|
|
27
|
+
* would throw here and take the importing modules (`../../supabase/introspect.ts`,
|
|
28
|
+
* `schema-builder.ts`, `verify.ts`) down with it. `types` is declared
|
|
29
|
+
* `satisfies Record<string, V3ColumnFactory>` so that mistake is a compile error
|
|
30
|
+
* at its source instead. Do not reintroduce a cast on `Object.values` below: it
|
|
31
|
+
* would silence exactly the check that keeps this loop total.
|
|
32
|
+
*/
|
|
33
|
+
declare const DOMAIN_REGISTRY: Record<string, V3ColumnFactory>;
|
|
34
|
+
/** Strip a leading `public.` schema qualifier from a qualified `eqlType`. */
|
|
35
|
+
declare function stripDomainSchema(eqlType: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Look up the factory for an unqualified domain name, or `undefined`.
|
|
38
|
+
*
|
|
39
|
+
* `Object.hasOwn` is required, not decorative: without it a domain named
|
|
40
|
+
* `constructor` / `toString` / `valueOf` / `__proto__` resolves to an inherited
|
|
41
|
+
* `Object.prototype` member and violates the "unknown domain = plaintext" rule.
|
|
42
|
+
*/
|
|
43
|
+
declare function factoryForDomain(domainName: string): V3ColumnFactory | undefined;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Shared JSONPath-selector path handling for the first-party adapters
|
|
47
|
+
* (`@cipherstash/stack-drizzle`, `@cipherstash/stack-supabase`), exposed via
|
|
48
|
+
* `@cipherstash/stack/adapter-kit`.
|
|
49
|
+
*
|
|
50
|
+
* Both adapters express "compare the value at `$.a.b`" over an encrypted
|
|
51
|
+
* `eql_v3_json` column, and both need the same three pieces: parse + validate
|
|
52
|
+
* the dot-notation path, reconstruct the `{ a: { b: value } }` needle document
|
|
53
|
+
* whose ste_vec entry at the path carries the comparison terms, and reject
|
|
54
|
+
* non-scalar leaves up front. Originally private to the Drizzle v3 operators
|
|
55
|
+
* (#651); moved here when the Supabase adapter grew the same querying (#650) so
|
|
56
|
+
* the validation rules cannot drift between adapters.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* Parse a dot-notation JSONPath into its object-key segments. Rejects, each with
|
|
60
|
+
* a clear message: array-index/wildcard syntax (v1 is object-keys-only), the
|
|
61
|
+
* empty/root path, malformed paths (`..`, stray/leading/trailing dots, so we
|
|
62
|
+
* never silently query a *different* path), and prototype-pollution keys.
|
|
63
|
+
* `'$.a.b'` / `' a.b '` → `['a', 'b']`.
|
|
64
|
+
*/
|
|
65
|
+
declare function parseSelectorSegments(path: string): string[];
|
|
66
|
+
/** `$`-rooted JSONPath for `encryptQuery`'s selector needle. */
|
|
67
|
+
declare function jsonPathOf(segments: string[]): string;
|
|
68
|
+
/**
|
|
69
|
+
* A selector compares a single scalar LEAF. Returns a reason string when `value`
|
|
70
|
+
* is unsupported — a non-scalar (object/array → that's `contains`), or a boolean
|
|
71
|
+
* under an ordering operator (no ordering term) — else `null`. Callers raise it
|
|
72
|
+
* as their adapter's operator error with column context, so a bad value is an
|
|
73
|
+
* actionable SDK error rather than a deferred, opaque DB failure.
|
|
74
|
+
*/
|
|
75
|
+
declare function unsupportedLeafReason(value: unknown, ordering: boolean): string | null;
|
|
76
|
+
/**
|
|
77
|
+
* Nest `value` under the segments: `['a','b']` → `{ a: { b: value } }`. The
|
|
78
|
+
* storage-needle document whose ste_vec entry at the path supplies the
|
|
79
|
+
* ciphertext-bearing comparison entry.
|
|
80
|
+
*/
|
|
81
|
+
declare function reconstructSelectorDocument(segments: string[], value: unknown): Record<string, unknown>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Why a needle cannot be answered by this match index, or `undefined` when it
|
|
85
|
+
* can. Callers throw their own error type with this as the reason.
|
|
86
|
+
*
|
|
87
|
+
* A needle that tokenizes to NOTHING has an empty bloom filter, and
|
|
88
|
+
* `stored_bf @> '{}'` is true for every row ("contains nothing, contained by
|
|
89
|
+
* everything"). Such a query is unanswerable rather than merely unmatched, so
|
|
90
|
+
* it must fail loudly instead of silently returning the whole table. Two ways
|
|
91
|
+
* to tokenize to nothing:
|
|
92
|
+
*
|
|
93
|
+
* - the empty needle, under ANY tokenizer;
|
|
94
|
+
* - a needle shorter than the ngram tokenizer's `token_length`.
|
|
95
|
+
*
|
|
96
|
+
* The ngram floor counts Unicode CODEPOINTS, because that is what the tokenizer
|
|
97
|
+
* counts. `needle.length` would count UTF-16 code units and wave through an
|
|
98
|
+
* astral-plane needle: `'👍👍'` is 4 code units but only 2 codepoints, yields no
|
|
99
|
+
* trigram, and (measured live) matched every row. Graphemes are the wrong unit
|
|
100
|
+
* in the other direction — NFD `'éé'` is 4 codepoints but 2 grapheme clusters,
|
|
101
|
+
* and does yield trigrams.
|
|
102
|
+
*
|
|
103
|
+
* Currently wired ONLY into the v3 Drizzle adapter (`eql/v3/drizzle/operators`).
|
|
104
|
+
* It lives here, beside the shared match defaults, because v2 builds the same
|
|
105
|
+
* bloom filters and needs the same floor — but v2's `like`/`ilike` path remains
|
|
106
|
+
* unguarded and still fails open. Do not reuse this for v2 without first
|
|
107
|
+
* measuring what its tokenizer actually receives: v2 needles carry SQL wildcards
|
|
108
|
+
* (`'%ada%'`), so the floor may have to apply to the unwrapped term rather than
|
|
109
|
+
* to the string the caller passed.
|
|
110
|
+
*/
|
|
111
|
+
declare function matchNeedleError(needle: unknown, opts: MatchIndexOpts): string | undefined;
|
|
112
|
+
|
|
113
|
+
declare const logger: {
|
|
114
|
+
debug(...args: unknown[]): void;
|
|
115
|
+
info(...args: unknown[]): void;
|
|
116
|
+
warn(...args: unknown[]): void;
|
|
117
|
+
error(...args: unknown[]): void;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export { DOMAIN_REGISTRY, factoryForDomain, jsonPathOf, logger, matchNeedleError, parseSelectorSegments, reconstructSelectorDocument, stripDomainSchema, unsupportedLeafReason };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DATE_LIKE_CASTS,
|
|
3
|
+
EncryptedV3Column,
|
|
4
|
+
types
|
|
5
|
+
} from "./chunk-6SGN52W6.js";
|
|
6
|
+
import {
|
|
7
|
+
bulkModelsToEncryptedPgComposites,
|
|
8
|
+
modelToEncryptedPgComposites
|
|
9
|
+
} from "./chunk-BNO32ZMV.js";
|
|
10
|
+
import {
|
|
11
|
+
matchNeedleError
|
|
12
|
+
} from "./chunk-7333ZC6L.js";
|
|
13
|
+
import {
|
|
14
|
+
logger
|
|
15
|
+
} from "./chunk-HQANMV7R.js";
|
|
16
|
+
import "./chunk-PZ5AY32C.js";
|
|
17
|
+
|
|
18
|
+
// src/eql/v3/domain-registry.ts
|
|
19
|
+
var DOMAIN_REGISTRY = (() => {
|
|
20
|
+
const registry = /* @__PURE__ */ Object.create(null);
|
|
21
|
+
for (const factory of Object.values(types)) {
|
|
22
|
+
const key = stripDomainSchema(factory("_probe").getEqlType());
|
|
23
|
+
if (Object.hasOwn(registry, key)) {
|
|
24
|
+
throw new Error(`duplicate EQL v3 domain key: ${key}`);
|
|
25
|
+
}
|
|
26
|
+
registry[key] = factory;
|
|
27
|
+
}
|
|
28
|
+
return registry;
|
|
29
|
+
})();
|
|
30
|
+
function stripDomainSchema(eqlType) {
|
|
31
|
+
return eqlType.startsWith("public.") ? eqlType.slice("public.".length) : eqlType;
|
|
32
|
+
}
|
|
33
|
+
function factoryForDomain(domainName) {
|
|
34
|
+
return Object.hasOwn(DOMAIN_REGISTRY, domainName) ? DOMAIN_REGISTRY[domainName] : void 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/eql/v3/selector-path.ts
|
|
38
|
+
var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set([
|
|
39
|
+
"__proto__",
|
|
40
|
+
"prototype",
|
|
41
|
+
"constructor"
|
|
42
|
+
]);
|
|
43
|
+
function parseSelectorSegments(path) {
|
|
44
|
+
const trimmed = path.trim();
|
|
45
|
+
let body = trimmed.startsWith("$") ? trimmed.slice(1) : trimmed;
|
|
46
|
+
if (body.startsWith(".")) body = body.slice(1);
|
|
47
|
+
if (body === "") {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`JSON selector path "${path}" addresses no field \u2014 use e.g. "$.a" or "$.a.b".`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (/[[\]*]/.test(body)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`JSON selector path "${path}" uses array/wildcard syntax, which is not yet supported \u2014 use dot-notation object keys (e.g. "$.a.b").`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const segments = body.split(".");
|
|
58
|
+
for (const segment of segments) {
|
|
59
|
+
if (segment === "") {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`JSON selector path "${path}" is malformed (empty segment / ".." / stray dot) \u2014 use dot-notation object keys (e.g. "$.a.b").`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (segment !== segment.trim()) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`JSON selector path "${path}" has whitespace at a segment boundary ("${segment}") \u2014 it would address a different key. Remove the spacing around the dot.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (FORBIDDEN_SEGMENTS.has(segment)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`JSON selector path "${path}" addresses the forbidden key "${segment}".`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return segments;
|
|
76
|
+
}
|
|
77
|
+
function jsonPathOf(segments) {
|
|
78
|
+
return `$.${segments.join(".")}`;
|
|
79
|
+
}
|
|
80
|
+
function unsupportedLeafReason(value, ordering) {
|
|
81
|
+
if (value == null) {
|
|
82
|
+
return "a selector compares a non-null scalar leaf, but got null/undefined \u2014 SQL NULL never equals anything; use is(column, null) for null checks.";
|
|
83
|
+
}
|
|
84
|
+
const isScalar = value instanceof Date || typeof value === "number" || typeof value === "bigint" || typeof value === "string" || typeof value === "boolean";
|
|
85
|
+
if (!isScalar) {
|
|
86
|
+
return `a selector compares a scalar leaf, but got ${Array.isArray(value) ? "an array" : "an object"} \u2014 use contains() for sub-object matching.`;
|
|
87
|
+
}
|
|
88
|
+
if (ordering && typeof value === "boolean") {
|
|
89
|
+
return "a boolean leaf has no ordering \u2014 use eq/ne (or contains()).";
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function reconstructSelectorDocument(segments, value) {
|
|
94
|
+
const root = /* @__PURE__ */ Object.create(null);
|
|
95
|
+
let cursor = root;
|
|
96
|
+
segments.forEach((segment, index) => {
|
|
97
|
+
if (index === segments.length - 1) {
|
|
98
|
+
cursor[segment] = value;
|
|
99
|
+
} else {
|
|
100
|
+
const next = /* @__PURE__ */ Object.create(null);
|
|
101
|
+
cursor[segment] = next;
|
|
102
|
+
cursor = next;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return root;
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
DATE_LIKE_CASTS,
|
|
109
|
+
DOMAIN_REGISTRY,
|
|
110
|
+
EncryptedV3Column,
|
|
111
|
+
bulkModelsToEncryptedPgComposites,
|
|
112
|
+
factoryForDomain,
|
|
113
|
+
jsonPathOf,
|
|
114
|
+
logger,
|
|
115
|
+
matchNeedleError,
|
|
116
|
+
modelToEncryptedPgComposites,
|
|
117
|
+
parseSelectorSegments,
|
|
118
|
+
reconstructSelectorDocument,
|
|
119
|
+
stripDomainSchema,
|
|
120
|
+
unsupportedLeafReason
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=adapter-kit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/eql/v3/domain-registry.ts","../src/eql/v3/selector-path.ts"],"sourcesContent":["import type { V3ColumnFactory } from './columns'\nimport { types } from './types'\n\nexport type { V3ColumnFactory } from './columns'\n\n/**\n * Unqualified Postgres `domain_name` → the `eql/v3/types.ts` factory that\n * builds that domain's column.\n *\n * DERIVED, not hand-listed. Every factory already carries its domain in the\n * constant it passes to the column constructor, so the key is recoverable from\n * the factory itself — and `types` and this registry cannot drift. Adding a\n * domain to `types` is enough; forgetting an entry here is no longer possible.\n *\n * The keys are an external contract (they are the `information_schema` query\n * parameter in `../../supabase/introspect.ts`). Because they are derived from\n * `getEqlType()`, no test that also derives them can detect a corrupted domain\n * constant — `eql-v3-domain-registry.test.ts` pins them against a hand-written\n * literal list for exactly that reason.\n *\n * `factory('_probe')` runs at module load, so a non-factory value in `types`\n * would throw here and take the importing modules (`../../supabase/introspect.ts`,\n * `schema-builder.ts`, `verify.ts`) down with it. `types` is declared\n * `satisfies Record<string, V3ColumnFactory>` so that mistake is a compile error\n * at its source instead. Do not reintroduce a cast on `Object.values` below: it\n * would silence exactly the check that keeps this loop total.\n */\n// NULL PROTOTYPE — load-bearing. A plain object literal inherits from\n// Object.prototype, so `DOMAIN_REGISTRY['constructor']` returns a *function*\n// and passes a truthiness check. A column whose domain is named `constructor`,\n// `toString`, `valueOf` or `__proto__` would then be treated as a modelled EQL\n// domain and \"synthesized\" from Object.prototype.constructor. `factoryForDomain`\n// additionally guards with `Object.hasOwn`; both are kept — belt and braces,\n// because a future refactor that drops the null prototype must not silently\n// reopen the hole. It also makes the `Object.hasOwn` collision check below\n// exact: on a plain object `'constructor' in registry` is spuriously true.\nexport const DOMAIN_REGISTRY: Record<string, V3ColumnFactory> = (() => {\n const registry = Object.create(null) as Record<string, V3ColumnFactory>\n for (const factory of Object.values(types)) {\n // Probe name only: the constructors store their arguments and nothing else,\n // so building one column per domain at module load is free of side effects.\n const key = stripDomainSchema(factory('_probe').getEqlType())\n if (Object.hasOwn(registry, key)) {\n throw new Error(`duplicate EQL v3 domain key: ${key}`)\n }\n registry[key] = factory\n }\n return registry\n})()\n\n/** Strip a leading `public.` schema qualifier from a qualified `eqlType`. */\nexport function stripDomainSchema(eqlType: string): string {\n return eqlType.startsWith('public.')\n ? eqlType.slice('public.'.length)\n : eqlType\n}\n\n/**\n * Look up the factory for an unqualified domain name, or `undefined`.\n *\n * `Object.hasOwn` is required, not decorative: without it a domain named\n * `constructor` / `toString` / `valueOf` / `__proto__` resolves to an inherited\n * `Object.prototype` member and violates the \"unknown domain = plaintext\" rule.\n */\nexport function factoryForDomain(\n domainName: string,\n): V3ColumnFactory | undefined {\n return Object.hasOwn(DOMAIN_REGISTRY, domainName)\n ? DOMAIN_REGISTRY[domainName]\n : undefined\n}\n","/**\n * Shared JSONPath-selector path handling for the first-party adapters\n * (`@cipherstash/stack-drizzle`, `@cipherstash/stack-supabase`), exposed via\n * `@cipherstash/stack/adapter-kit`.\n *\n * Both adapters express \"compare the value at `$.a.b`\" over an encrypted\n * `eql_v3_json` column, and both need the same three pieces: parse + validate\n * the dot-notation path, reconstruct the `{ a: { b: value } }` needle document\n * whose ste_vec entry at the path carries the comparison terms, and reject\n * non-scalar leaves up front. Originally private to the Drizzle v3 operators\n * (#651); moved here when the Supabase adapter grew the same querying (#650) so\n * the validation rules cannot drift between adapters.\n */\n\n/**\n * Object keys that are prototype-pollution vectors — rejected outright (mirrors\n * core's `FORBIDDEN_KEYS`), so a selector can never address them.\n */\nconst FORBIDDEN_SEGMENTS: ReadonlySet<string> = new Set([\n '__proto__',\n 'prototype',\n 'constructor',\n])\n\n/**\n * Parse a dot-notation JSONPath into its object-key segments. Rejects, each with\n * a clear message: array-index/wildcard syntax (v1 is object-keys-only), the\n * empty/root path, malformed paths (`..`, stray/leading/trailing dots, so we\n * never silently query a *different* path), and prototype-pollution keys.\n * `'$.a.b'` / `' a.b '` → `['a', 'b']`.\n */\nexport function parseSelectorSegments(path: string): string[] {\n const trimmed = path.trim()\n let body = trimmed.startsWith('$') ? trimmed.slice(1) : trimmed\n if (body.startsWith('.')) body = body.slice(1)\n if (body === '') {\n throw new Error(\n `JSON selector path \"${path}\" addresses no field — use e.g. \"$.a\" or \"$.a.b\".`,\n )\n }\n if (/[[\\]*]/.test(body)) {\n throw new Error(\n `JSON selector path \"${path}\" uses array/wildcard syntax, which is not yet supported — use dot-notation object keys (e.g. \"$.a.b\").`,\n )\n }\n const segments = body.split('.')\n for (const segment of segments) {\n if (segment === '') {\n throw new Error(\n `JSON selector path \"${path}\" is malformed (empty segment / \"..\" / stray dot) — use dot-notation object keys (e.g. \"$.a.b\").`,\n )\n }\n // Leading/trailing whitespace in a segment is almost certainly spacing\n // around a dot ('$.user .role'), which would silently address a DIFFERENT\n // key ('user '). Interior whitespace stays legal — 'a b' is a valid JSON\n // key a caller may genuinely have.\n if (segment !== segment.trim()) {\n throw new Error(\n `JSON selector path \"${path}\" has whitespace at a segment boundary (\"${segment}\") — it would address a different key. Remove the spacing around the dot.`,\n )\n }\n if (FORBIDDEN_SEGMENTS.has(segment)) {\n throw new Error(\n `JSON selector path \"${path}\" addresses the forbidden key \"${segment}\".`,\n )\n }\n }\n return segments\n}\n\n/** `$`-rooted JSONPath for `encryptQuery`'s selector needle. */\nexport function jsonPathOf(segments: string[]): string {\n return `$.${segments.join('.')}`\n}\n\n/**\n * A selector compares a single scalar LEAF. Returns a reason string when `value`\n * is unsupported — a non-scalar (object/array → that's `contains`), or a boolean\n * under an ordering operator (no ordering term) — else `null`. Callers raise it\n * as their adapter's operator error with column context, so a bad value is an\n * actionable SDK error rather than a deferred, opaque DB failure.\n */\nexport function unsupportedLeafReason(\n value: unknown,\n ordering: boolean,\n): string | null {\n // Explicit null arm: `typeof null === 'object'`, so without it a null leaf\n // would get the actively-wrong \"got an object — use contains()\" steer.\n if (value == null) {\n return 'a selector compares a non-null scalar leaf, but got null/undefined — SQL NULL never equals anything; use is(column, null) for null checks.'\n }\n const isScalar =\n value instanceof Date ||\n typeof value === 'number' ||\n typeof value === 'bigint' ||\n typeof value === 'string' ||\n typeof value === 'boolean'\n if (!isScalar) {\n return `a selector compares a scalar leaf, but got ${Array.isArray(value) ? 'an array' : 'an object'} — use contains() for sub-object matching.`\n }\n if (ordering && typeof value === 'boolean') {\n return 'a boolean leaf has no ordering — use eq/ne (or contains()).'\n }\n return null\n}\n\n/**\n * Nest `value` under the segments: `['a','b']` → `{ a: { b: value } }`. The\n * storage-needle document whose ste_vec entry at the path supplies the\n * ciphertext-bearing comparison entry.\n */\nexport function reconstructSelectorDocument(\n segments: string[],\n value: unknown,\n): Record<string, unknown> {\n // Null-prototype objects: a segment like `__proto__` must become an OWN key,\n // not invoke the prototype setter (which would drop it and mis-serialize the\n // needle). JSON.stringify ignores the [[Prototype]], so this serializes fine.\n const root: Record<string, unknown> = Object.create(null)\n let cursor = root\n segments.forEach((segment, index) => {\n if (index === segments.length - 1) {\n cursor[segment] = value\n } else {\n const next: Record<string, unknown> = Object.create(null)\n cursor[segment] = next\n cursor = next\n }\n })\n return root\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoCO,IAAM,mBAAoD,MAAM;AACrE,QAAM,WAAW,uBAAO,OAAO,IAAI;AACnC,aAAW,WAAW,OAAO,OAAO,KAAK,GAAG;AAG1C,UAAM,MAAM,kBAAkB,QAAQ,QAAQ,EAAE,WAAW,CAAC;AAC5D,QAAI,OAAO,OAAO,UAAU,GAAG,GAAG;AAChC,YAAM,IAAI,MAAM,gCAAgC,GAAG,EAAE;AAAA,IACvD;AACA,aAAS,GAAG,IAAI;AAAA,EAClB;AACA,SAAO;AACT,GAAG;AAGI,SAAS,kBAAkB,SAAyB;AACzD,SAAO,QAAQ,WAAW,SAAS,IAC/B,QAAQ,MAAM,UAAU,MAAM,IAC9B;AACN;AASO,SAAS,iBACd,YAC6B;AAC7B,SAAO,OAAO,OAAO,iBAAiB,UAAU,IAC5C,gBAAgB,UAAU,IAC1B;AACN;;;ACpDA,IAAM,qBAA0C,oBAAI,IAAI;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AACF,CAAC;AASM,SAAS,sBAAsB,MAAwB;AAC5D,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,OAAO,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;AACxD,MAAI,KAAK,WAAW,GAAG,EAAG,QAAO,KAAK,MAAM,CAAC;AAC7C,MAAI,SAAS,IAAI;AACf,UAAM,IAAI;AAAA,MACR,uBAAuB,IAAI;AAAA,IAC7B;AAAA,EACF;AACA,MAAI,SAAS,KAAK,IAAI,GAAG;AACvB,UAAM,IAAI;AAAA,MACR,uBAAuB,IAAI;AAAA,IAC7B;AAAA,EACF;AACA,QAAM,WAAW,KAAK,MAAM,GAAG;AAC/B,aAAW,WAAW,UAAU;AAC9B,QAAI,YAAY,IAAI;AAClB,YAAM,IAAI;AAAA,QACR,uBAAuB,IAAI;AAAA,MAC7B;AAAA,IACF;AAKA,QAAI,YAAY,QAAQ,KAAK,GAAG;AAC9B,YAAM,IAAI;AAAA,QACR,uBAAuB,IAAI,4CAA4C,OAAO;AAAA,MAChF;AAAA,IACF;AACA,QAAI,mBAAmB,IAAI,OAAO,GAAG;AACnC,YAAM,IAAI;AAAA,QACR,uBAAuB,IAAI,kCAAkC,OAAO;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,WAAW,UAA4B;AACrD,SAAO,KAAK,SAAS,KAAK,GAAG,CAAC;AAChC;AASO,SAAS,sBACd,OACA,UACe;AAGf,MAAI,SAAS,MAAM;AACjB,WAAO;AAAA,EACT;AACA,QAAM,WACJ,iBAAiB,QACjB,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU;AACnB,MAAI,CAAC,UAAU;AACb,WAAO,8CAA8C,MAAM,QAAQ,KAAK,IAAI,aAAa,WAAW;AAAA,EACtG;AACA,MAAI,YAAY,OAAO,UAAU,WAAW;AAC1C,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,4BACd,UACA,OACyB;AAIzB,QAAM,OAAgC,uBAAO,OAAO,IAAI;AACxD,MAAI,SAAS;AACb,WAAS,QAAQ,CAAC,SAAS,UAAU;AACnC,QAAI,UAAU,SAAS,SAAS,GAAG;AACjC,aAAO,OAAO,IAAI;AAAA,IACpB,OAAO;AACL,YAAM,OAAgC,uBAAO,OAAO,IAAI;AACxD,aAAO,OAAO,IAAI;AAClB,eAAS;AAAA,IACX;AAAA,EACF,CAAC;AACD,SAAO;AACT;","names":[]}
|