cvx-kit 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/AGENTS.md +47 -0
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +144 -0
  4. package/dist/auth.d.mts +3 -1
  5. package/dist/auth.mjs +1 -1
  6. package/dist/auth.mjs.map +1 -1
  7. package/dist/index.d.mts +5 -2
  8. package/dist/index.mjs +1 -1
  9. package/dist/modules/effect/api-zod.d.mts +1 -1
  10. package/dist/oxlint/architecture.d.mts +2 -0
  11. package/dist/oxlint/architecture.mjs +1 -1
  12. package/dist/oxlint/architecture.mjs.map +1 -1
  13. package/dist/oxlint/ast.d.mts +10 -1
  14. package/dist/oxlint/ast.mjs +1 -1
  15. package/dist/oxlint/ast.mjs.map +1 -1
  16. package/dist/oxlint.d.mts +1 -1
  17. package/dist/oxlint.mjs +1 -1
  18. package/dist/oxlint.mjs.map +1 -1
  19. package/dist/querying/cursors.d.mts +45 -0
  20. package/dist/querying/cursors.mjs +2 -0
  21. package/dist/querying/cursors.mjs.map +1 -0
  22. package/dist/querying/execution.d.mts +30 -0
  23. package/dist/querying/execution.mjs +2 -0
  24. package/dist/querying/execution.mjs.map +1 -0
  25. package/dist/querying/filters.d.mts +136 -0
  26. package/dist/querying/filters.mjs +2 -0
  27. package/dist/querying/filters.mjs.map +1 -0
  28. package/dist/querying/planner.d.mts +50 -0
  29. package/dist/querying/planner.mjs +2 -0
  30. package/dist/querying/planner.mjs.map +1 -0
  31. package/dist/querying/relationships.d.mts +41 -0
  32. package/dist/querying/relationships.mjs +2 -0
  33. package/dist/querying/relationships.mjs.map +1 -0
  34. package/dist/querying/security.d.mts +107 -0
  35. package/dist/querying/security.mjs +2 -0
  36. package/dist/querying/security.mjs.map +1 -0
  37. package/dist/querying.d.mts +39 -0
  38. package/dist/querying.mjs +2 -0
  39. package/dist/querying.mjs.map +1 -0
  40. package/dist/relations-public.d.mts +2 -0
  41. package/dist/relations-public.mjs +1 -0
  42. package/dist/relations.d.mts +155 -0
  43. package/dist/relations.mjs +2 -0
  44. package/dist/relations.mjs.map +1 -0
  45. package/dist/zod-convex.d.mts +10 -0
  46. package/dist/zod-convex.mjs +2 -0
  47. package/dist/zod-convex.mjs.map +1 -0
  48. package/dist/zod-registration.d.mts +10 -0
  49. package/dist/zod-registration.mjs +2 -0
  50. package/dist/zod-registration.mjs.map +1 -0
  51. package/dist/zod-table.d.mts +63 -8
  52. package/dist/zod-table.mjs +1 -1
  53. package/dist/zod-table.mjs.map +1 -1
  54. package/docs/conventions.md +12 -0
  55. package/docs/llms.md +15 -5
  56. package/docs/oxlint.md +129 -25
  57. package/docs/querying.md +293 -0
  58. package/docs/zod-table.md +93 -13
  59. package/package.json +11 -1
  60. package/scripts/owned-process.mjs +140 -0
  61. package/scripts/owned-process.test.mjs +107 -0
  62. package/scripts/smoke-packed-querying.mjs +296 -0
  63. package/scripts/test-packed-effect.mjs +8 -1
  64. package/scripts/test-packed-oxlint.mjs +5 -1
  65. package/scripts/test-packed-querying.mjs +185 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.mjs","names":["MAX_PAGE_SIZE","MAX_FILTER_DEPTH","MAX_FILTER_NODES","MAX_FILTER_IN_VALUES","nativeOptions","convexToZod","paginationOptsValidator","nativePagination","scalarPaginationSchema","predicates","key","selected","selection","validators","scalarPredicateSchema","filters","boundedFilterSchema","z","assertSupportedScalar","validator","scalar","equality","ranged","supportsRange","explicit","value","entry","relationPredicates","filterGrammars","BoundedFilterObject","inst","def","parse","namespace","namespaceObject","selectedKeys","grammar","schema","relation","withinFilterBudget","payload","ctx","relationFilterSchema","filterNodeSchema","compileFilters","parsed","input","server","where","paginationGrammar","residual","normalizeFilters","guaranteed","guaranteedBindings","sorted","indexes","a","b","condition","conditions","index","prefixBindings","length","candidate","usable","root","cached","name","predicate","children","expression","child","field","normalizeScalar","target","operator","valueEqual","compareValues","left","right","values","ast","choices","branches","viable","branch","impossible","prefix","bindings","member","direct","relations","depth","node","pending","nodes","object","array","prototype","keys","consumed","descriptor","context"],"sources":["../../src/querying/filters.ts"],"sourcesContent":["import { convexToZod, type ConvexValidatorFromZod } from 'convex-helpers/server/zod4'\nimport { paginationOptsValidator } from 'convex/server'\nimport { compareValues, type GenericId, type GenericValidator } from 'convex/values'\nimport { z } from 'zod'\n\n/** Shared hard ceilings; the execution engine may impose lower budgets. */\nexport const MAX_PAGE_SIZE = 100\nexport const MAX_FILTER_DEPTH = 4\nexport const MAX_FILTER_NODES = 256\nexport const MAX_FILTER_IN_VALUES = 100\n\nexport type FieldSchemas = Record<string, z.ZodType>\ntype NativeField<Schema extends z.ZodType> = ConvexValidatorFromZod<Schema, 'required'>\ntype ScalarKind = 'id' | 'string' | 'float64' | 'int64' | 'boolean' | 'null' | 'literal'\ntype SupportedNative<Validator extends GenericValidator> = Validator extends { kind: ScalarKind }\n\t? true\n\t: Validator extends { kind: 'union'; members: infer Members extends GenericValidator[] }\n\t\t? false extends SupportedNative<Members[number]>\n\t\t\t? false\n\t\t\t: true\n\t\t: false\n\nexport type ScalarSelection<Fields extends FieldSchemas> = {\n\t[\n\t\tKey in keyof Fields as false extends SupportedNative<NativeField<Fields[Key]>> ? never : Key\n\t]?: true\n}\nexport type ExactScalarSelection<Fields extends FieldSchemas, Selection> = Selection & {\n\t[Key in keyof Selection]: Key extends keyof ScalarSelection<Fields>\n\t\t? Selection[Key] extends true\n\t\t\t? true\n\t\t\t: never\n\t\t: never\n}\n\ntype NativeValue<Schema extends z.ZodType> = Exclude<NativeField<Schema>['type'], undefined>\ntype FilterInputValue<Value> = Value extends GenericId<string> ? string : Value\ntype FilterValue<Schema extends z.ZodType, Side extends 'input' | 'output'> = Side extends 'input'\n\t? FilterInputValue<NativeValue<Schema>>\n\t: NativeValue<Schema>\ntype RangeKind = 'string' | 'float64' | 'int64' | 'literal'\ntype OrderedNative<Validator extends GenericValidator> = Validator extends { kind: RangeKind }\n\t? Validator['type'] extends string | number | bigint | undefined\n\t\t? true\n\t\t: false\n\t: Validator extends { kind: 'union'; members: infer Members extends GenericValidator[] }\n\t\t? false extends OrderedNative<Members[number]>\n\t\t\t? false\n\t\t\t: true\n\t\t: false\ntype OrderedValue<Schema extends z.ZodType> =\n\tOrderedNative<NativeField<Schema>> extends true ? NativeValue<Schema> : never\n\nexport type ScalarPredicate<Schema extends z.ZodType, Side extends 'input' | 'output'> =\n\t| FilterValue<Schema, Side>\n\t| ({ eq?: FilterValue<Schema, Side>; in?: FilterValue<Schema, Side>[] } & ([\n\t\t\tOrderedValue<Schema>,\n\t ] extends [never]\n\t\t\t? {}\n\t\t\t: {\n\t\t\t\t\tgt?: OrderedValue<Schema>\n\t\t\t\t\tgte?: OrderedValue<Schema>\n\t\t\t\t\tlt?: OrderedValue<Schema>\n\t\t\t\t\tlte?: OrderedValue<Schema>\n\t\t\t\t}) &\n\t\t\t(NativeField<Schema>['isOptional'] extends 'optional' ? { exists?: boolean } : {}))\n\ntype SelectedFieldFilters<\n\tFields extends FieldSchemas,\n\tSelection,\n\tSide extends 'input' | 'output',\n> = {\n\t[Key in Extract<keyof Selection, keyof Fields>]?: ScalarPredicate<Fields[Key], Side>\n}\n\n/** Direct shorthand and explicit fields combine with AND. The fields namespace\n * accepts every selected scalar name, including boolean-control/namespace names.\n */\ntype FilterDepth = readonly [1, 1, 1, 1]\ntype Tail<Depth extends readonly 1[]> = Depth extends readonly [1, ...infer Rest extends 1[]]\n\t? Rest\n\t: []\n\nexport type ScalarFilters<\n\tFields extends FieldSchemas,\n\tSelection,\n\tSide extends 'input' | 'output' = 'output',\n\tDepth extends readonly 1[] = FilterDepth,\n> = Omit<SelectedFieldFilters<Fields, Selection, Side>, 'and' | 'or' | 'not' | 'fields'> & {\n\tfields?: SelectedFieldFilters<Fields, Selection, Side>\n} & (Depth extends readonly []\n\t\t? {}\n\t\t: {\n\t\t\t\tand?: ScalarFilters<Fields, Selection, Side, Tail<Depth>>[]\n\t\t\t\tor?: ScalarFilters<Fields, Selection, Side, Tail<Depth>>[]\n\t\t\t\tnot?: ScalarFilters<Fields, Selection, Side, Tail<Depth>>\n\t\t\t})\n\nconst nativeOptions = convexToZod(paginationOptsValidator)\nexport const nativePagination = nativeOptions.strict().extend({\n\tnumItems: nativeOptions.shape.numItems.int().positive().max(MAX_PAGE_SIZE),\n\tmaximumRowsRead: nativeOptions.shape.maximumRowsRead.unwrap().int().positive().optional(),\n\tmaximumBytesRead: nativeOptions.shape.maximumBytesRead.unwrap().int().positive().optional(),\n})\nexport type NativePagination = z.output<typeof nativePagination>\n/** Preserve the object boundary so pagination options can be registered directly as query args. */\nexport type PaginationOptionsSchema<Filters, InputFilters = Filters> = z.ZodObject<\n\t(typeof nativePagination)['shape'] & {\n\t\tfilters: z.ZodOptional<z.ZodType<Filters, InputFilters>>\n\t\torder: z.ZodOptional<z.ZodEnum<{ asc: 'asc'; desc: 'desc' }>>\n\t},\n\tz.core.$strict\n>\nexport type ScalarPaginationSchema<\n\tFields extends FieldSchemas,\n\tSelection,\n> = PaginationOptionsSchema<\n\tScalarFilters<Fields, Selection>,\n\tScalarFilters<Fields, Selection, 'input'>\n>\n\n/** Scalar-only schema constructor shared by table and future registry boundaries.\n * Convex metadata describes stored INPUT, so storage refinements/transforms never run here.\n */\nexport function scalarPaginationSchema<\n\tFields extends FieldSchemas,\n\tconst Selection extends ScalarSelection<Fields>,\n>(\n\tvalidators: Record<string, GenericValidator>,\n\tselection: ExactScalarSelection<Fields, Selection>,\n): ScalarPaginationSchema<Fields, Selection> {\n\tconst predicates: FieldSchemas = {}\n\tfor (const [key, selected] of Object.entries(selection)) {\n\t\tif (selected !== true || !Object.hasOwn(validators, key)) {\n\t\t\tthrow new Error(`Invalid scalar filter selection \"${key}\"`)\n\t\t}\n\t\tpredicates[key] = scalarPredicateSchema(validators[key]).optional()\n\t}\n\tconst filters = boundedFilterSchema(predicates)\n\tconst result = nativePagination.extend({\n\t\tfilters: filters.optional(),\n\t\torder: z.enum(['asc', 'desc']).optional(),\n\t})\n\t// SAFETY: selected validators generate the same native scalar input/output relationships\n\t// described by ScalarFilters, including converter-registered ID brands.\n\treturn result as ScalarPaginationSchema<Fields, Selection>\n}\n\nexport function scalarPredicateSchema(validator: GenericValidator): z.ZodType {\n\tassertSupportedScalar(validator)\n\tconst scalar = convexToZod(validator).nonoptional()\n\tconst equality = z.object({\n\t\teq: scalar.optional(),\n\t\tin: z.array(scalar).min(1).max(MAX_FILTER_IN_VALUES).optional(),\n\t})\n\tconst ranged = supportsRange(validator)\n\t\t? equality.extend({\n\t\t\t\tgt: scalar.optional(),\n\t\t\t\tgte: scalar.optional(),\n\t\t\t\tlt: scalar.optional(),\n\t\t\t\tlte: scalar.optional(),\n\t\t\t})\n\t\t: equality\n\tconst existence =\n\t\tvalidator.isOptional === 'optional' ? ranged.extend({ exists: z.boolean().optional() }) : ranged\n\tconst explicit = existence\n\t\t.strict()\n\t\t.refine(\n\t\t\t(value) => Object.values(value).some((entry) => entry !== undefined),\n\t\t\t'At least one operator is required',\n\t\t)\n\treturn z.union([scalar, explicit])\n}\n\ntype FilterTree =\n\t| string\n\t| number\n\t| bigint\n\t| boolean\n\t| null\n\t| undefined\n\t| FilterTree[]\n\t| { [key: string]: FilterTree }\n\n// Compose Zod's exported core constructor with its ordinary object initializer.\n// Clones use this constructor again, preserving preflight without replacing the\n// converter-recognized object definition or any native scalar/ID validators.\nconst relationPredicates = new WeakMap<\n\tz.core.$ZodType,\n\t{ cardinality: 'one' | 'many'; target: FilterGrammar }\n>()\nconst filterGrammars = new WeakMap<z.core.$ZodType, FilterGrammar>()\n\nconst BoundedFilterObject = z.core.$constructor<z.ZodObject>('BoundedFilterObject', (inst, def) => {\n\tz.ZodObject.init(inst, def)\n\tconst parse = inst._zod.parse\n\tconst namespace = def.shape.fields\n\tconst namespaceObject = namespace instanceof z.ZodOptional ? namespace.unwrap() : undefined\n\tconst selectedKeys =\n\t\tnamespaceObject instanceof z.ZodObject\n\t\t\t? Object.keys(namespaceObject.shape)\n\t\t\t: Object.keys(def.shape)\n\tconst grammar: FilterGrammar = {\n\t\tfields: Object.fromEntries(selectedKeys.map((key) => [key, def.shape[key]])),\n\t\trelations: Object.fromEntries(\n\t\t\tObject.entries(def.shape).flatMap(([key, schema]) => {\n\t\t\t\tconst relation = relationPredicates.get(schema)\n\t\t\t\treturn relation ? [[key, relation]] : []\n\t\t\t}),\n\t\t),\n\t}\n\t// Zod refinements run after parsing. Guard parse itself so both the deferred\n\t// no-check run and the refinement run retain the same pre-child boundary.\n\tinst._zod.parse = (payload, ctx) => {\n\t\t// SAFETY: preflight examines layout and budgets only; Zod still validates values.\n\t\tif (!withinFilterBudget(payload.value as FilterTree, grammar)) {\n\t\t\tpayload.issues.push({\n\t\t\t\tcode: 'custom',\n\t\t\t\tinput: payload.value,\n\t\t\t\tmessage: 'Unsupported filter layout or depth/node budget exceeded',\n\t\t\t})\n\t\t\treturn payload\n\t\t}\n\t\treturn parse(payload, ctx)\n\t}\n})\n\n/** Each boolean child and relation target expression consumes one of four edges.\n * some/none wrappers and fields namespaces consume nodes, but no extra depth.\n */\nexport interface FilterGrammar {\n\tfields: FieldSchemas\n\trelations: Record<string, { cardinality: 'one' | 'many'; target: FilterGrammar }>\n}\n\nexport function relationFilterSchema(grammar: FilterGrammar): z.ZodType {\n\tconst schema = new BoundedFilterObject(filterNodeSchema(grammar, 0)._zod.def)\n\tfilterGrammars.set(schema, grammar)\n\treturn schema\n}\n\nexport type FilterScalar = string | number | bigint | boolean | null\nexport type FilterOperator = 'eq' | 'in' | 'gt' | 'gte' | 'lt' | 'lte' | 'exists'\nexport type FilterAst =\n\t| { kind: 'and' | 'or'; children: FilterAst[] }\n\t| { kind: 'not'; child: FilterAst }\n\t| {\n\t\t\tkind: 'scalar'\n\t\t\tfield: string\n\t\t\toperator: FilterOperator\n\t\t\tvalue: FilterScalar | FilterScalar[]\n\t }\n\t| { kind: 'relation'; name: string; mode: 'one' | 'some' | 'none'; predicate: FilterAst | null }\nexport interface QueryIndex {\n\tname: string\n\tfields: readonly string[]\n}\nexport interface IndexCondition {\n\tvalue: FilterScalar | undefined\n\tindex: string\n}\nexport interface CompiledFilters {\n\tresidual: FilterAst\n\tbindings: ReadonlyMap<string, FilterScalar>\n\tunsatisfiable: boolean\n\tindex: QueryIndex | null\n\tprefix: readonly { field: string; value: FilterScalar }[]\n}\ntype FilterSpec<Schema extends z.ZodType> =\n\tz.input<Schema> extends { filters?: infer Spec } ? Spec : never\nexport type ServerFilter<Fields extends FieldSchemas> = ScalarFilters<\n\tFields,\n\tScalarSelection<Fields>\n>\n\ninterface BindingResult {\n\tvalues: Map<string, FilterScalar>\n\timpossible: boolean\n}\n\n/** Validate first; where is independently validated and ANDed with client predicates.\n * OR remains a residual expression over one traversal, never multiple streams.\n */\nexport function compileFilters<Schema extends z.ZodType>(\n\tschema: Schema,\n\tinput: z.input<Schema>,\n\tindexes: readonly QueryIndex[],\n\tconditions: readonly IndexCondition[] = [],\n\twhere?: FilterSpec<Schema>,\n): CompiledFilters {\n\t// SAFETY: pagination schemas validate filters before this internal AST adapter reads them.\n\tconst parsed = schema.parse(input) as { filters?: FilterTree }\n\t// SAFETY: server filters use the same validation boundary as client filters.\n\tconst server =\n\t\twhere === undefined\n\t\t\t? undefined\n\t\t\t: (schema.parse({ ...parsed, filters: where }) as { filters?: FilterTree })\n\tconst grammar = paginationGrammar(schema)\n\tconst residual: FilterAst = {\n\t\tkind: 'and',\n\t\tchildren: [\n\t\t\tnormalizeFilters(parsed.filters, grammar),\n\t\t\tnormalizeFilters(server?.filters, grammar),\n\t\t],\n\t}\n\tconst guaranteed = guaranteedBindings(residual)\n\tconst sorted = [...indexes].sort(\n\t\t(a, b) =>\n\t\t\ta.name.localeCompare(b.name) || a.fields.join('\\0').localeCompare(b.fields.join('\\0')),\n\t)\n\tconst condition = conditions.find((entry) => entry.value !== null && entry.value !== undefined)\n\tlet index: QueryIndex | null = null\n\tif (condition) {\n\t\tindex = sorted.find((entry) => entry.name === condition.index) ?? null\n\t\tif (!index || prefixBindings(index, guaranteed.values).length === 0)\n\t\t\tthrow new Error(`Explicit index \"${condition.index}\" is incompatible with guaranteed filters`)\n\t} else {\n\t\tlet length = 0\n\t\tfor (const candidate of sorted) {\n\t\t\tconst usable = prefixBindings(candidate, guaranteed.values).length\n\t\t\tif (usable > length) {\n\t\t\t\tindex = candidate\n\t\t\t\tlength = usable\n\t\t\t}\n\t\t}\n\t}\n\treturn {\n\t\tresidual,\n\t\tbindings: guaranteed.values,\n\t\tunsatisfiable: guaranteed.impossible,\n\t\tindex,\n\t\tprefix: index ? prefixBindings(index, guaranteed.values) : [],\n\t}\n}\nfunction paginationGrammar(schema: z.ZodType): FilterGrammar | undefined {\n\tif (!(schema instanceof z.ZodObject)) return undefined\n\tconst filters = schema.shape.filters\n\tif (!(filters instanceof z.ZodOptional)) return undefined\n\tconst root = filters.unwrap()\n\tconst cached = filterGrammars.get(root)\n\tif (cached) return cached\n\tif (!(root instanceof z.ZodObject)) return undefined\n\treturn {\n\t\tfields: root.shape,\n\t\trelations: Object.fromEntries(\n\t\t\tObject.entries(root.shape).flatMap(([name, predicate]) => {\n\t\t\t\t// SAFETY: object shapes contain Zod schemas; metadata only recognizes predicates created by our grammar.\n\t\t\t\tconst relation = relationPredicates.get(predicate as z.core.$ZodType)\n\t\t\t\treturn relation ? [[name, relation]] : []\n\t\t\t}),\n\t\t),\n\t}\n}\nfunction normalizeFilters(value: FilterTree, grammar?: FilterGrammar): FilterAst {\n\tconst children: FilterAst[] = []\n\tif (!value) return { kind: 'and', children }\n\t// SAFETY: the caller parsed a strict filter object; its grammar has no array/primitive expression.\n\tconst expression = value as Record<string, FilterTree>\n\tfor (const [name, predicate] of Object.entries(expression)) {\n\t\tif (predicate === undefined) continue\n\t\tif (name === 'and' || name === 'or') {\n\t\t\t// SAFETY: boolean composition is validated as an array of expressions.\n\t\t\tchildren.push({\n\t\t\t\tkind: name,\n\t\t\t\tchildren: (predicate as FilterTree[]).map((child) => normalizeFilters(child, grammar)),\n\t\t\t})\n\t\t} else if (name === 'not')\n\t\t\tchildren.push({ kind: 'not', child: normalizeFilters(predicate, grammar) })\n\t\telse if (name === 'fields') {\n\t\t\t// SAFETY: fields is a strict selected scalar namespace.\n\t\t\tfor (const [field, scalar] of Object.entries(predicate as Record<string, FilterTree>))\n\t\t\t\tchildren.push(...normalizeScalar(field, scalar))\n\t\t} else if (grammar?.relations[name]) {\n\t\t\tconst relation = grammar.relations[name]\n\t\t\tif (relation.cardinality === 'one')\n\t\t\t\tchildren.push({\n\t\t\t\t\tkind: 'relation',\n\t\t\t\t\tname,\n\t\t\t\t\tmode: 'one',\n\t\t\t\t\tpredicate: predicate === null ? null : normalizeFilters(predicate, relation.target),\n\t\t\t\t})\n\t\t\telse {\n\t\t\t\t// SAFETY: many predicates validate as exactly one some/none target expression.\n\t\t\t\tconst target = predicate as { some?: FilterTree; none?: FilterTree }\n\t\t\t\tchildren.push({\n\t\t\t\t\tkind: 'relation',\n\t\t\t\t\tname,\n\t\t\t\t\tmode: target.some === undefined ? 'none' : 'some',\n\t\t\t\t\tpredicate: normalizeFilters(target.some ?? target.none, relation.target),\n\t\t\t\t})\n\t\t\t}\n\t\t} else children.push(...normalizeScalar(name, predicate))\n\t}\n\treturn { kind: 'and', children }\n}\nfunction normalizeScalar(field: string, predicate: FilterTree): FilterAst[] {\n\tif (predicate === undefined) return []\n\tif (Object(predicate) !== predicate) {\n\t\t// SAFETY: native scalar grammar excludes object/array values and preserves branded ID strings.\n\t\treturn [{ kind: 'scalar', field, operator: 'eq', value: predicate as FilterScalar }]\n\t}\n\t// SAFETY: only selected supported scalar operator objects survive validation.\n\treturn Object.entries(predicate as Record<FilterOperator, FilterScalar | FilterScalar[]>)\n\t\t.filter(([, value]) => value !== undefined)\n\t\t.map(([operator, value]) => ({\n\t\t\tkind: 'scalar',\n\t\t\tfield,\n\t\t\toperator: operator as FilterOperator,\n\t\t\tvalue,\n\t\t}))\n}\nfunction valueEqual(left: FilterScalar, right: FilterScalar): boolean {\n\treturn compareValues(left, right) === 0\n}\nfunction guaranteedBindings(ast: FilterAst): BindingResult {\n\tconst values = new Map<string, FilterScalar>()\n\tif (ast.kind === 'scalar') {\n\t\tif (ast.operator === 'eq' && !Array.isArray(ast.value)) values.set(ast.field, ast.value)\n\t\tconst choices = Array.isArray(ast.value) ? ast.value : undefined\n\t\tif (ast.operator === 'in' && choices && choices.every((value) => valueEqual(value, choices[0])))\n\t\t\tvalues.set(ast.field, choices[0])\n\t\treturn { values, impossible: false }\n\t}\n\tif (ast.kind === 'not' || ast.kind === 'relation') return { values, impossible: false }\n\tconst branches = ast.children.map(guaranteedBindings)\n\tif (ast.kind === 'or') {\n\t\tconst viable = branches\n\t\tfor (const [field, value] of viable[0]?.values ?? [])\n\t\t\tif (\n\t\t\t\tviable.every(\n\t\t\t\t\t(branch) => branch.values.has(field) && valueEqual(branch.values.get(field)!, value),\n\t\t\t\t)\n\t\t\t)\n\t\t\t\tvalues.set(field, value)\n\t\treturn { values, impossible: viable.every((branch) => branch.impossible) }\n\t}\n\tlet impossible = branches.some((branch) => branch.impossible)\n\tfor (const branch of branches)\n\t\tfor (const [field, value] of branch.values) {\n\t\t\tif (values.has(field) && !valueEqual(values.get(field)!, value)) impossible = true\n\t\t\tvalues.set(field, value)\n\t\t}\n\treturn { values, impossible }\n}\nfunction prefixBindings(\n\tindex: QueryIndex,\n\tbindings: ReadonlyMap<string, FilterScalar>,\n): { field: string; value: FilterScalar }[] {\n\tconst prefix: { field: string; value: FilterScalar }[] = []\n\tfor (const field of index.fields) {\n\t\tif (!bindings.has(field)) break\n\t\tprefix.push({ field, value: bindings.get(field)! })\n\t}\n\treturn prefix\n}\n\nfunction assertSupportedScalar(validator: GenericValidator): void {\n\tswitch (validator.kind) {\n\t\tcase 'id':\n\t\tcase 'string':\n\t\tcase 'float64':\n\t\tcase 'int64':\n\t\tcase 'boolean':\n\t\tcase 'null':\n\t\tcase 'literal':\n\t\t\treturn\n\t\tcase 'union':\n\t\t\tfor (const member of validator.members) {\n\t\t\t\tassertSupportedScalar(member)\n\t\t\t}\n\t\t\treturn\n\t\tdefault:\n\t\t\tthrow new Error(`Unsupported scalar filter kind \"${validator.kind}\"`)\n\t}\n}\n\nfunction supportsRange(validator: GenericValidator): boolean {\n\tswitch (validator.kind) {\n\t\tcase 'string':\n\t\tcase 'float64':\n\t\tcase 'int64':\n\t\t\treturn true\n\t\tcase 'literal':\n\t\t\treturn z.union([z.string(), z.number(), z.bigint()]).safeParse(validator.value).success\n\t\tcase 'union':\n\t\t\treturn validator.members.every(supportsRange)\n\t\tdefault:\n\t\t\treturn false\n\t}\n}\n\nfunction boundedFilterSchema(predicates: FieldSchemas): z.ZodType {\n\treturn relationFilterSchema({ fields: predicates, relations: {} })\n}\n\nfunction filterNodeSchema(grammar: FilterGrammar, depth: number): z.ZodObject {\n\tconst predicates = grammar.fields\n\tconst direct = Object.fromEntries(\n\t\tObject.entries(predicates).filter(([key]) => !['and', 'or', 'not', 'fields'].includes(key)),\n\t)\n\tconst relations: FieldSchemas = {}\n\tfor (const [name, relation] of Object.entries(grammar.relations)) {\n\t\tif (depth === MAX_FILTER_DEPTH) continue\n\t\tconst target = filterNodeSchema(relation.target, depth + 1)\n\t\trelations[name] = (\n\t\t\trelation.cardinality === 'one'\n\t\t\t\t? target.nullable()\n\t\t\t\t: z.union([z.object({ some: target }).strict(), z.object({ none: target }).strict()])\n\t\t).optional()\n\t\trelationPredicates.set(relations[name], relation)\n\t}\n\tconst node = z\n\t\t.object({ ...direct, ...relations, fields: z.object(predicates).strict().optional() })\n\t\t.strict()\n\tif (depth === MAX_FILTER_DEPTH) return node\n\tconst child = filterNodeSchema(grammar, depth + 1)\n\treturn node.extend({\n\t\tand: z.array(child).min(1).max(MAX_FILTER_NODES).optional(),\n\t\tor: z.array(child).min(1).max(MAX_FILTER_NODES).optional(),\n\t\tnot: child.optional(),\n\t})\n}\n\nfunction withinFilterBudget(value: FilterTree, grammar: FilterGrammar): boolean {\n\ttype Context = 'expression' | 'fields' | 'predicate' | 'composition' | 'many' | 'value'\n\tinterface Entry {\n\t\tvalue: FilterTree\n\t\tdepth: number\n\t\tcontext: Context\n\t\tgrammar: FilterGrammar\n\t}\n\tconst pending: Entry[] = [{ value, depth: 0, context: 'expression', grammar }]\n\tlet nodes = 0\n\twhile (pending.length) {\n\t\tconst entry = pending.pop()!\n\t\tif (++nodes > MAX_FILTER_NODES || entry.depth > MAX_FILTER_DEPTH) return false\n\t\tif (Object(entry.value) !== entry.value) continue\n\t\t// SAFETY: boxing excludes null and primitives; descriptors below avoid invoking getters.\n\t\tconst object = entry.value as FilterTree[] | Record<string, FilterTree>\n\t\tconst array = Array.isArray(object) ? object : undefined\n\t\tconst prototype = Object.getPrototypeOf(object)\n\t\tif (\n\t\t\tarray ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null\n\t\t)\n\t\t\treturn false\n\t\tif (array && array.length + nodes + pending.length > MAX_FILTER_NODES) return false\n\t\tconst keys = Reflect.ownKeys(object)\n\t\tif (\n\t\t\tkeys.length - (array ? 1 : 0) + nodes + pending.length > MAX_FILTER_NODES ||\n\t\t\t(array && keys.length !== array.length + 1)\n\t\t)\n\t\t\treturn false\n\t\tconst consumed =\n\t\t\tentry.context === 'expression'\n\t\t\t\t? [\n\t\t\t\t\t\t...Object.keys(entry.grammar.fields),\n\t\t\t\t\t\t...Object.keys(entry.grammar.relations),\n\t\t\t\t\t\t'and',\n\t\t\t\t\t\t'or',\n\t\t\t\t\t\t'not',\n\t\t\t\t\t\t'fields',\n\t\t\t\t\t]\n\t\t\t\t: entry.context === 'fields'\n\t\t\t\t\t? Object.keys(entry.grammar.fields)\n\t\t\t\t\t: entry.context === 'predicate'\n\t\t\t\t\t\t? ['eq', 'in', 'gt', 'gte', 'lt', 'lte', 'exists']\n\t\t\t\t\t\t: entry.context === 'many'\n\t\t\t\t\t\t\t? ['some', 'none']\n\t\t\t\t\t\t\t: []\n\t\tfor (const key of consumed) if (!Object.hasOwn(object, key) && key in object) return false\n\t\tfor (const key of keys) {\n\t\t\tif (array && key === 'length') continue\n\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(object, key)!\n\t\t\tconst name = String(key)\n\t\t\tif (!descriptor.enumerable || !('value' in descriptor) || name !== key) return false\n\t\t\tif (array && (!/^(0|[1-9]\\d*)$/.test(name) || Number(name) >= array.length)) return false\n\t\t\tlet context: Context = 'value'\n\t\t\tlet depth = entry.depth\n\t\t\tlet target = entry.grammar\n\t\t\tif (entry.context === 'expression') {\n\t\t\t\tcontext = 'predicate'\n\t\t\t\tif (name === 'fields') context = 'fields'\n\t\t\t\telse if (name === 'and' || name === 'or') {\n\t\t\t\t\tcontext = 'composition'\n\t\t\t\t\tdepth++\n\t\t\t\t} else if (name === 'not') {\n\t\t\t\t\tcontext = 'expression'\n\t\t\t\t\tdepth++\n\t\t\t\t} else if (entry.grammar.relations[name]) {\n\t\t\t\t\tconst relation = entry.grammar.relations[name]\n\t\t\t\t\tcontext = relation.cardinality === 'one' ? 'expression' : 'many'\n\t\t\t\t\ttarget = relation.target\n\t\t\t\t\tdepth++\n\t\t\t\t}\n\t\t\t} else if (entry.context === 'fields') context = 'predicate'\n\t\t\telse if (entry.context === 'composition' || entry.context === 'many') context = 'expression'\n\t\t\tpending.push({ value: descriptor.value, depth, context, grammar: target })\n\t\t}\n\t}\n\treturn true\n}\n"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { FilterAst, FilterScalar, IndexCondition, QueryIndex } from "./filters.mjs";
2
+ import { RelationMetadata, RelationRegistryIdentity, SelectionMask, getRelationExecutor } from "../relations.mjs";
3
+ import { GenericSchema, SchemaDefinition } from "convex/server";
4
+ import { z } from "zod";
5
+ import { Value } from "convex/values";
6
+ //#region src/querying/planner.d.ts
7
+ export type QueryInput = z.input<ReturnType<ReturnType<typeof getRelationExecutor>['pagination']>>;
8
+ export type StoredConstraint = NonNullable<QueryInput['filters']>;
9
+ export declare function createPlanner(schema: SchemaDefinition<GenericSchema, boolean>, relations: RelationRegistryIdentity): {
10
+ metadata: RelationMetadata;
11
+ page: (table: string, selection: SelectionMask) => z.ZodObject<{
12
+ page: z.ZodArray<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
13
+ isDone: z.ZodBoolean;
14
+ continueCursor: z.ZodString;
15
+ splitCursor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
16
+ pageStatus: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
17
+ SplitRecommended: "SplitRecommended";
18
+ SplitRequired: "SplitRequired";
19
+ }>>>;
20
+ }, z.core.$strip>;
21
+ assertUnchanged: () => void;
22
+ beforeLeaf(table: string): void;
23
+ complete(table: string, document: Record<string, Value>, permitted: boolean, residual: FilterAst): Record<string, unknown> | null;
24
+ plan(table: string, input: QueryInput, conditions: readonly IndexCondition[], constraints: readonly StoredConstraint[]): {
25
+ residual: FilterAst;
26
+ bindings: ReadonlyMap<string, FilterScalar>;
27
+ unsatisfiable: boolean;
28
+ index: QueryIndex | null;
29
+ prefix: readonly {
30
+ field: string;
31
+ value: FilterScalar;
32
+ }[];
33
+ args: {
34
+ cursor: string | null;
35
+ endCursor?: string | null | undefined;
36
+ id?: number | undefined;
37
+ numItems: number;
38
+ maximumRowsRead?: number | undefined;
39
+ maximumBytesRead?: number | undefined;
40
+ filters?: unknown;
41
+ order?: "asc" | "desc" | undefined;
42
+ };
43
+ clientIdentity: Value;
44
+ serverIdentity: Value;
45
+ };
46
+ };
47
+ /** Native scalar ordering/equality, including missing vs null, without coercion. */
48
+ export declare function scalarMatches(ast: FilterAst, document: Record<string, Value | undefined>): boolean;
49
+ //#endregion
50
+ //# sourceMappingURL=planner.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{compileFilters as e}from"./filters.mjs";import{getRelationExecutor as t,getRelationMetadata as n}from"../relations.mjs";import{compareValues as r}from"convex/values";function i(r,i){let o=t(i),s=()=>o.assertSchema(r.tables);s();let c=n(i),l=new Map(Object.keys(c.tables).map(e=>[e,o.pagination(e)]));return{metadata:c,page:o.page,assertUnchanged:s,beforeLeaf(e){o.assertNativeTable(e,r.tables[e])},complete(e,t,n,i){return o.project(r.tables,e,t,()=>n&&a(i,t))},plan(t,n,r,i){s();let a=l.get(t);if(!a)throw Error(`Unknown query table`);let o=a.parse(n),u=i.length===1?i[0]:i.length?{and:i}:void 0,d=e(a,o,c.tables[t].indexes,r,u),f=e(a,o,[]).residual,p=e(a,{...o,filters:u},[]).residual;return{...d,args:o,clientIdentity:f,serverIdentity:p}}}}function a(e,t){switch(e.kind){case`and`:return e.children.every(e=>a(e,t));case`or`:return e.children.some(e=>a(e,t));case`not`:return!a(e.child,t);case`relation`:throw Error(`Relation execution requires U6`);case`scalar`:{let n=t[e.field];if(e.operator===`exists`)return n!==void 0===e.value;if(e.operator===`in`)return Array.isArray(e.value)&&e.value.some(e=>r(n,e)===0);if(Array.isArray(e.value))throw Error(`Invalid scalar predicate`);let i=r(n,e.value);switch(e.operator){case`eq`:return i===0;case`gt`:return i>0;case`gte`:return i>=0;case`lt`:return i<0;case`lte`:return i<=0}}}}export{i as createPlanner,a as scalarMatches};
2
+ //# sourceMappingURL=planner.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner.mjs","names":["createPlanner","owned","getRelationExecutor","relations","assertUnchanged","schema","metadata","getRelationMetadata","inputs","table","document","permitted","scalarMatches","residual","validator","args","input","where","constraints","compiled","compileFilters","conditions","client","server","ast","child","actual","compareValues","value","compared"],"sources":["../../src/querying/planner.ts"],"sourcesContent":["import type { z } from 'zod'\nimport type { SchemaDefinition, GenericSchema } from 'convex/server'\nimport { compareValues, type Value } from 'convex/values'\nimport {\n\tgetRelationExecutor,\n\tgetRelationMetadata,\n\ttype RelationRegistryIdentity,\n} from '../relations'\nimport { compileFilters, type FilterAst, type IndexCondition } from './filters'\n\nexport type QueryInput = z.input<ReturnType<ReturnType<typeof getRelationExecutor>['pagination']>>\nexport type StoredConstraint = NonNullable<QueryInput['filters']>\n\nexport function createPlanner(\n\tschema: SchemaDefinition<GenericSchema, boolean>,\n\trelations: RelationRegistryIdentity,\n) {\n\tconst owned = getRelationExecutor(relations)\n\tconst assertUnchanged = () => owned.assertSchema(schema.tables)\n\tassertUnchanged()\n\tconst metadata = getRelationMetadata(relations)\n\tconst inputs = new Map(\n\t\tObject.keys(metadata.tables).map((table) => [table, owned.pagination(table)]),\n\t)\n\treturn {\n\t\tmetadata,\n\t\tpage: owned.page,\n\t\tassertUnchanged,\n\t\tbeforeLeaf(table: string) {\n\t\t\towned.assertNativeTable(table, schema.tables[table])\n\t\t},\n\t\tcomplete(\n\t\t\ttable: string,\n\t\t\tdocument: Record<string, Value>,\n\t\t\tpermitted: boolean,\n\t\t\tresidual: FilterAst,\n\t\t) {\n\t\t\treturn owned.project(\n\t\t\t\tschema.tables,\n\t\t\t\ttable,\n\t\t\t\tdocument,\n\t\t\t\t() => permitted && scalarMatches(residual, document),\n\t\t\t)\n\t\t},\n\t\tplan(\n\t\t\ttable: string,\n\t\t\tinput: QueryInput,\n\t\t\tconditions: readonly IndexCondition[],\n\t\t\tconstraints: readonly StoredConstraint[],\n\t\t) {\n\t\t\tassertUnchanged()\n\t\t\tconst validator = inputs.get(table)\n\t\t\tif (!validator) throw new Error('Unknown query table')\n\t\t\tconst args = validator.parse(input)\n\t\t\t// Server constraints are independently validated against stored fields,\n\t\t\t// not the endpoint's public input/filter mask. Client values cannot replace them.\n\t\t\tconst where =\n\t\t\t\tconstraints.length === 1\n\t\t\t\t\t? constraints[0]\n\t\t\t\t\t: constraints.length\n\t\t\t\t\t\t? { and: constraints }\n\t\t\t\t\t\t: undefined\n\t\t\tconst compiled = compileFilters(\n\t\t\t\tvalidator,\n\t\t\t\targs,\n\t\t\t\tmetadata.tables[table].indexes,\n\t\t\t\tconditions,\n\t\t\t\twhere,\n\t\t\t)\n\t\t\tconst client = compileFilters(validator, args, []).residual\n\t\t\tconst server = compileFilters(validator, { ...args, filters: where }, []).residual\n\t\t\t// SAFETY: normalized AST nodes contain only validated native scalar Values.\n\t\t\treturn { ...compiled, args, clientIdentity: client as Value, serverIdentity: server as Value }\n\t\t},\n\t}\n}\n\n/** Native scalar ordering/equality, including missing vs null, without coercion. */\nexport function scalarMatches(\n\tast: FilterAst,\n\tdocument: Record<string, Value | undefined>,\n): boolean {\n\tswitch (ast.kind) {\n\t\tcase 'and':\n\t\t\treturn ast.children.every((child) => scalarMatches(child, document))\n\t\tcase 'or':\n\t\t\treturn ast.children.some((child) => scalarMatches(child, document))\n\t\tcase 'not':\n\t\t\treturn !scalarMatches(ast.child, document)\n\t\tcase 'relation':\n\t\t\tthrow new Error('Relation execution requires U6')\n\t\tcase 'scalar': {\n\t\t\tconst actual = document[ast.field]\n\t\t\tif (ast.operator === 'exists') return (actual !== undefined) === ast.value\n\t\t\tif (ast.operator === 'in')\n\t\t\t\treturn (\n\t\t\t\t\tArray.isArray(ast.value) && ast.value.some((value) => compareValues(actual, value) === 0)\n\t\t\t\t)\n\t\t\tif (Array.isArray(ast.value)) throw new Error('Invalid scalar predicate')\n\t\t\tconst compared = compareValues(actual, ast.value)\n\t\t\tswitch (ast.operator) {\n\t\t\t\tcase 'eq':\n\t\t\t\t\treturn compared === 0\n\t\t\t\tcase 'gt':\n\t\t\t\t\treturn compared > 0\n\t\t\t\tcase 'gte':\n\t\t\t\t\treturn compared >= 0\n\t\t\t\tcase 'lt':\n\t\t\t\t\treturn compared < 0\n\t\t\t\tcase 'lte':\n\t\t\t\t\treturn compared <= 0\n\t\t\t}\n\t\t}\n\t}\n}\n"],"mappings":"6KAaA,SAAgBA,EACf,EACA,EACC,CACD,IAAMC,EAAQC,EAAoBC,CAAS,EACrCC,MAAwBH,EAAM,aAAaI,EAAO,MAAM,EAC9DD,EAAgB,EAChB,IAAME,EAAWC,EAAoBJ,CAAS,EACxCK,EAAS,IAAI,IAClB,OAAO,KAAKF,EAAS,MAAM,CAAC,CAAC,IAAK,GAAU,CAACG,EAAOR,EAAM,WAAWQ,CAAK,CAAC,CAAC,CAC7E,EACA,MAAO,CACN,WACA,KAAMR,EAAM,KACZ,kBACA,WAAW,EAAe,CACzBA,EAAM,kBAAkBQ,EAAOJ,EAAO,OAAOI,EAAM,CACpD,EACA,SACC,EACA,EACA,EACA,EACC,CACD,OAAOR,EAAM,QACZI,EAAO,OACPI,EACAC,MACMC,GAAaC,EAAcC,EAAUH,CAAQ,CACpD,CACD,EACA,KACC,EACA,EACA,EACA,EACC,CACDN,EAAgB,EAChB,IAAMU,EAAYN,EAAO,IAAIC,CAAK,EAClC,GAAI,CAACK,EAAW,MAAU,MAAM,qBAAqB,EACrD,IAAMC,EAAOD,EAAU,MAAME,CAAK,EAG5BC,EACLC,EAAY,SAAW,EACpBA,EAAY,GACZA,EAAY,OACX,CAAE,IAAKA,CAAY,EACnB,IAAA,GACCC,EAAWC,EAChBN,EACAC,EACAT,EAAS,OAAOG,EAAM,CAAC,QACvBY,EACAJ,CACD,EACMK,EAASF,EAAeN,EAAWC,EAAM,CAAC,CAAC,CAAC,CAAC,SAC7CQ,EAASH,EAAeN,EAAW,CAAE,GAAGC,EAAM,QAASE,CAAM,EAAG,CAAC,CAAC,CAAC,CAAC,SAE1E,MAAO,CAAE,GAAGE,EAAU,OAAM,eAAgBG,EAAiB,eAAgBC,CAAgB,CAC9F,CACD,CACD,CAGA,SAAgBX,EACf,EACA,EACU,CACV,OAAQY,EAAI,KAAZ,CACC,IAAK,MACJ,OAAOA,EAAI,SAAS,MAAO,GAAUZ,EAAca,EAAOf,CAAQ,CAAC,EACpE,IAAK,KACJ,OAAOc,EAAI,SAAS,KAAM,GAAUZ,EAAca,EAAOf,CAAQ,CAAC,EACnE,IAAK,MACJ,MAAO,CAACE,EAAcY,EAAI,MAAOd,CAAQ,EAC1C,IAAK,WACJ,MAAU,MAAM,gCAAgC,EACjD,IAAK,SAAU,CACd,IAAMgB,EAAShB,EAASc,EAAI,OAC5B,GAAIA,EAAI,WAAa,SAAU,OAAQE,IAAW,IAAA,KAAeF,EAAI,MACrE,GAAIA,EAAI,WAAa,KACpB,OACC,MAAM,QAAQA,EAAI,KAAK,GAAKA,EAAI,MAAM,KAAM,GAAUG,EAAcD,EAAQE,CAAK,IAAM,CAAC,EAE1F,GAAI,MAAM,QAAQJ,EAAI,KAAK,EAAG,MAAU,MAAM,0BAA0B,EACxE,IAAMK,EAAWF,EAAcD,EAAQF,EAAI,KAAK,EAChD,OAAQA,EAAI,SAAZ,CACC,IAAK,KACJ,OAAOK,IAAa,EACrB,IAAK,KACJ,OAAOA,EAAW,EACnB,IAAK,MACJ,OAAOA,GAAY,EACpB,IAAK,KACJ,OAAOA,EAAW,EACnB,IAAK,MACJ,OAAOA,GAAY,CACrB,CACD,CACD,CACD"}
@@ -0,0 +1,41 @@
1
+ import { FilterAst, IndexCondition, NativePagination } from "./filters.mjs";
2
+ import { RelationEdge } from "../relations.mjs";
3
+ import { CursorBinding, createCursorCodec } from "./cursors.mjs";
4
+ import { QueryInput, StoredConstraint, createPlanner } from "./planner.mjs";
5
+ import { ExecutionRequest } from "./execution.mjs";
6
+ import { GenericSchema, SchemaDefinition } from "convex/server";
7
+ import { GenericId, Value } from "convex/values";
8
+ //#region src/querying/relationships.d.ts
9
+ export interface OutputSelection {
10
+ [name: string]: true | OutputSelection;
11
+ }
12
+ type Edge = RelationEdge<string, string, 'one' | 'many'>;
13
+ export type RelationDocument = Record<string, Value> & {
14
+ _id: GenericId<string>;
15
+ _creationTime: number;
16
+ };
17
+ type Document = RelationDocument;
18
+ type Planner = ReturnType<typeof createPlanner>;
19
+ export declare class RelationshipError extends Error {
20
+ readonly code: 'AMBIGUOUS_RELATION' | 'INVALID_RELATION';
21
+ constructor(code: 'AMBIGUOUS_RELATION' | 'INVALID_RELATION');
22
+ }
23
+ /** The selection has already passed the registry validator; count leaf edges too. */
24
+ export declare function assertRelationshipDepth(selection: OutputSelection, depth: number): void;
25
+ /** One request owns all traversal and authorized ID memoization. No raw reader escapes. */
26
+ export declare function relationshipExecutor(schema: SchemaDefinition<GenericSchema, boolean>, request: ExecutionRequest, planner: Planner, codec: ReturnType<typeof createCursorCodec>, rootBinding: CursorBinding): {
27
+ matches: (table: string, parent: Document, ast: FilterAst, depth?: number) => Promise<boolean>;
28
+ output: (table: string, parent: Document, selection: OutputSelection, args: NativePagination & {
29
+ order?: 'asc' | 'desc';
30
+ }, path?: readonly string[], depth?: number, parents?: readonly string[]) => Promise<Record<string, Value>>;
31
+ continuation: (rootTable: string, rootId: string, path: readonly string[], selection: OutputSelection, input: QueryInput, conditions: readonly IndexCondition[], constraints: readonly StoredConstraint[]) => Promise<{
32
+ page: unknown[];
33
+ isDone: boolean;
34
+ continueCursor: string;
35
+ splitCursor?: string | null | undefined;
36
+ pageStatus?: "SplitRecommended" | "SplitRequired" | null | undefined;
37
+ }>;
38
+ edge: (table: string, name: string) => Edge;
39
+ };
40
+ //#endregion
41
+ //# sourceMappingURL=relationships.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{scalarMatches as e}from"./planner.mjs";import{CursorError as t}from"./cursors.mjs";import{executeStream as n,rethrowQueryError as r}from"./execution.mjs";import{z as i}from"zod";import{EmptyStream as a,getIndexFields as o,stream as s}from"convex-helpers/server/stream";var c=class extends Error{code;constructor(e){super(e),this.code=e,this.name=`RelationshipError`}};function l(e,t){for(let n of Object.values(e)){if(t>=4)throw new c(`INVALID_RELATION`);n!==!0&&l(n,t+1)}}function u(u,d,f,p,m){let h=f.metadata,g=i.object({cursor:i.string(),ancestors:i.array(i.string()).max(3)}).strict(),_=e=>`base`in e?e.base:e;function v(e,t){f.assertUnchanged();let n=h.declarations[e]?.[t];if(!n)throw new c(`INVALID_RELATION`);return n}function y(e,t){let n=t===`_id`?{name:`by_id`,fields:[`_id`]}:h.tables[e].indexes.find(e=>e.fields[0]===t);if(!n)throw new c(`INVALID_RELATION`);return n}function b(e,t,n,r){let i=t===`_id`?[{name:`by_id`,fields:[`_id`]}]:h.tables[e].indexes,a=e=>{let t=1;for(let r of e.fields.slice(1)){if(!n.bindings.has(r))break;t++}return t},o=r.find(e=>e.value!==null&&e.value!==void 0);if(o){let e=i.find(e=>e.name===o.index&&e.fields[0]===t);if(!e)throw Error(`Explicit index "${o.index}" is incompatible with declared relationship lookup`);return e}let s=i.filter(e=>e.fields[0]===t).sort((e,t)=>a(t)-a(e)||e.name.localeCompare(t.name)||e.fields.join(`\0`).localeCompare(t.fields.join(`\0`)));if(!s.length)throw new c(`INVALID_RELATION`);return s[0]}function x(e,t,n){let r=[{field:e.fields[0],value:t}];for(let t of e.fields.slice(1)){if(!n.bindings.has(t))break;r.push({field:t,value:n.bindings.get(t)})}return r}async function S(e,t){if(f.beforeLeaf(e),t==null||!d.db.normalizeId(e,t))return null;let n=await d.db.get(e,t);return f.assertUnchanged(),n}async function*C(e,t,n,r){if(d.expand(r,0),n==null)return;if(t===`_id`){let t=await S(e,n);t&&(d.expand(r),yield t);return}f.beforeLeaf(e);let i=y(e,t),a=d.leafQuery(e).withIndex(i.name,e=>e.eq(t,n))[Symbol.asyncIterator]();try{for(;;){f.assertUnchanged(),f.beforeLeaf(e);let t=await a.next();if(f.assertUnchanged(),t.done)return;let n=await d.authorize(e,t.value);f.assertUnchanged(),n&&(d.expand(r),yield t.value)}}finally{await a.return?.()}}async function*w(e,t,n){d.expand(n,0);let r=t[_(e.from).field];if(r!=null){if(`base`in e.from&&`base`in e.to)for await(let t of C(e.from.junction.table,e.from.junction.field,r,n))yield*C(e.target,e.to.base.field,t[e.to.junction.field],n);else yield*C(e.target,_(e.to).field,r,n)}}async function T(e,t,n){let r=null;for await(let i of w(e,t,n)){if(r)throw new c(`AMBIGUOUS_RELATION`);r=i}return r}async function E(t,n,r,i=0){if(d.expand(i,0),f.assertUnchanged(),r.kind===`scalar`)return e(r,n);if(r.kind===`not`)return!await E(t,n,r.child,i);if(r.kind===`and`||r.kind===`or`){for(let e of r.children){let a=await E(t,n,e,i);if(r.kind===`and`&&!a)return!1;if(r.kind===`or`&&a)return!0}return r.kind===`and`}if(r.kind!==`relation`)throw new c(`INVALID_RELATION`);let a=v(t,r.name);if(r.mode===`one`){let e=await T(a,n,i+1);return r.predicate===null?e===null:e!==null&&await E(a.target,e,r.predicate,i+1)}for await(let e of w(a,n,i+1))if(await E(a.target,e,r.predicate,i+1))return r.mode===`some`;return r.mode===`none`}async function D(e,t,n,r,i=[],a=0,o=[]){d.expand(a,0);let s=f.complete(e,t,!0,{kind:`and`,children:[]});for(let[c,l]of Object.entries(n)){let n=v(e,c),u=l===!0?{}:l;if(n.cardinality===`one`){let e=await T(n,t,a+1);s[c]=e?await D(n.target,e,u,r,[...i,c],a+1,[...o,t._id]):null}else s[c]=await k(e,t,c,u,{numItems:r.numItems,cursor:null,order:r.order},[],[],[...i,c],a+1,[...o,t._id])}return s}function O(e,n,r,i,a,o,s,u){f.page(v(e,n).target,r),l(r,s.length);let h=v(e,n);if(h.cardinality!==`many`)throw new c(`INVALID_RELATION`);let x=f.plan(h.target,i,[],o),S=h.from,C=h.to,w=`base`in S&&`base`in C?{table:S.junction.table,field:S.junction.field,targetField:C.base.field,targetReference:C.junction.field}:null,T=w?w.table:h.target,E=w?w.field:_(C).field,D=b(h.target,w?w.targetField:E,x,a),O=w?y(T,E):D,k={...m,table:T,index:O.name,order:x.args.order??`asc`,clientFilters:x.clientIdentity,serverFilters:{predicates:x.serverIdentity,selection:r,scope:{...d.scope},rootTable:m.table,lookupIndex:D.name},relationPath:s,parent:u};function A(e){try{return g.parse(JSON.parse(p.open(e,k)))}catch{throw new t}}return{declared:h,plan:x,through:w,sourceTable:T,field:E,index:O,targetIndex:D,binding:k,open:A}}async function k(e,t,r,i,c,l=[],m=[],h=[r],g=1,v=[],y=O(e,r,i,c,l,m,h,v[0]??t._id),b){d.expand(g,0);let{declared:C,plan:w,through:T,sourceTable:k,field:A,index:j,targetIndex:M,binding:N}=y,P=t?.[_(C.from).field],F={...w.args,...b??{cursor:null}},I={...w,index:j,prefix:P==null?[]:T?[{field:A,value:P}]:x(j,P,w),unsatisfiable:P==null||w.unsatisfiable},L=T&&T.targetField!==`_id`,R=await n(u,d,k,I,f,p,N,F,async(e,t)=>{if(!t)return null;d.expand(g);let n;return n=T&&!L?await S(C.target,e[T.targetReference]):e,!n||!await E(C.target,n,w.residual,g)?null:D(C.target,n,i,w.args,h,g,v)},e=>p.seal(JSON.stringify({cursor:e,ancestors:v.slice(1)}),N),L?(e,t)=>{let n=u,r=o(C.target,M.name,n);return e.flatMap(async e=>{d.expand(g);let i=await d.authorize(k,e);f.assertUnchanged();let o=e[T.targetReference];return!i||o==null?new a(w.args.order??`asc`,r):s(t,n).query(C.target).withIndex(M.name,e=>{let t=e,n=x(M,o,w);for(let e of n)t=t.eq(e.field,e.value);return t}).order(w.args.order??`asc`)},r)}:void 0,L?C.target:k);return f.assertUnchanged(),f.page(C.target,i).parse(R)}async function A(e,n,i,a,o,s,l){if(!i.length||i.length>4||!d.db.normalizeId(e,n))throw new c(`INVALID_RELATION`);let u=e,f=i.map(e=>{let t=v(u,e);return u=t.target,t}),p=f.at(-1),m=O(p.source,i.at(-1),a,o,s,l,i,n),h=o.cursor===null?null:m.open(o.cursor),g=o.endCursor==null?null:m.open(o.endCursor),_=h?.ancestors??g?.ancestors??[];if(_.length!==i.length-1||h&&g&&JSON.stringify(h.ancestors)!==JSON.stringify(g.ancestors)||_.some((e,t)=>!d.db.normalizeId(f[t].target,e)))throw new t;try{let t=await S(e,n);for(let e=0;t&&e<_.length;e++){let n=f[e],r=null;if(n.cardinality===`one`){let i=await T(n,t,e+1);i?._id===_[e]&&(r=i)}else for await(let i of w(n,t,e+1))if(i._id===_[e]){r=i;break}t=r}return await k(p.source,t,i.at(-1),a,o,s,l,i,i.length,[n,..._],m,{cursor:h?.cursor??null,endCursor:g?.cursor??o.endCursor})}catch(e){r(e)}}return{matches:E,output:D,continuation:A,edge:v}}export{c as RelationshipError,l as assertRelationshipDepth,u as relationshipExecutor};
2
+ //# sourceMappingURL=relationships.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relationships.mjs","names":["RelationshipError","code","assertRelationshipDepth","nested","selection","depth","relationshipExecutor","metadata","planner","navigation","z","base","leg","edge","declared","table","name","lookup","index","field","i","relationIndex","indexes","length","size","key","plan","condition","conditions","c","chosen","candidates","b","a","relationPrefix","prefix","get","id","request","result","targets","value","target","iterator","q","next","permitted","traverse","parent","junction","one","match","matches","ast","scalarMatches","child","output","dto","selected","args","path","parents","pageRelation","prepare","input","constraints","from","to","through","sourceTable","targetIndex","binding","rootBinding","rootId","open","codec","token","CursorError","prepared","opened","lookupPlan","indexedTarget","executeStream","schema","row","raw","nativeSchema","fields","getIndexFields","source","reference","EmptyStream","stream","db","range","continuation","rootTable","declarations","declaration","final","start","end","ancestors","found","error","rethrowQueryError"],"sources":["../../src/querying/relationships.ts"],"sourcesContent":["import type { GenericSchema, SchemaDefinition, IndexRangeBuilder } from 'convex/server'\nimport type { GenericId, Value } from 'convex/values'\nimport type { RelationEdge } from '../relations'\nimport type { createPlanner, QueryInput, StoredConstraint } from './planner'\nimport { scalarMatches } from './planner'\nimport type {\n\tFilterAst,\n\tFilterScalar,\n\tIndexCondition,\n\tNativePagination,\n\tQueryIndex,\n} from './filters'\nimport { executeStream, rethrowQueryError, type ExecutionRequest } from './execution'\nimport type { createCursorCodec, CursorBinding } from './cursors'\nimport { CursorError } from './cursors'\nimport { z } from 'zod'\nimport { stream, EmptyStream, getIndexFields } from 'convex-helpers/server/stream'\n\nexport interface OutputSelection {\n\t[name: string]: true | OutputSelection\n}\ntype Edge = RelationEdge<string, string, 'one' | 'many'>\nexport type RelationDocument = Record<string, Value> & {\n\t_id: GenericId<string>\n\t_creationTime: number\n}\ntype Document = RelationDocument\ntype Planner = ReturnType<typeof createPlanner>\n\nexport class RelationshipError extends Error {\n\tconstructor(readonly code: 'AMBIGUOUS_RELATION' | 'INVALID_RELATION') {\n\t\tsuper(code)\n\t\tthis.name = 'RelationshipError'\n\t}\n}\n\n/** The selection has already passed the registry validator; count leaf edges too. */\nexport function assertRelationshipDepth(selection: OutputSelection, depth: number): void {\n\tfor (const nested of Object.values(selection)) {\n\t\tif (depth >= 4) throw new RelationshipError('INVALID_RELATION')\n\t\tif (nested !== true) assertRelationshipDepth(nested, depth + 1)\n\t}\n}\n\n/** One request owns all traversal and authorized ID memoization. No raw reader escapes. */\nexport function relationshipExecutor(\n\tschema: SchemaDefinition<GenericSchema, boolean>,\n\trequest: ExecutionRequest,\n\tplanner: Planner,\n\tcodec: ReturnType<typeof createCursorCodec>,\n\trootBinding: CursorBinding,\n) {\n\tconst metadata = planner.metadata\n\tconst navigation = z\n\t\t.object({ cursor: z.string(), ancestors: z.array(z.string()).max(3) })\n\t\t.strict()\n\tconst base = (leg: Edge['from']) => ('base' in leg ? leg.base : leg)\n\tfunction edge(table: string, name: string): Edge {\n\t\tplanner.assertUnchanged()\n\t\tconst declared = metadata.declarations[table]?.[name]\n\t\tif (!declared) throw new RelationshipError('INVALID_RELATION')\n\t\treturn declared\n\t}\n\tfunction lookup(table: string, field: string) {\n\t\tconst index =\n\t\t\tfield === '_id'\n\t\t\t\t? { name: 'by_id', fields: ['_id'] }\n\t\t\t\t: metadata.tables[table].indexes.find((i) => i.fields[0] === field)\n\t\tif (!index) throw new RelationshipError('INVALID_RELATION')\n\t\treturn index\n\t}\n\tfunction relationIndex(\n\t\ttable: string,\n\t\tfield: string,\n\t\tplan: ReturnType<Planner['plan']>,\n\t\tconditions: readonly IndexCondition[],\n\t): QueryIndex {\n\t\tconst indexes =\n\t\t\tfield === '_id' ? [{ name: 'by_id', fields: ['_id'] }] : metadata.tables[table].indexes\n\t\tconst length = (index: QueryIndex) => {\n\t\t\tlet size = 1\n\t\t\tfor (const key of index.fields.slice(1)) {\n\t\t\t\tif (!plan.bindings.has(key)) break\n\t\t\t\tsize++\n\t\t\t}\n\t\t\treturn size\n\t\t}\n\t\tconst condition = conditions.find((c) => c.value !== null && c.value !== undefined)\n\t\tif (condition) {\n\t\t\tconst chosen = indexes.find((i) => i.name === condition.index && i.fields[0] === field)\n\t\t\tif (!chosen)\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Explicit index \"${condition.index}\" is incompatible with declared relationship lookup`,\n\t\t\t\t)\n\t\t\treturn chosen\n\t\t}\n\t\tconst candidates = indexes\n\t\t\t.filter((i) => i.fields[0] === field)\n\t\t\t.sort(\n\t\t\t\t(a, b) =>\n\t\t\t\t\tlength(b) - length(a) ||\n\t\t\t\t\ta.name.localeCompare(b.name) ||\n\t\t\t\t\ta.fields.join('\\0').localeCompare(b.fields.join('\\0')),\n\t\t\t)\n\t\tif (!candidates.length) throw new RelationshipError('INVALID_RELATION')\n\t\treturn candidates[0]\n\t}\n\tfunction relationPrefix(\n\t\tindex: QueryIndex,\n\t\tvalue: FilterScalar,\n\t\tplan: ReturnType<Planner['plan']>,\n\t) {\n\t\tconst prefix = [{ field: index.fields[0], value }]\n\t\tfor (const field of index.fields.slice(1)) {\n\t\t\tif (!plan.bindings.has(field)) break\n\t\t\tprefix.push({ field, value: plan.bindings.get(field)! })\n\t\t}\n\t\treturn prefix\n\t}\n\n\tasync function get(table: string, id: Value | undefined): Promise<Document | null> {\n\t\tplanner.beforeLeaf(table)\n\t\t// SAFETY: native normalization rejects non-string/non-table IDs before any read.\n\t\tif (id == null || !request.db.normalizeId(table, id as string)) return null\n\t\t// SAFETY: normalizeId verifies the declared native table before the authorized metered get.\n\t\tconst result = await request.db.get(table, id as GenericId<string>)\n\t\tplanner.assertUnchanged()\n\t\t// SAFETY: normalizeId checked the table; the authorized native get retains system fields.\n\t\treturn result as Document | null\n\t}\n\tasync function* targets(\n\t\ttable: string,\n\t\tfield: string,\n\t\tvalue: Value | undefined,\n\t\tdepth: number,\n\t): AsyncGenerator<Document> {\n\t\trequest.expand(depth, 0)\n\t\tif (value == null) return\n\t\tif (field === '_id') {\n\t\t\tconst target = await get(table, value)\n\t\t\tif (target) {\n\t\t\t\trequest.expand(depth)\n\t\t\t\tyield target\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tplanner.beforeLeaf(table)\n\t\tconst index = lookup(table, field)\n\t\t// SAFETY: registry validates the indexed native lookup prefix and compatible values.\n\t\tconst source = request.leafQuery(table).withIndex(index.name, (q) => q.eq(field, value))\n\t\tconst iterator = source[Symbol.asyncIterator]()\n\t\ttry {\n\t\t\twhile (true) {\n\t\t\t\tplanner.assertUnchanged()\n\t\t\t\tplanner.beforeLeaf(table)\n\t\t\t\tconst next = await iterator.next()\n\t\t\t\tplanner.assertUnchanged()\n\t\t\t\tif (next.done) return\n\t\t\t\tconst permitted = await request.authorize(table, next.value)\n\t\t\t\t// Denials are an await boundary too; never hide them inside an authorized iterator.\n\t\t\t\tplanner.assertUnchanged()\n\t\t\t\tif (!permitted) continue\n\t\t\t\trequest.expand(depth)\n\t\t\t\t// SAFETY: native rows include system fields and explicit authorization just completed.\n\t\t\t\tyield next.value as Document\n\t\t\t}\n\t\t} finally {\n\t\t\tawait iterator.return?.()\n\t\t}\n\t}\n\tasync function* traverse(\n\t\tdeclared: Edge,\n\t\tparent: Document,\n\t\tdepth: number,\n\t): AsyncGenerator<Document> {\n\t\trequest.expand(depth, 0)\n\t\tconst value = parent[base(declared.from).field]\n\t\tif (value == null) return\n\t\tif ('base' in declared.from && 'base' in declared.to) {\n\t\t\tfor await (const junction of targets(\n\t\t\t\tdeclared.from.junction.table,\n\t\t\t\tdeclared.from.junction.field,\n\t\t\t\tvalue,\n\t\t\t\tdepth,\n\t\t\t))\n\t\t\t\tyield* targets(\n\t\t\t\t\tdeclared.target,\n\t\t\t\t\tdeclared.to.base.field,\n\t\t\t\t\tjunction[declared.to.junction.field],\n\t\t\t\t\tdepth,\n\t\t\t\t)\n\t\t} else yield* targets(declared.target, base(declared.to).field, value, depth)\n\t}\n\tasync function one(declared: Edge, parent: Document, depth: number) {\n\t\tlet match: Document | null = null\n\t\tfor await (const target of traverse(declared, parent, depth)) {\n\t\t\tif (match) throw new RelationshipError('AMBIGUOUS_RELATION')\n\t\t\tmatch = target\n\t\t}\n\t\treturn match\n\t}\n\tasync function matches(\n\t\ttable: string,\n\t\tparent: Document,\n\t\tast: FilterAst,\n\t\tdepth = 0,\n\t): Promise<boolean> {\n\t\trequest.expand(depth, 0)\n\t\tplanner.assertUnchanged()\n\t\tif (ast.kind === 'scalar') return scalarMatches(ast, parent)\n\t\tif (ast.kind === 'not') return !(await matches(table, parent, ast.child, depth))\n\t\tif (ast.kind === 'and' || ast.kind === 'or') {\n\t\t\tfor (const child of ast.children) {\n\t\t\t\tconst result = await matches(table, parent, child, depth)\n\t\t\t\tif (ast.kind === 'and' && !result) return false\n\t\t\t\tif (ast.kind === 'or' && result) return true\n\t\t\t}\n\t\t\treturn ast.kind === 'and'\n\t\t}\n\t\tif (ast.kind !== 'relation') throw new RelationshipError('INVALID_RELATION')\n\t\tconst declared = edge(table, ast.name)\n\t\tif (ast.mode === 'one') {\n\t\t\tconst target = await one(declared, parent, depth + 1)\n\t\t\treturn ast.predicate === null\n\t\t\t\t? target === null\n\t\t\t\t: target !== null && (await matches(declared.target, target, ast.predicate, depth + 1))\n\t\t}\n\t\tfor await (const target of traverse(declared, parent, depth + 1))\n\t\t\tif (await matches(declared.target, target, ast.predicate!, depth + 1))\n\t\t\t\treturn ast.mode === 'some'\n\t\t// Only actual authorized traversal exhaustion proves none.\n\t\treturn ast.mode === 'none'\n\t}\n\tasync function output(\n\t\ttable: string,\n\t\tparent: Document,\n\t\tselection: OutputSelection,\n\t\targs: NativePagination & { order?: 'asc' | 'desc' },\n\t\tpath: readonly string[] = [],\n\t\tdepth = 0,\n\t\tparents: readonly string[] = [],\n\t): Promise<Record<string, Value>> {\n\t\trequest.expand(depth, 0)\n\t\t// SAFETY: the registry strictly validates this public native DTO projection.\n\t\tconst dto = planner.complete(table, parent, true, { kind: 'and', children: [] }) as Record<\n\t\t\tstring,\n\t\t\tValue\n\t\t>\n\t\tfor (const [name, selected] of Object.entries(selection)) {\n\t\t\tconst declared = edge(table, name),\n\t\t\t\tnested = selected === true ? {} : selected\n\t\t\tif (declared.cardinality === 'one') {\n\t\t\t\tconst target = await one(declared, parent, depth + 1)\n\t\t\t\tdto[name] = target\n\t\t\t\t\t? await output(declared.target, target, nested, args, [...path, name], depth + 1, [\n\t\t\t\t\t\t\t...parents,\n\t\t\t\t\t\t\tparent._id,\n\t\t\t\t\t\t])\n\t\t\t\t\t: null\n\t\t\t} else {\n\t\t\t\tconst page = await pageRelation(\n\t\t\t\t\ttable,\n\t\t\t\t\tparent,\n\t\t\t\t\tname,\n\t\t\t\t\tnested,\n\t\t\t\t\t{ numItems: args.numItems, cursor: null, order: args.order },\n\t\t\t\t\t[],\n\t\t\t\t\t[],\n\t\t\t\t\t[...path, name],\n\t\t\t\t\tdepth + 1,\n\t\t\t\t\t[...parents, parent._id],\n\t\t\t\t)\n\t\t\t\t// SAFETY: the declared page validator has parsed this nested DTO page.\n\t\t\t\tdto[name] = page as Value\n\t\t\t}\n\t\t}\n\t\treturn dto\n\t}\n\tfunction prepare(\n\t\ttable: string,\n\t\tname: string,\n\t\tselection: OutputSelection,\n\t\tinput: QueryInput,\n\t\tconditions: readonly IndexCondition[],\n\t\tconstraints: readonly StoredConstraint[],\n\t\tpath: readonly string[],\n\t\trootId: string,\n\t) {\n\t\tplanner.page(edge(table, name).target, selection)\n\t\tassertRelationshipDepth(selection, path.length)\n\t\tconst declared = edge(table, name)\n\t\tif (declared.cardinality !== 'many') throw new RelationshipError('INVALID_RELATION')\n\t\tconst plan = planner.plan(declared.target, input, [], constraints)\n\t\tconst from = declared.from,\n\t\t\tto = declared.to\n\t\tconst through =\n\t\t\t'base' in from && 'base' in to\n\t\t\t\t? {\n\t\t\t\t\t\ttable: from.junction.table,\n\t\t\t\t\t\tfield: from.junction.field,\n\t\t\t\t\t\ttargetField: to.base.field,\n\t\t\t\t\t\ttargetReference: to.junction.field,\n\t\t\t\t\t}\n\t\t\t\t: null\n\t\tconst sourceTable = through ? through.table : declared.target\n\t\tconst field = through ? through.field : base(to).field\n\t\tconst targetIndex = relationIndex(\n\t\t\tdeclared.target,\n\t\t\tthrough ? through.targetField : field,\n\t\t\tplan,\n\t\t\tconditions,\n\t\t)\n\t\tconst index = through ? lookup(sourceTable, field) : targetIndex\n\t\t// SAFETY: planner.page validated the complete output mask before cursor binding.\n\t\tconst binding: CursorBinding = {\n\t\t\t...rootBinding,\n\t\t\ttable: sourceTable,\n\t\t\tindex: index.name,\n\t\t\torder: plan.args.order ?? 'asc',\n\t\t\tclientFilters: plan.clientIdentity,\n\t\t\tserverFilters: {\n\t\t\t\tpredicates: plan.serverIdentity,\n\t\t\t\tselection: selection as Value,\n\t\t\t\tscope: { ...request.scope },\n\t\t\t\trootTable: rootBinding.table,\n\t\t\t\tlookupIndex: targetIndex.name,\n\t\t\t},\n\t\t\trelationPath: path,\n\t\t\tparent: rootId,\n\t\t}\n\t\tfunction open(token: string) {\n\t\t\ttry {\n\t\t\t\treturn navigation.parse(JSON.parse(codec.open(token, binding)))\n\t\t\t} catch {\n\t\t\t\tthrow new CursorError()\n\t\t\t}\n\t\t}\n\t\treturn { declared, plan, through, sourceTable, field, index, targetIndex, binding, open }\n\t}\n\tasync function pageRelation(\n\t\ttable: string,\n\t\tparent: Document | null,\n\t\tname: string,\n\t\tselection: OutputSelection,\n\t\tinput: QueryInput,\n\t\tconditions: readonly IndexCondition[] = [],\n\t\tconstraints: readonly StoredConstraint[] = [],\n\t\tpath: readonly string[] = [name],\n\t\tdepth = 1,\n\t\tparents: readonly string[] = [],\n\t\tprepared = prepare(\n\t\t\ttable,\n\t\t\tname,\n\t\t\tselection,\n\t\t\tinput,\n\t\t\tconditions,\n\t\t\tconstraints,\n\t\t\tpath,\n\t\t\tparents[0] ?? parent!._id,\n\t\t),\n\t\topened?: { cursor: string | null; endCursor?: string | null },\n\t) {\n\t\trequest.expand(depth, 0)\n\t\tconst { declared, plan, through, sourceTable, field, index, targetIndex, binding } = prepared\n\t\tconst value = parent?.[base(declared.from).field]\n\t\tconst args = { ...plan.args, ...(opened ?? { cursor: null }) }\n\t\t// SAFETY: declarations accept only native scalar lookup values; nullish references are absence.\n\t\tconst lookupPlan = {\n\t\t\t...plan,\n\t\t\tindex,\n\t\t\tprefix:\n\t\t\t\tvalue == null\n\t\t\t\t\t? []\n\t\t\t\t\t: through\n\t\t\t\t\t\t? [{ field, value: value as FilterScalar }]\n\t\t\t\t\t\t: relationPrefix(index, value as FilterScalar, plan),\n\t\t\tunsatisfiable: value == null || plan.unsatisfiable,\n\t\t}\n\t\tconst indexedTarget = through && through.targetField !== '_id'\n\t\tconst result = await executeStream(\n\t\t\tschema,\n\t\t\trequest,\n\t\t\tsourceTable,\n\t\t\tlookupPlan,\n\t\t\tplanner,\n\t\t\tcodec,\n\t\t\tbinding,\n\t\t\targs,\n\t\t\tasync (row, permitted) => {\n\t\t\t\tif (!permitted) return null\n\t\t\t\trequest.expand(depth)\n\t\t\t\tlet target: Document | null\n\t\t\t\tif (through && !indexedTarget)\n\t\t\t\t\ttarget = await get(declared.target, row[through.targetReference])\n\t\t\t\telse {\n\t\t\t\t\t// SAFETY: native direct/flattened target streams retain validated system fields.\n\t\t\t\t\ttarget = row as Document\n\t\t\t\t}\n\t\t\t\tif (!target || !(await matches(declared.target, target, plan.residual, depth))) return null\n\t\t\t\treturn output(declared.target, target, selection, plan.args, path, depth, parents)\n\t\t\t},\n\t\t\t(raw) => codec.seal(JSON.stringify({ cursor: raw, ancestors: parents.slice(1) }), binding),\n\t\t\tindexedTarget\n\t\t\t\t? (source, db) => {\n\t\t\t\t\t\t// SAFETY: the registry owns the native schema/index descriptors. Public\n\t\t\t\t\t\t// helper flatMap concatenates native outer/inner keys and paginates them.\n\t\t\t\t\t\tconst nativeSchema = schema as SchemaDefinition<\n\t\t\t\t\t\t\tRecord<\n\t\t\t\t\t\t\t\tstring,\n\t\t\t\t\t\t\t\timport('convex/server').TableDefinition<any, Record<string, string[]>>\n\t\t\t\t\t\t\t>,\n\t\t\t\t\t\t\tboolean\n\t\t\t\t\t\t>\n\t\t\t\t\t\tconst fields = getIndexFields(declared.target, targetIndex.name, nativeSchema)\n\t\t\t\t\t\treturn source.flatMap(async (junction) => {\n\t\t\t\t\t\t\trequest.expand(depth)\n\t\t\t\t\t\t\tconst permitted = await request.authorize(sourceTable, junction)\n\t\t\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\t\t\tconst reference = junction[through.targetReference]\n\t\t\t\t\t\t\tif (!permitted || reference == null)\n\t\t\t\t\t\t\t\treturn new EmptyStream(plan.args.order ?? 'asc', fields)\n\t\t\t\t\t\t\treturn stream(db, nativeSchema)\n\t\t\t\t\t\t\t\t.query(declared.target)\n\t\t\t\t\t\t\t\t.withIndex(targetIndex.name, (q) => {\n\t\t\t\t\t\t\t\t\t// SAFETY: registry validates the native leading lookup; validated filters supply each consecutive trailing binding.\n\t\t\t\t\t\t\t\t\tlet range = q as IndexRangeBuilder<any, string[], number>\n\t\t\t\t\t\t\t\t\t// SAFETY: junction references are native scalar Values validated by the declared lookup.\n\t\t\t\t\t\t\t\t\tconst prefix = relationPrefix(targetIndex, reference as FilterScalar, plan)\n\t\t\t\t\t\t\t\t\tfor (const binding of prefix) {\n\t\t\t\t\t\t\t\t\t\t// SAFETY: native eq advances the validated consecutive index prefix.\n\t\t\t\t\t\t\t\t\t\trange = range.eq(binding.field, binding.value) as IndexRangeBuilder<\n\t\t\t\t\t\t\t\t\t\t\tany,\n\t\t\t\t\t\t\t\t\t\t\tstring[],\n\t\t\t\t\t\t\t\t\t\t\tnumber\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn range\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.order(plan.args.order ?? 'asc')\n\t\t\t\t\t\t}, fields)\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t\tindexedTarget ? declared.target : sourceTable,\n\t\t)\n\t\tplanner.assertUnchanged()\n\t\treturn planner.page(declared.target, selection).parse(result)\n\t}\n\tasync function continuation(\n\t\trootTable: string,\n\t\trootId: string,\n\t\tpath: readonly string[],\n\t\tselection: OutputSelection,\n\t\tinput: QueryInput,\n\t\tconditions: readonly IndexCondition[],\n\t\tconstraints: readonly StoredConstraint[],\n\t) {\n\t\tif (!path.length || path.length > 4 || !request.db.normalizeId(rootTable, rootId))\n\t\t\tthrow new RelationshipError('INVALID_RELATION')\n\t\tlet table = rootTable\n\t\tconst declarations = path.map((name) => {\n\t\t\tconst declaration = edge(table, name)\n\t\t\ttable = declaration.target\n\t\t\treturn declaration\n\t\t})\n\t\tconst final = declarations.at(-1)!\n\t\tconst prepared = prepare(\n\t\t\tfinal.source,\n\t\t\tpath.at(-1)!,\n\t\t\tselection,\n\t\t\tinput,\n\t\t\tconditions,\n\t\t\tconstraints,\n\t\t\tpath,\n\t\t\trootId,\n\t\t)\n\t\t// Authenticate both slots and native ancestor IDs before ANY parent/traversal read.\n\t\tconst start = input.cursor === null ? null : prepared.open(input.cursor)\n\t\tconst end = input.endCursor == null ? null : prepared.open(input.endCursor)\n\t\tconst ancestors = start?.ancestors ?? end?.ancestors ?? []\n\t\tif (\n\t\t\tancestors.length !== path.length - 1 ||\n\t\t\t(start && end && JSON.stringify(start.ancestors) !== JSON.stringify(end.ancestors)) ||\n\t\t\tancestors.some((id, i) => !request.db.normalizeId(declarations[i].target, id))\n\t\t)\n\t\t\tthrow new CursorError()\n\t\t// Declarative input/index/cursor validation above retains its pre-read errors,\n\t\t// just as on the root path. Every awaited read, ancestry proof and page is protected.\n\t\ttry {\n\t\t\tlet parent = await get(rootTable, rootId)\n\t\t\tfor (let i = 0; parent && i < ancestors.length; i++) {\n\t\t\t\tconst declared = declarations[i]\n\t\t\t\tlet found: Document | null = null\n\t\t\t\tif (declared.cardinality === 'one') {\n\t\t\t\t\tconst target = await one(declared, parent, i + 1)\n\t\t\t\t\tif (target?._id === ancestors[i]) found = target\n\t\t\t\t} else\n\t\t\t\t\tfor await (const target of traverse(declared, parent, i + 1)) {\n\t\t\t\t\t\tif (target._id === ancestors[i]) {\n\t\t\t\t\t\t\tfound = target\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tparent = found\n\t\t\t}\n\t\t\treturn await pageRelation(\n\t\t\t\tfinal.source,\n\t\t\t\tparent,\n\t\t\t\tpath.at(-1)!,\n\t\t\t\tselection,\n\t\t\t\tinput,\n\t\t\t\tconditions,\n\t\t\t\tconstraints,\n\t\t\t\tpath,\n\t\t\t\tpath.length,\n\t\t\t\t[rootId, ...ancestors],\n\t\t\t\tprepared,\n\t\t\t\t{ cursor: start?.cursor ?? null, endCursor: end?.cursor ?? input.endCursor },\n\t\t\t)\n\t\t} catch (error) {\n\t\t\trethrowQueryError(error)\n\t\t}\n\t}\n\treturn { matches, output, continuation, edge }\n}\n"],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import { Rules } from "../tenancy.mjs";
2
+ import { GenericDataModel, GenericDatabaseReader, GenericDocument, QueryInitializer } from "convex/server";
3
+ //#region src/querying/security.d.ts
4
+ export declare const MAX_DOCUMENT_BYTES: number;
5
+ /** Additive context supplied to existing read/insert/modify rules on BOTH paths.
6
+ * Reads through db are authorized and bounded. collect, native paginate/filter,
7
+ * search, and system reads fail closed; use get, take/first/unique, or iteration.
8
+ * Trusted server callbacks can close
9
+ * over other readers; arbitrary captured JavaScript cannot be sandboxed here.
10
+ */
11
+ export interface SecurityPolicyContext<DM extends GenericDataModel> {
12
+ readonly db: GenericDatabaseReader<DM>;
13
+ }
14
+ export interface ManagedScope {
15
+ readonly mode: 'authenticated' | 'system' | 'trusted';
16
+ readonly principal: string | null;
17
+ readonly organization: string | null;
18
+ readonly tenant: string | null;
19
+ readonly role: string | null;
20
+ }
21
+ export interface ManagedBudgets {
22
+ readOperations: number;
23
+ indexRanges: number;
24
+ rawCandidates: number;
25
+ documentBytes: number;
26
+ fanout: number;
27
+ depth: number;
28
+ memoEntries: number;
29
+ }
30
+ export declare class ManagedReadError extends Error {
31
+ readonly code: 'UNMANAGED_QUERY_CONTEXT' | 'MANAGED_READ_BUDGET' | 'POLICY_RECURSION' | 'UNSUPPORTED_MANAGED_READ';
32
+ constructor(code: 'UNMANAGED_QUERY_CONTEXT' | 'MANAGED_READ_BUDGET' | 'POLICY_RECURSION' | 'UNSUPPORTED_MANAGED_READ');
33
+ }
34
+ interface Authority<DM extends GenericDataModel> {
35
+ scope: ManagedScope;
36
+ rules: Rules<SecurityPolicyContext<DM>, DM>;
37
+ defaultPolicy: 'allow' | 'deny';
38
+ }
39
+ /** Unique closure-only ordinary facade: helper implementation fields never leak.
40
+ * Ordinary operations preserve their native/helper behavior and receive no new
41
+ * engine ceilings. Register only from query constructors, never mutations/actions.
42
+ */
43
+ export declare function registerQueryReader<DM extends GenericDataModel>(raw: GenericDatabaseReader<DM>, ordinary: GenericDatabaseReader<DM>, authority: Authority<DM>): GenericDatabaseReader<DM>;
44
+ export interface ManagedCounters {
45
+ readonly readOperations: number;
46
+ readonly indexRanges: number;
47
+ readonly rawCandidates: number;
48
+ readonly documentBytesObserved: number;
49
+ readonly documentBytesReserved: number;
50
+ readonly fanout: number;
51
+ readonly memoEntries: number;
52
+ }
53
+ export interface LeafReservation {
54
+ /** Settle/refund exactly once, including missing/error results (null). */
55
+ settle(document: GenericDocument | null): void;
56
+ }
57
+ export interface ManagedReadRequest<DM extends GenericDataModel> {
58
+ readonly db: GenericDatabaseReader<DM>;
59
+ readonly scope: ManagedScope & {
60
+ readonly policyVersion: string;
61
+ };
62
+ readonly budgets: Readonly<ManagedBudgets>;
63
+ readonly counters: ManagedCounters;
64
+ /** Future native leaf adapter reserves BEFORE every actual next/get, not an outer stream. */
65
+ reserveLeaf(): LeafReservation;
66
+ openRange(): void;
67
+ expand(depth: number, count?: number): void;
68
+ /** Raw candidate visibility for the private executor; every leaf is metered.
69
+ * Its iterator reserves each next internally; do not add an outer reservation.
70
+ * Eligibility/authorization must complete before its key is eligible to advance.
71
+ */
72
+ leafQuery(table: string): QueryInitializer<any>;
73
+ authorize(table: string, document: GenericDocument): Promise<boolean>;
74
+ }
75
+ export interface ManagedSecurityConfig<DM extends GenericDataModel> {
76
+ /** Entire registry: parent/junction/target/policy dependencies share one request. */
77
+ tables: readonly string[];
78
+ policyVersion: string;
79
+ budgets?: Partial<ManagedBudgets>;
80
+ /** Central server trust assertion. Never inspect third-party wrapper fields.
81
+ * The adapter must attest a QUERY transaction reader and the exact effective
82
+ * composed policy and immutable scope. It assumes responsibility for that claim.
83
+ */
84
+ trustedAdapter?: (ctx: ManagedQueryContext<DM>) => (Authority<DM> & {
85
+ reader: GenericDatabaseReader<DM>;
86
+ }) | null;
87
+ }
88
+ /** Native writer/action contexts are not accepted by the engine contract. */
89
+ export type ManagedQueryContext<DM extends GenericDataModel> = {
90
+ db: GenericDatabaseReader<DM> & {
91
+ insert?: never;
92
+ patch?: never;
93
+ replace?: never;
94
+ delete?: never;
95
+ };
96
+ };
97
+ /** Central construction, then one shared request per engine invocation.
98
+ * Requests may lower, never raise ceilings. Unknown/native contexts fail closed.
99
+ */
100
+ export declare function createManagedSecurity<DM extends GenericDataModel = GenericDataModel>(config: ManagedSecurityConfig<DM>): <Ctx extends ManagedQueryContext<DM>>(ctx: Ctx, lower?: Partial<ManagedBudgets>) => ManagedReadRequest<DM>;
101
+ /** Ordinary rule invocations share a policy-only meter. Ordinary db reads stay
102
+ * unmetered by engine ceilings; supplied rule dependencies never bypass policy.
103
+ * Mutation insert/modify receive the same supplied contract as read callbacks.
104
+ */
105
+ export declare function suppliedPolicyRules<DM extends GenericDataModel>(raw: GenericDatabaseReader<DM>, rules: Rules<SecurityPolicyContext<DM>, DM>, defaultPolicy: 'allow' | 'deny'): Rules<SecurityPolicyContext<DM>, DM>;
106
+ //#endregion
107
+ //# sourceMappingURL=security.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{getDocumentSize as e}from"convex/values";const t=1048576,n=Object.freeze({readOperations:4096,indexRanges:1024,rawCandidates:8192,documentBytes:16*t,fanout:1024,depth:8,memoEntries:4096});var r=class extends Error{code;constructor(e){super(e),this.code=e,this.name=`ManagedReadError`}};const i=/* @__PURE__ */ new WeakMap;function a(e,t,n){let r=Object.freeze({get:t.get.bind(t),query:e=>c(t.query(e)),normalizeId:t.normalizeId.bind(t),system:Object.freeze({get:t.system.get.bind(t.system),query:e=>c(t.system.query(e)),normalizeId:t.system.normalizeId.bind(t.system)})});return i.set(r,{reader:e,...u(n)}),r}function o(e){if(!e.policyVersion||!e.tables.length||new Set(e.tables).size!==e.tables.length||e.tables.some(e=>!e||e.startsWith(`_`)))throw Error(`Invalid managed security configuration`);let t=new Set(e.tables);l(e.budgets);let a=Object.freeze({...n,...e.budgets}),o=e.policyVersion,s=e.trustedAdapter;return(e,n)=>{let c=i.get(e.db),f=c?void 0:s?.(e);if(!c&&!f)throw new r(`UNMANAGED_QUERY_CONTEXT`);return d(c??{...f,...u(f)},t,l(n,a),o)}}function s(e,t,r){let i=d({reader:e,...u({rules:t,defaultPolicy:r,scope:{mode:`trusted`,principal:null,organization:null,tenant:null,role:null}})},null,n,`ordinary-policy`,!1),a={};for(let e of Object.keys(t)){let n=t[e],r={};n?.read&&(r.read=(t,n)=>i.authorize(e,n)),n?.insert&&(r.insert=(e,t)=>n.insert({db:i.db},t)),n?.modify&&(r.modify=(e,t)=>n.modify({db:i.db},t)),a[e]=r}return a}function c(e){return Object.freeze({fullTableScan:()=>c(e.fullTableScan()),withIndex:(...t)=>c(e.withIndex(...t)),withSearchIndex:(...t)=>c(e.withSearchIndex(...t)),filter:(...t)=>c(e.filter(...t)),order:(...t)=>c(e.order(...t)),paginate:e.paginate.bind(e),collect:e.collect.bind(e),take:e.take.bind(e),first:e.first.bind(e),unique:e.unique.bind(e),[Symbol.asyncIterator]:()=>{let t=e[Symbol.asyncIterator](),n={next:t.next.bind(t)};return t.return&&(n.return=t.return.bind(t)),t.throw&&(n.throw=t.throw.bind(t)),Object.freeze(n)}})}function l(e={},t=n){for(let[t,r]of Object.entries(e))if(!Object.hasOwn(n,t)||!Number.isSafeInteger(r)||r<=0)throw Error(`Invalid managed read budgets`);return Object.freeze({readOperations:Math.min(t.readOperations,e.readOperations??t.readOperations),indexRanges:Math.min(t.indexRanges,e.indexRanges??t.indexRanges),rawCandidates:Math.min(t.rawCandidates,e.rawCandidates??t.rawCandidates),documentBytes:Math.min(t.documentBytes,e.documentBytes??t.documentBytes),fanout:Math.min(t.fanout,e.fanout??t.fanout),depth:Math.min(t.depth,e.depth??t.depth),memoEntries:Math.min(t.memoEntries,e.memoEntries??t.memoEntries)})}function u(e){let t=Object.fromEntries(Object.entries(e.rules).map(([e,t])=>[e,Object.freeze({...t})]));return Object.freeze({scope:Object.freeze({...e.scope}),rules:Object.freeze(t),defaultPolicy:e.defaultPolicy})}function d(n,i,a,o,s=!0){let c={readOperations:0,indexRanges:0,rawCandidates:0,documentBytesObserved:0,documentBytesReserved:0,fanout:0,memoEntries:0},l=Object.freeze({get readOperations(){return c.readOperations},get indexRanges(){return c.indexRanges},get rawCandidates(){return c.rawCandidates},get documentBytesObserved(){return c.documentBytesObserved},get documentBytesReserved(){return c.documentBytesReserved},get fanout(){return c.fanout},get memoEntries(){return c.memoEntries}}),u=/* @__PURE__ */ new Map,d=0,f=0,p=()=>{throw new r(`MANAGED_READ_BUDGET`)},m=e=>{if(e.startsWith(`_`)||i&&!i.has(e))throw new r(`UNSUPPORTED_MANAGED_READ`)},h=()=>{c.indexRanges>=a.indexRanges&&p(),c.indexRanges++},g=()=>{(c.readOperations>=a.readOperations||c.rawCandidates+f>=a.rawCandidates||c.documentBytesObserved+c.documentBytesReserved+1048576>a.documentBytes)&&p(),c.readOperations++,f++,c.documentBytesReserved+=t;let n=!1;return{settle(r){if(n)throw Error(`Leaf reservation already settled`);if(n=!0,f--,c.documentBytesReserved-=t,r){c.rawCandidates++;let t=e(r);c.documentBytesObserved+=t,t>1048576&&p()}}}},_=(e,t)=>JSON.stringify([e,t]),v=(e,t)=>{if(t.includes(e))throw new r(`POLICY_RECURSION`);t.length>=a.depth&&p()};async function y(e,t,i=[]){m(e);let a=t._id;if(!n.reader.normalizeId(e,a))throw new r(`UNSUPPORTED_MANAGED_READ`);let o=_(e,a);v(o,i);let s=n.rules[e]?.read;return s?s(Object.freeze({db:S([...i,o])}),t):n.defaultPolicy===`allow`}async function b(e,t,r){m(e);let i=_(e,t);if(v(i,r),u.has(i))return u.get(i);s&&c.memoEntries>=a.memoEntries&&p(),s&&(d++,c.memoEntries=u.size+d);try{h();let a=g(),o;try{o=await n.reader.get(e,t)}catch(e){throw a.settle(null),e}a.settle(o);let c=o&&await y(e,o,r)?o:null;return s&&u.set(i,c),c}finally{s&&(d--,c.memoEntries=u.size+d)}}function x(e,t,n,i){function o(){throw new r(`UNSUPPORTED_MANAGED_READ`)}async function*s(){h();let r;try{for(;;){let a=g(),o;try{r??=t[Symbol.asyncIterator](),o=await r.next()}catch(e){throw a.settle(null),e}if(a.settle(o.done?null:o.value),o.done)return;(!i||await y(e,o.value,n))&&(yield o.value)}}finally{await r?.return?.()}}async function c(e){if(!Number.isSafeInteger(e)||e<0||e>a.rawCandidates)throw new r(`MANAGED_READ_BUDGET`);let t=[];if(e===0)return t;for await(let n of s())if(t.push(n),t.length===e)break;return t}return Object.freeze({fullTableScan:()=>x(e,t.fullTableScan(),n,i),withIndex:(...r)=>x(e,t.withIndex(...r),n,i),order:(...r)=>x(e,t.order(...r),n,i),filter:o,withSearchIndex:o,paginate:o,collect:o,take:c,first:async()=>(await c(1))[0]??null,unique:async()=>{let e=await c(2);if(e.length>1)throw Error(`Query is not unique`);return e[0]??null},[Symbol.asyncIterator]:s})}function S(e){let t=()=>{throw new r(`UNSUPPORTED_MANAGED_READ`)};return Object.freeze({get:async(t,r)=>{if(r!==void 0)return b(t,r,e);let a=[...i??new Set(Object.keys(n.rules))].find(e=>n.reader.normalizeId(e,t));return a?b(a,t,e):null},query:t=>(m(t),x(t,n.reader.query(t),e,!0)),normalizeId:(e,t)=>(m(e),n.reader.normalizeId(e,t)),system:Object.freeze({get:t,query:t,normalizeId:t})})}return Object.freeze({db:S([]),scope:Object.freeze({...n.scope,policyVersion:o}),budgets:a,counters:l,reserveLeaf:g,openRange:h,expand(e,t=1){(!Number.isSafeInteger(e)||e<0||e>a.depth||!Number.isSafeInteger(t)||t<0||c.fanout+t>a.fanout)&&p(),c.fanout+=t},leafQuery:e=>(m(e),x(e,n.reader.query(e),[],!1)),authorize:(e,t)=>y(e,t)})}export{t as MAX_DOCUMENT_BYTES,r as ManagedReadError,o as createManagedSecurity,a as registerQueryReader,s as suppliedPolicyRules};
2
+ //# sourceMappingURL=security.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.mjs","names":["MAX_DOCUMENT_BYTES","defaultBudgets","ManagedReadError","code","capabilities","registerQueryReader","facade","ordinary","ordinaryQuery","table","raw","snapshot","authority","createManagedSecurity","config","tables","limits","central","policyVersion","trustedAdapter","registered","ctx","adapted","request","lower","suppliedPolicyRules","session","result","rules","operations","wrapped","doc","source","args","iterator","key","value","overrides","counts","view","memo","pendingMemo","pendingCandidates","fail","assertTable","openRange","budgets","reserveLeaf","settled","document","size","getDocumentSize","keyOf","id","checkCycle","ancestors","authorize","cap","rule","reader","get","memoize","reservation","error","query","unsupported","iterate","next","authorized","take","n","docs","blocked","arg1","arg0","name","depth","count"],"sources":["../../src/querying/security.ts"],"sourcesContent":["/** Internal managed-read contracts for the relational executor. No root/package export. */\nimport type {\n\tGenericDataModel,\n\tGenericDatabaseReader,\n\tGenericDocument,\n\tGenericTableInfo,\n\tQueryInitializer,\n} from 'convex/server'\nimport { getDocumentSize, type GenericId } from 'convex/values'\nimport type { Rules } from '../tenancy'\n\nexport const MAX_DOCUMENT_BYTES = 1024 * 1024\n\n/** Additive context supplied to existing read/insert/modify rules on BOTH paths.\n * Reads through db are authorized and bounded. collect, native paginate/filter,\n * search, and system reads fail closed; use get, take/first/unique, or iteration.\n * Trusted server callbacks can close\n * over other readers; arbitrary captured JavaScript cannot be sandboxed here.\n */\nexport interface SecurityPolicyContext<DM extends GenericDataModel> {\n\treadonly db: GenericDatabaseReader<DM>\n}\n\nexport interface ManagedScope {\n\treadonly mode: 'authenticated' | 'system' | 'trusted'\n\treadonly principal: string | null\n\treadonly organization: string | null\n\treadonly tenant: string | null\n\treadonly role: string | null\n}\n\nexport interface ManagedBudgets {\n\treadOperations: number\n\tindexRanges: number\n\trawCandidates: number\n\tdocumentBytes: number\n\tfanout: number\n\tdepth: number\n\tmemoEntries: number\n}\n\n/** Finite starting ceilings; U5/U6 adversarial workload evidence must tune them.\n * Document payload accounting is NOT Convex storage/index/prefetch I/O accounting.\n */\nconst defaultBudgets: Readonly<ManagedBudgets> = Object.freeze({\n\treadOperations: 4096,\n\tindexRanges: 1024,\n\trawCandidates: 8192,\n\tdocumentBytes: 16 * MAX_DOCUMENT_BYTES,\n\tfanout: 1024,\n\tdepth: 8,\n\tmemoEntries: 4096,\n})\n\nexport class ManagedReadError extends Error {\n\tconstructor(\n\t\treadonly code:\n\t\t\t| 'UNMANAGED_QUERY_CONTEXT'\n\t\t\t| 'MANAGED_READ_BUDGET'\n\t\t\t| 'POLICY_RECURSION'\n\t\t\t| 'UNSUPPORTED_MANAGED_READ',\n\t) {\n\t\tsuper(code)\n\t\tthis.name = 'ManagedReadError'\n\t}\n}\n\ninterface Authority<DM extends GenericDataModel> {\n\tscope: ManagedScope\n\trules: Rules<SecurityPolicyContext<DM>, DM>\n\tdefaultPolicy: 'allow' | 'deny'\n}\ninterface Capability<DM extends GenericDataModel> extends Authority<DM> {\n\treader: GenericDatabaseReader<DM>\n}\n// Type erasure stays inside the private identity map; registration retains native DM.\nconst capabilities = new WeakMap<object, Capability<any>>()\n\n/** Unique closure-only ordinary facade: helper implementation fields never leak.\n * Ordinary operations preserve their native/helper behavior and receive no new\n * engine ceilings. Register only from query constructors, never mutations/actions.\n */\nexport function registerQueryReader<DM extends GenericDataModel>(\n\traw: GenericDatabaseReader<DM>,\n\tordinary: GenericDatabaseReader<DM>,\n\tauthority: Authority<DM>,\n): GenericDatabaseReader<DM> {\n\t// SAFETY: all base-reader methods are bound forwards; the new identity hides helper fields.\n\tconst facade: GenericDatabaseReader<DM> = Object.freeze({\n\t\tget: ordinary.get.bind(ordinary),\n\t\tquery: <Table extends Parameters<typeof ordinary.query>[0]>(table: Table) =>\n\t\t\tordinaryQuery(ordinary.query(table)),\n\t\tnormalizeId: ordinary.normalizeId.bind(ordinary),\n\t\tsystem: Object.freeze({\n\t\t\tget: ordinary.system.get.bind(ordinary.system),\n\t\t\tquery: <Table extends Parameters<typeof ordinary.system.query>[0]>(table: Table) =>\n\t\t\t\tordinaryQuery(ordinary.system.query(table)),\n\t\t\tnormalizeId: ordinary.system.normalizeId.bind(ordinary.system),\n\t\t}),\n\t})\n\t// SAFETY: capabilities are retrieved using the same facade with its native DM.\n\tcapabilities.set(facade, { reader: raw, ...snapshot(authority) })\n\treturn facade\n}\n\nexport interface ManagedCounters {\n\treadonly readOperations: number\n\treadonly indexRanges: number\n\treadonly rawCandidates: number\n\treadonly documentBytesObserved: number\n\treadonly documentBytesReserved: number\n\treadonly fanout: number\n\treadonly memoEntries: number\n}\nexport interface LeafReservation {\n\t/** Settle/refund exactly once, including missing/error results (null). */\n\tsettle(document: GenericDocument | null): void\n}\nexport interface ManagedReadRequest<DM extends GenericDataModel> {\n\treadonly db: GenericDatabaseReader<DM>\n\treadonly scope: ManagedScope & { readonly policyVersion: string }\n\treadonly budgets: Readonly<ManagedBudgets>\n\treadonly counters: ManagedCounters\n\t/** Future native leaf adapter reserves BEFORE every actual next/get, not an outer stream. */\n\treserveLeaf(): LeafReservation\n\topenRange(): void\n\texpand(depth: number, count?: number): void\n\t/** Raw candidate visibility for the private executor; every leaf is metered.\n\t * Its iterator reserves each next internally; do not add an outer reservation.\n\t * Eligibility/authorization must complete before its key is eligible to advance.\n\t */\n\tleafQuery(table: string): QueryInitializer<any>\n\tauthorize(table: string, document: GenericDocument): Promise<boolean>\n}\n\nexport interface ManagedSecurityConfig<DM extends GenericDataModel> {\n\t/** Entire registry: parent/junction/target/policy dependencies share one request. */\n\ttables: readonly string[]\n\tpolicyVersion: string\n\tbudgets?: Partial<ManagedBudgets>\n\t/** Central server trust assertion. Never inspect third-party wrapper fields.\n\t * The adapter must attest a QUERY transaction reader and the exact effective\n\t * composed policy and immutable scope. It assumes responsibility for that claim.\n\t */\n\ttrustedAdapter?: (\n\t\tctx: ManagedQueryContext<DM>,\n\t) => (Authority<DM> & { reader: GenericDatabaseReader<DM> }) | null\n}\n\n/** Native writer/action contexts are not accepted by the engine contract. */\nexport type ManagedQueryContext<DM extends GenericDataModel> = {\n\tdb: GenericDatabaseReader<DM> & { insert?: never; patch?: never; replace?: never; delete?: never }\n}\n\n/** Central construction, then one shared request per engine invocation.\n * Requests may lower, never raise ceilings. Unknown/native contexts fail closed.\n */\nexport function createManagedSecurity<DM extends GenericDataModel = GenericDataModel>(\n\tconfig: ManagedSecurityConfig<DM>,\n) {\n\tif (\n\t\t!config.policyVersion ||\n\t\t!config.tables.length ||\n\t\tnew Set(config.tables).size !== config.tables.length ||\n\t\tconfig.tables.some((table) => !table || table.startsWith('_'))\n\t)\n\t\tthrow new Error('Invalid managed security configuration')\n\tconst tables = new Set(config.tables)\n\t// Central values may exceed starting defaults; these are application-owned ceilings.\n\tlimits(config.budgets)\n\tconst central = Object.freeze({ ...defaultBudgets, ...config.budgets })\n\tconst policyVersion = config.policyVersion\n\tconst trustedAdapter = config.trustedAdapter\n\treturn <Ctx extends ManagedQueryContext<DM>>(\n\t\tctx: Ctx,\n\t\tlower?: Partial<ManagedBudgets>,\n\t): ManagedReadRequest<DM> => {\n\t\t// SAFETY: identity is registered privately by the query constructor for this DM.\n\t\tconst registered = capabilities.get(ctx.db) as Capability<DM> | undefined\n\t\tconst adapted = registered ? undefined : trustedAdapter?.(ctx)\n\t\tif (!registered && !adapted) throw new ManagedReadError('UNMANAGED_QUERY_CONTEXT')\n\t\tconst cap = registered ?? { ...adapted!, ...snapshot(adapted!) }\n\t\treturn request(cap, tables, limits(lower, central), policyVersion)\n\t}\n}\n\n/** Ordinary rule invocations share a policy-only meter. Ordinary db reads stay\n * unmetered by engine ceilings; supplied rule dependencies never bypass policy.\n * Mutation insert/modify receive the same supplied contract as read callbacks.\n */\nexport function suppliedPolicyRules<DM extends GenericDataModel>(\n\traw: GenericDatabaseReader<DM>,\n\trules: Rules<SecurityPolicyContext<DM>, DM>,\n\tdefaultPolicy: 'allow' | 'deny',\n): Rules<SecurityPolicyContext<DM>, DM> {\n\tconst cap = {\n\t\treader: raw,\n\t\t...snapshot({\n\t\t\trules,\n\t\t\tdefaultPolicy,\n\t\t\tscope: { mode: 'trusted', principal: null, organization: null, tenant: null, role: null },\n\t\t}),\n\t}\n\t// Mutations can change dependencies between callbacks; never memoize ordinary\n\t// policy reads across writes. The shared policy meter still bounds all calls.\n\tconst session = request(cap, null, defaultBudgets, 'ordinary-policy', false)\n\tconst result: Rules<SecurityPolicyContext<DM>, DM> = {}\n\tfor (const table of Object.keys(rules)) {\n\t\tconst operations = rules[table]\n\t\tconst wrapped: NonNullable<typeof operations> = {}\n\t\tif (operations?.read) wrapped.read = (_ctx, doc) => session.authorize(table, doc)\n\t\tif (operations?.insert)\n\t\t\twrapped.insert = (_ctx, doc) => operations.insert!({ db: session.db }, doc)\n\t\tif (operations?.modify)\n\t\t\twrapped.modify = (_ctx, doc) => operations.modify!({ db: session.db }, doc)\n\t\t// SAFETY: the keys originated from this same Rules map.\n\t\tresult[table as keyof typeof result] = wrapped\n\t}\n\treturn result\n}\n\n/** Forward only native query methods, never the RLS helper's unfiltered fields.\n * Ordinary consumption retains its existing policy and has no engine ceilings.\n */\nfunction ordinaryQuery<T extends GenericTableInfo>(\n\tsource: QueryInitializer<T>,\n): QueryInitializer<T> {\n\t// SAFETY: native fluent methods retain the same table metadata; ordered results\n\t// only expose methods the native/helper source itself can successfully invoke.\n\treturn Object.freeze({\n\t\tfullTableScan: () => ordinaryQuery(source.fullTableScan() as QueryInitializer<T>),\n\t\twithIndex: (...args: Parameters<typeof source.withIndex>) =>\n\t\t\tordinaryQuery(source.withIndex(...args) as QueryInitializer<T>),\n\t\twithSearchIndex: (...args: Parameters<typeof source.withSearchIndex>) =>\n\t\t\tordinaryQuery(source.withSearchIndex(...args) as QueryInitializer<T>),\n\t\tfilter: (...args: Parameters<typeof source.filter>) =>\n\t\t\tordinaryQuery(source.filter(...args) as QueryInitializer<T>),\n\t\torder: (...args: Parameters<typeof source.order>) =>\n\t\t\tordinaryQuery(source.order(...args) as QueryInitializer<T>),\n\t\tpaginate: source.paginate.bind(source),\n\t\tcollect: source.collect.bind(source),\n\t\ttake: source.take.bind(source),\n\t\tfirst: source.first.bind(source),\n\t\tunique: source.unique.bind(source),\n\t\t[Symbol.asyncIterator]: () => {\n\t\t\tconst iterator = source[Symbol.asyncIterator]()\n\t\t\tconst facade: typeof iterator = {\n\t\t\t\tnext: iterator.next.bind(iterator),\n\t\t\t}\n\t\t\tif (iterator.return) facade.return = iterator.return.bind(iterator)\n\t\t\tif (iterator.throw) facade.throw = iterator.throw.bind(iterator)\n\t\t\treturn Object.freeze(facade)\n\t\t},\n\t}) as QueryInitializer<T>\n}\n\nfunction limits(\n\toverrides: Partial<ManagedBudgets> = {},\n\tcentral = defaultBudgets,\n): Readonly<ManagedBudgets> {\n\tfor (const [key, value] of Object.entries(overrides)) {\n\t\tif (!Object.hasOwn(defaultBudgets, key) || !Number.isSafeInteger(value) || value <= 0)\n\t\t\tthrow new Error('Invalid managed read budgets')\n\t}\n\treturn Object.freeze({\n\t\treadOperations: Math.min(\n\t\t\tcentral.readOperations,\n\t\t\toverrides.readOperations ?? central.readOperations,\n\t\t),\n\t\tindexRanges: Math.min(central.indexRanges, overrides.indexRanges ?? central.indexRanges),\n\t\trawCandidates: Math.min(\n\t\t\tcentral.rawCandidates,\n\t\t\toverrides.rawCandidates ?? central.rawCandidates,\n\t\t),\n\t\tdocumentBytes: Math.min(\n\t\t\tcentral.documentBytes,\n\t\t\toverrides.documentBytes ?? central.documentBytes,\n\t\t),\n\t\tfanout: Math.min(central.fanout, overrides.fanout ?? central.fanout),\n\t\tdepth: Math.min(central.depth, overrides.depth ?? central.depth),\n\t\tmemoEntries: Math.min(central.memoEntries, overrides.memoEntries ?? central.memoEntries),\n\t})\n}\n\nfunction snapshot<DM extends GenericDataModel>(authority: Authority<DM>): Authority<DM> {\n\tconst rules = Object.fromEntries(\n\t\tObject.entries(authority.rules).map(([table, rules]) => [table, Object.freeze({ ...rules })]),\n\t)\n\treturn Object.freeze({\n\t\tscope: Object.freeze({ ...authority.scope }),\n\t\t// SAFETY: only the table keys and operation callbacks of Rules were copied.\n\t\trules: Object.freeze(rules) as Rules<SecurityPolicyContext<DM>, DM>,\n\t\tdefaultPolicy: authority.defaultPolicy,\n\t})\n}\n\nfunction request<DM extends GenericDataModel>(\n\tcap: Capability<DM>,\n\ttables: ReadonlySet<string> | null,\n\tbudgets: Readonly<ManagedBudgets>,\n\tpolicyVersion: string,\n\tmemoize = true,\n): ManagedReadRequest<DM> {\n\tconst counts = {\n\t\treadOperations: 0,\n\t\tindexRanges: 0,\n\t\trawCandidates: 0,\n\t\tdocumentBytesObserved: 0,\n\t\tdocumentBytesReserved: 0,\n\t\tfanout: 0,\n\t\tmemoEntries: 0,\n\t}\n\tconst view: ManagedCounters = Object.freeze({\n\t\tget readOperations() {\n\t\t\treturn counts.readOperations\n\t\t},\n\t\tget indexRanges() {\n\t\t\treturn counts.indexRanges\n\t\t},\n\t\tget rawCandidates() {\n\t\t\treturn counts.rawCandidates\n\t\t},\n\t\tget documentBytesObserved() {\n\t\t\treturn counts.documentBytesObserved\n\t\t},\n\t\tget documentBytesReserved() {\n\t\t\treturn counts.documentBytesReserved\n\t\t},\n\t\tget fanout() {\n\t\t\treturn counts.fanout\n\t\t},\n\t\tget memoEntries() {\n\t\t\treturn counts.memoEntries\n\t\t},\n\t})\n\tconst memo = new Map<string, GenericDocument | null>()\n\tlet pendingMemo = 0\n\tlet pendingCandidates = 0\n\tconst fail = (): never => {\n\t\tthrow new ManagedReadError('MANAGED_READ_BUDGET')\n\t}\n\tconst assertTable = (table: string) => {\n\t\tif (table.startsWith('_') || (tables && !tables.has(table)))\n\t\t\tthrow new ManagedReadError('UNSUPPORTED_MANAGED_READ')\n\t}\n\tconst openRange = () => {\n\t\tif (counts.indexRanges >= budgets.indexRanges) fail()\n\t\tcounts.indexRanges++\n\t}\n\tconst reserveLeaf = (): LeafReservation => {\n\t\tif (\n\t\t\tcounts.readOperations >= budgets.readOperations ||\n\t\t\tcounts.rawCandidates + pendingCandidates >= budgets.rawCandidates ||\n\t\t\tcounts.documentBytesObserved + counts.documentBytesReserved + MAX_DOCUMENT_BYTES >\n\t\t\t\tbudgets.documentBytes\n\t\t)\n\t\t\tfail()\n\t\tcounts.readOperations++\n\t\tpendingCandidates++\n\t\tcounts.documentBytesReserved += MAX_DOCUMENT_BYTES\n\t\tlet settled = false\n\t\treturn {\n\t\t\tsettle(document) {\n\t\t\t\tif (settled) throw new Error('Leaf reservation already settled')\n\t\t\t\tsettled = true\n\t\t\t\tpendingCandidates--\n\t\t\t\tcounts.documentBytesReserved -= MAX_DOCUMENT_BYTES\n\t\t\t\tif (document) {\n\t\t\t\t\tcounts.rawCandidates++\n\t\t\t\t\tconst size = getDocumentSize(document)\n\t\t\t\t\tcounts.documentBytesObserved += size\n\t\t\t\t\tif (size > MAX_DOCUMENT_BYTES) fail()\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n\tconst keyOf = (table: string, id: string) => JSON.stringify([table, id])\n\tconst checkCycle = (key: string, ancestors: readonly string[]) => {\n\t\tif (ancestors.includes(key)) throw new ManagedReadError('POLICY_RECURSION')\n\t\tif (ancestors.length >= budgets.depth) fail()\n\t}\n\tasync function authorize(\n\t\ttable: string,\n\t\tdoc: GenericDocument,\n\t\tancestors: readonly string[] = [],\n\t): Promise<boolean> {\n\t\tassertTable(table)\n\t\t// SAFETY: native documents carry a string system ID; normalizeId additionally verifies the declared table.\n\t\tconst id = doc._id as GenericId<string>\n\t\tif (!cap.reader.normalizeId(table, id)) throw new ManagedReadError('UNSUPPORTED_MANAGED_READ')\n\t\tconst key = keyOf(table, id)\n\t\tcheckCycle(key, ancestors)\n\t\tconst rule = cap.rules[table]?.read\n\t\treturn rule\n\t\t\t? rule(Object.freeze({ db: reader([...ancestors, key]) }), doc)\n\t\t\t: cap.defaultPolicy === 'allow'\n\t}\n\tasync function get(\n\t\ttable: string,\n\t\tid: string,\n\t\tancestors: readonly string[],\n\t): Promise<GenericDocument | null> {\n\t\tassertTable(table)\n\t\tconst key = keyOf(table, id)\n\t\tcheckCycle(key, ancestors) // BEFORE consulting memo: active policy cannot await itself.\n\t\tif (memo.has(key)) return memo.get(key)!\n\t\tif (memoize && counts.memoEntries >= budgets.memoEntries) fail()\n\t\t// Completed results only: independent concurrent branches cannot deadlock\n\t\t// through mutually pending memo promises. Concurrent duplicates may fetch twice.\n\t\tif (memoize) {\n\t\t\tpendingMemo++\n\t\t\tcounts.memoEntries = memo.size + pendingMemo\n\t\t}\n\t\ttry {\n\t\t\topenRange()\n\t\t\tconst reservation = reserveLeaf()\n\t\t\tlet doc: GenericDocument | null\n\t\t\ttry {\n\t\t\t\t// SAFETY: IDs come from native readers or caller-branded IDs, with explicit table identity.\n\t\t\t\tdoc = await cap.reader.get(table, id as GenericId<string>)\n\t\t\t} catch (error) {\n\t\t\t\treservation.settle(null)\n\t\t\t\tthrow error\n\t\t\t}\n\t\t\treservation.settle(doc)\n\t\t\tconst result = doc && (await authorize(table, doc, ancestors)) ? doc : null\n\t\t\tif (memoize) memo.set(key, result)\n\t\t\treturn result\n\t\t} finally {\n\t\t\tif (memoize) {\n\t\t\t\tpendingMemo--\n\t\t\t\tcounts.memoEntries = memo.size + pendingMemo\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction query(\n\t\ttable: string,\n\t\tsource: QueryInitializer<any>,\n\t\tancestors: readonly string[],\n\t\tauthorized: boolean,\n\t): QueryInitializer<any> {\n\t\tfunction unsupported(): never {\n\t\t\tthrow new ManagedReadError('UNSUPPORTED_MANAGED_READ')\n\t\t}\n\t\tasync function* iterate() {\n\t\t\topenRange()\n\t\t\tlet iterator: AsyncIterator<GenericDocument> | undefined\n\t\t\ttry {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst reservation = reserveLeaf()\n\t\t\t\t\tlet next: IteratorResult<GenericDocument>\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Native iterator construction starts the query. Reserve before\n\t\t\t\t\t\t// both initialization and next; every later next reserves anew.\n\t\t\t\t\t\titerator ??= source[Symbol.asyncIterator]()\n\t\t\t\t\t\tnext = await iterator.next()\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treservation.settle(null)\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t\treservation.settle(next.done ? null : next.value)\n\t\t\t\t\tif (next.done) return\n\t\t\t\t\tif (!authorized || (await authorize(table, next.value, ancestors))) yield next.value\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tawait iterator?.return?.()\n\t\t\t}\n\t\t}\n\t\tasync function take(n: number) {\n\t\t\tif (!Number.isSafeInteger(n) || n < 0 || n > budgets.rawCandidates)\n\t\t\t\tthrow new ManagedReadError('MANAGED_READ_BUDGET')\n\t\t\tconst docs: GenericDocument[] = []\n\t\t\tif (n === 0) return docs\n\t\t\tfor await (const doc of iterate()) {\n\t\t\t\tdocs.push(doc)\n\t\t\t\tif (docs.length === n) break\n\t\t\t}\n\t\t\treturn docs\n\t\t}\n\t\t// SAFETY: forwarding preserves native document/index types; only consumers are replaced.\n\t\treturn Object.freeze({\n\t\t\tfullTableScan: () =>\n\t\t\t\tquery(table, source.fullTableScan() as QueryInitializer<any>, ancestors, authorized),\n\t\t\twithIndex: (...args: Parameters<typeof source.withIndex>) =>\n\t\t\t\tquery(table, source.withIndex(...args) as QueryInitializer<any>, ancestors, authorized),\n\t\t\torder: (...args: Parameters<typeof source.order>) =>\n\t\t\t\tquery(table, source.order(...args) as QueryInitializer<any>, ancestors, authorized),\n\t\t\t// Native filters/search/pagination may scan/prefetch hidden rows. They\n\t\t\t// cannot prove per-leaf reservation via public APIs; reject on managed readers.\n\t\t\tfilter: unsupported,\n\t\t\twithSearchIndex: unsupported,\n\t\t\tpaginate: unsupported,\n\t\t\tcollect: unsupported,\n\t\t\ttake,\n\t\t\tfirst: async () => (await take(1))[0] ?? null,\n\t\t\tunique: async () => {\n\t\t\t\tconst docs = await take(2)\n\t\t\t\tif (docs.length > 1) throw new Error('Query is not unique')\n\t\t\t\treturn docs[0] ?? null\n\t\t\t},\n\t\t\t[Symbol.asyncIterator]: iterate,\n\t\t}) as QueryInitializer<any>\n\t}\n\tfunction reader(ancestors: readonly string[]): GenericDatabaseReader<any> {\n\t\tconst blocked = () => {\n\t\t\tthrow new ManagedReadError('UNSUPPORTED_MANAGED_READ')\n\t\t}\n\t\t// SAFETY: normalizeId retains the requested table brand; query forwarding retains native table metadata.\n\t\treturn Object.freeze({\n\t\t\tget: async (arg0: string, arg1?: string) => {\n\t\t\t\tif (arg1 !== undefined) return get(arg0, arg1, ancestors)\n\t\t\t\t// Resolve only from the explicit registry (or known policy tables for\n\t\t\t\t// ordinary policy callbacks). Never raw-get an unrecognized ID.\n\t\t\t\tconst table = [...(tables ?? new Set(Object.keys(cap.rules)))].find((name) =>\n\t\t\t\t\tcap.reader.normalizeId(name, arg0),\n\t\t\t\t)\n\t\t\t\treturn table ? get(table, arg0, ancestors) : null\n\t\t\t},\n\t\t\tquery: (table: string) => {\n\t\t\t\tassertTable(table)\n\t\t\t\treturn query(table, cap.reader.query(table), ancestors, true)\n\t\t\t},\n\t\t\tnormalizeId: (table: string, id: string) => {\n\t\t\t\tassertTable(table)\n\t\t\t\treturn cap.reader.normalizeId(table, id)\n\t\t\t},\n\t\t\tsystem: Object.freeze({ get: blocked, query: blocked, normalizeId: blocked }),\n\t\t}) as GenericDatabaseReader<any>\n\t}\n\treturn Object.freeze({\n\t\tdb: reader([]),\n\t\tscope: Object.freeze({ ...cap.scope, policyVersion }),\n\t\tbudgets,\n\t\tcounters: view,\n\t\treserveLeaf,\n\t\topenRange,\n\t\texpand(depth: number, count = 1) {\n\t\t\tif (\n\t\t\t\t!Number.isSafeInteger(depth) ||\n\t\t\t\tdepth < 0 ||\n\t\t\t\tdepth > budgets.depth ||\n\t\t\t\t!Number.isSafeInteger(count) ||\n\t\t\t\tcount < 0 ||\n\t\t\t\tcounts.fanout + count > budgets.fanout\n\t\t\t)\n\t\t\t\tfail()\n\t\t\tcounts.fanout += count\n\t\t},\n\t\tleafQuery: (table: string) => {\n\t\t\tassertTable(table)\n\t\t\treturn query(table, cap.reader.query(table), [], false)\n\t\t},\n\t\tauthorize: (table: string, doc: GenericDocument) => authorize(table, doc),\n\t})\n}\n"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { ManagedBudgets, ManagedQueryContext, ManagedSecurityConfig } from "./querying/security.mjs";
2
+ import { IndexCondition, NativePagination } from "./querying/filters.mjs";
3
+ import { RelationDeclarations, RelationFilterSelection, RelationFilters, RelationRegistry, RelationRow, RelationSelection, RelationTableHelpers, RelationTables } from "./relations.mjs";
4
+ import { CursorCodecConfig } from "./querying/cursors.mjs";
5
+ import { DataModelFromSchemaDefinition, GenericSchema, PaginationResult, SchemaDefinition } from "convex/server";
6
+ import { GenericId } from "convex/values";
7
+ //#region src/querying.d.ts
8
+ type FullMask<S> = { [K in keyof S]-?: NonNullable<S[K]> extends true ? true : FullMask<NonNullable<S[K]>>; };
9
+ type Paths<D extends RelationDeclarations, T extends string, L extends readonly 1[] = [1, 1, 1, 1]> = L extends readonly [1, ...infer Rest extends 1[]] ? { [K in keyof D[T] & string]: (D[T][K]['cardinality'] extends 'many' ? K : never) | `${K}.${Paths<D, D[T][K]['target'], Rest>}`; }[keyof D[T] & string] : never;
10
+ type PathTarget<D extends RelationDeclarations, T extends string, P extends string> = P extends `${infer Head}.${infer Rest}` ? PathTarget<D, D[T][Head]['target'], Rest> : D[T][P]['target'];
11
+ type RemainingDepth<P extends string, L extends readonly 1[]> = L extends readonly [1, ...infer Rest extends 1[]] ? P extends `${string}.${infer Tail}` ? RemainingDepth<Tail, Rest> : Rest : [];
12
+ type EngineArgs<Tables extends RelationTables, D extends RelationDeclarations, T extends keyof Tables> = NativePagination & {
13
+ filters?: RelationFilters<Tables, D, T, FullMask<RelationFilterSelection<Tables, D, T>>>;
14
+ order?: 'asc' | 'desc';
15
+ };
16
+ /** Typed declarative chain returned by the engine; named for consumer declaration emission. */
17
+ export interface TableQuery<Tables extends RelationTables, D extends RelationDeclarations, T extends keyof Tables, S = {}, L extends readonly 1[] = [1, 1, 1, 1]> {
18
+ when(value: IndexCondition['value'], index: string): TableQuery<Tables, D, T, S, L>;
19
+ where(spec: NonNullable<EngineArgs<Tables, D, T>['filters']>): TableQuery<Tables, D, T, S, L>;
20
+ with<const Selection extends RelationSelection<Tables, D, T, L>>(selection: Selection & { [K in keyof Selection]: K extends keyof RelationSelection<Tables, D, T, L> ? Selection[K] : never; }): TableQuery<Tables, D, T, Selection, L>;
21
+ relation<const P extends Paths<D, Extract<T, string>>>(parent: GenericId<Tables[T]['tableName']>, path: P): Omit<TableQuery<Tables, D, Extract<PathTarget<D, Extract<T, string>, P>, keyof Tables>, {}, RemainingDepth<P, L>>, 'relation'>;
22
+ paginate(args: EngineArgs<Tables, D, T>): Promise<PaginationResult<RelationRow<Tables, D, T, S>>>;
23
+ }
24
+ export interface QueryEngineConfig<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables, D extends RelationDeclarations> {
25
+ schema: Schema;
26
+ relations: RelationRegistry<Tables, D>;
27
+ cursor: CursorCodecConfig;
28
+ budgets?: Partial<ManagedBudgets>;
29
+ maxPageSize?: number;
30
+ policyVersion?: string;
31
+ engineVersion?: string;
32
+ trustedAdapter?: ManagedSecurityConfig<DataModelFromSchemaDefinition<Schema>>['trustedAdapter'];
33
+ }
34
+ /** Central facade. Endpoint chains carry declarative constraints and explicit relationships. */
35
+ export declare function createQueryEngine<Schema extends SchemaDefinition<GenericSchema, boolean>, const Tables extends RelationTables, const D extends RelationDeclarations>(config: QueryEngineConfig<Schema, Tables, D>): ((ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>) => { [T in keyof Tables]: TableQuery<Tables, D, T>; }) & {
36
+ tables: { [T in keyof Tables]: RelationTableHelpers<Tables, D, T>; };
37
+ };
38
+ //#endregion
39
+ //# sourceMappingURL=querying.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{createManagedSecurity as e}from"./querying/security.mjs";import{createPlanner as t}from"./querying/planner.mjs";import{createCursorCodec as n}from"./querying/cursors.mjs";import{assertRelationshipDepth as r,relationshipExecutor as i}from"./querying/relationships.mjs";import{executeQuery as a}from"./querying/execution.mjs";function o(o){let{schema:s,relations:c}=o,l=t(s,c),u=n(o.cursor),d=o.maxPageSize??100;if(!Number.isSafeInteger(d)||d<=0||d>100)throw Error(`Invalid central page size`);let f=o.engineVersion??`scalar-1`,p=o.policyVersion??`1`,m,h=e({tables:Object.keys(c.tables),policyVersion:p,budgets:o.budgets,trustedAdapter:o.trustedAdapter});function g(e){l.assertUnchanged();let t=h(e);function n(e,o=[],h=[],g={},_){return Object.freeze({when:(t,r)=>n(e,[...o,{value:t,index:r}],h,g,_),where:t=>n(e,o,[...h,t],g,_),with:t=>n(e,o,h,t,_),relation:(t,r)=>{let i=r.split(`.`);if(!i.length||i.length>4)throw Error(`INVALID_RELATION`);let a=e;for(let e of i){let t=l.metadata.declarations[a]?.[e];if(!t)throw Error(`INVALID_RELATION`);a=t.target}return n(a,o,h,{},{table:e,parent:t,path:i})},async paginate(n){l.assertUnchanged();let v=l.page(e,g);r(g,_?.path.length??0);let y=l.plan(e,n,_?[]:o,h);if(y.args.numItems>d)throw Error(`Page size exceeds central ceiling`);y.args.maximumRowsRead=Math.min(y.args.maximumRowsRead??t.budgets.rawCandidates,t.budgets.rawCandidates),y.args.maximumBytesRead=Math.min(y.args.maximumBytesRead??t.budgets.documentBytes,t.budgets.documentBytes),m??=Array.from(new Uint8Array(await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(c.identity))),e=>e.toString(16).padStart(2,`0`)).join(``),l.assertUnchanged();let b={table:_?.table??e,index:y.index?.name??`by_creation_time`,order:y.args.order??`asc`,clientFilters:y.clientIdentity,serverFilters:{predicates:y.serverIdentity,selection:g,scope:{...t.scope}},relationPath:[],principal:JSON.stringify(t.scope),tenant:t.scope.tenant,policyVersion:p,engineVersion:f,registryVersion:m},x=i(s,t,l,u,b);if(_)return x.continuation(_.table,_.parent,_.path,g,y.args,o,h);let S=await a(s,t,e,y,l,u,b,async(t,n)=>!n||!await x.matches(e,t,y.residual)?null:x.output(e,t,g,y.args));return l.assertUnchanged(),v.parse(S)}})}return Object.freeze(Object.fromEntries(Object.keys(c.tables).map(e=>[e,n(e)])))}return Object.assign(g,{tables:c.tables})}export{o as createQueryEngine};
2
+ //# sourceMappingURL=querying.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"querying.mjs","names":["executorRequest","createQueryEngine","config","planner","createPlanner","schema","relations","codec","createCursorCodec","maxPageSize","engineVersion","policyVersion","registryDigest","security","createManagedSecurity","querying","request","ctx","tableQuery","table","conditions","constraints","selection","route","spec","selected","path","declaredPath","target","name","edge","pageSchema","assertRelationshipDepth","plan","input","byte","binding","executor","relationshipExecutor","result","executeQuery","permitted","row"],"sources":["../src/querying.ts"],"sourcesContent":["import type {\n\tGenericSchema,\n\tSchemaDefinition,\n\tDataModelFromSchemaDefinition,\n\tPaginationResult,\n} from 'convex/server'\nimport type { GenericId } from 'convex/values'\nimport type {\n\tRelationTables,\n\tRelationDeclarations,\n\tRelationRegistry,\n\tRelationRow,\n\tRelationSelection,\n\tRelationFilters,\n\tRelationFilterSelection,\n} from './relations'\nimport { createPlanner, type QueryInput, type StoredConstraint } from './querying/planner'\nimport { executeQuery, type ExecutionRequest } from './querying/execution'\nimport {\n\trelationshipExecutor,\n\tassertRelationshipDepth,\n\ttype OutputSelection,\n\ttype RelationDocument,\n} from './querying/relationships'\nimport { createCursorCodec, type CursorCodecConfig, type CursorBinding } from './querying/cursors'\nimport {\n\tcreateManagedSecurity,\n\ttype ManagedSecurityConfig,\n\ttype ManagedQueryContext,\n\ttype ManagedBudgets,\n} from './querying/security'\nimport type { IndexCondition, NativePagination } from './querying/filters'\n\ntype FullMask<S> = {\n\t[K in keyof S]-?: NonNullable<S[K]> extends true ? true : FullMask<NonNullable<S[K]>>\n}\ntype Paths<\n\tD extends RelationDeclarations,\n\tT extends string,\n\tL extends readonly 1[] = [1, 1, 1, 1],\n> = L extends readonly [1, ...infer Rest extends 1[]]\n\t? {\n\t\t\t[K in keyof D[T] & string]:\n\t\t\t\t| (D[T][K]['cardinality'] extends 'many' ? K : never)\n\t\t\t\t| `${K}.${Paths<D, D[T][K]['target'], Rest>}`\n\t\t}[keyof D[T] & string]\n\t: never\ntype PathTarget<\n\tD extends RelationDeclarations,\n\tT extends string,\n\tP extends string,\n> = P extends `${infer Head}.${infer Rest}`\n\t? PathTarget<D, D[T][Head]['target'], Rest>\n\t: D[T][P]['target']\ntype RemainingDepth<P extends string, L extends readonly 1[]> = L extends readonly [\n\t1,\n\t...infer Rest extends 1[],\n]\n\t? P extends `${string}.${infer Tail}`\n\t\t? RemainingDepth<Tail, Rest>\n\t\t: Rest\n\t: []\ntype EngineArgs<\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n\tT extends keyof Tables,\n> = NativePagination & {\n\tfilters?: RelationFilters<Tables, D, T, FullMask<RelationFilterSelection<Tables, D, T>>>\n\torder?: 'asc' | 'desc'\n}\n/** Typed declarative chain returned by the engine; named for consumer declaration emission. */\nexport interface TableQuery<\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n\tT extends keyof Tables,\n\tS = {},\n\tL extends readonly 1[] = [1, 1, 1, 1],\n> {\n\twhen(value: IndexCondition['value'], index: string): TableQuery<Tables, D, T, S, L>\n\twhere(spec: NonNullable<EngineArgs<Tables, D, T>['filters']>): TableQuery<Tables, D, T, S, L>\n\twith<const Selection extends RelationSelection<Tables, D, T, L>>(\n\t\tselection: Selection & {\n\t\t\t[K in keyof Selection]: K extends keyof RelationSelection<Tables, D, T, L>\n\t\t\t\t? Selection[K]\n\t\t\t\t: never\n\t\t},\n\t): TableQuery<Tables, D, T, Selection, L>\n\trelation<const P extends Paths<D, Extract<T, string>>>(\n\t\tparent: GenericId<Tables[T]['tableName']>,\n\t\tpath: P,\n\t): Omit<\n\t\tTableQuery<\n\t\t\tTables,\n\t\t\tD,\n\t\t\tExtract<PathTarget<D, Extract<T, string>, P>, keyof Tables>,\n\t\t\t{},\n\t\t\tRemainingDepth<P, L>\n\t\t>,\n\t\t'relation'\n\t>\n\tpaginate(args: EngineArgs<Tables, D, T>): Promise<PaginationResult<RelationRow<Tables, D, T, S>>>\n}\nexport interface QueryEngineConfig<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n> {\n\tschema: Schema\n\trelations: RelationRegistry<Tables, D>\n\tcursor: CursorCodecConfig\n\tbudgets?: Partial<ManagedBudgets>\n\tmaxPageSize?: number\n\tpolicyVersion?: string\n\tengineVersion?: string\n\ttrustedAdapter?: ManagedSecurityConfig<DataModelFromSchemaDefinition<Schema>>['trustedAdapter']\n}\n\n/** Central facade. Endpoint chains carry declarative constraints and explicit relationships. */\nexport function createQueryEngine<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tconst Tables extends RelationTables,\n\tconst D extends RelationDeclarations,\n>(config: QueryEngineConfig<Schema, Tables, D>) {\n\tconst { schema, relations } = config\n\tconst planner = createPlanner(schema, relations)\n\tconst codec = createCursorCodec(config.cursor)\n\tconst maxPageSize = config.maxPageSize ?? 100\n\tif (!Number.isSafeInteger(maxPageSize) || maxPageSize <= 0 || maxPageSize > 100)\n\t\tthrow new Error('Invalid central page size')\n\tconst engineVersion = config.engineVersion ?? 'scalar-1'\n\tconst policyVersion = config.policyVersion ?? '1'\n\t// Bind the full immutable identity through a collision-resistant digest so\n\t// real application registries fit the codec's bounded AAD. Live guards still\n\t// inspect current schemas; this caches only the immutable version identity.\n\tlet registryDigest: string | undefined\n\tconst security = createManagedSecurity<DataModelFromSchemaDefinition<Schema>>({\n\t\ttables: Object.keys(relations.tables),\n\t\tpolicyVersion,\n\t\tbudgets: config.budgets,\n\t\ttrustedAdapter: config.trustedAdapter,\n\t})\n\tfunction querying(ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>) {\n\t\tplanner.assertUnchanged()\n\t\tconst request = security(ctx)\n\t\tfunction tableQuery(\n\t\t\ttable: string,\n\t\t\tconditions: readonly IndexCondition[] = [],\n\t\t\tconstraints: readonly StoredConstraint[] = [],\n\t\t\tselection: OutputSelection = {},\n\t\t\troute?: { table: string; parent: string; path: string[] },\n\t\t) {\n\t\t\treturn Object.freeze({\n\t\t\t\twhen: (value: IndexCondition['value'], index: string) =>\n\t\t\t\t\ttableQuery(table, [...conditions, { value, index }], constraints, selection, route),\n\t\t\t\twhere: (spec: StoredConstraint) =>\n\t\t\t\t\ttableQuery(table, conditions, [...constraints, spec], selection, route),\n\t\t\t\twith: (selected: OutputSelection) =>\n\t\t\t\t\ttableQuery(table, conditions, constraints, selected, route),\n\t\t\t\trelation: (parent: string, declaredPath: string) => {\n\t\t\t\t\tconst path = declaredPath.split('.')\n\t\t\t\t\tif (!path.length || path.length > 4) throw new Error('INVALID_RELATION')\n\t\t\t\t\tlet target = table\n\t\t\t\t\tfor (const name of path) {\n\t\t\t\t\t\tconst edge = planner.metadata.declarations[target]?.[name]\n\t\t\t\t\t\tif (!edge) throw new Error('INVALID_RELATION')\n\t\t\t\t\t\ttarget = edge.target\n\t\t\t\t\t}\n\t\t\t\t\treturn tableQuery(target, conditions, constraints, {}, { table, parent, path })\n\t\t\t\t},\n\t\t\t\tasync paginate(input: QueryInput) {\n\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\tconst pageSchema = planner.page(table, selection)\n\t\t\t\t\tassertRelationshipDepth(selection, route?.path.length ?? 0)\n\t\t\t\t\tconst plan = planner.plan(table, input, route ? [] : conditions, constraints)\n\t\t\t\t\tif (plan.args.numItems > maxPageSize) throw new Error('Page size exceeds central ceiling')\n\t\t\t\t\t// Native root limits stay native; lowering those limits cannot raise\n\t\t\t\t\t// the independent shared request meter.\n\t\t\t\t\tplan.args.maximumRowsRead = Math.min(\n\t\t\t\t\t\tplan.args.maximumRowsRead ?? request.budgets.rawCandidates,\n\t\t\t\t\t\trequest.budgets.rawCandidates,\n\t\t\t\t\t)\n\t\t\t\t\tplan.args.maximumBytesRead = Math.min(\n\t\t\t\t\t\tplan.args.maximumBytesRead ?? request.budgets.documentBytes,\n\t\t\t\t\t\trequest.budgets.documentBytes,\n\t\t\t\t\t)\n\t\t\t\t\tregistryDigest ??= Array.from(\n\t\t\t\t\t\tnew Uint8Array(\n\t\t\t\t\t\t\tawait crypto.subtle.digest('SHA-256', new TextEncoder().encode(relations.identity)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(byte) => byte.toString(16).padStart(2, '0'),\n\t\t\t\t\t).join('')\n\t\t\t\t\t// The digest await is a boundary too: mutation must be detected\n\t\t\t\t\t// before authenticated cursor validation or native query construction.\n\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\t// SAFETY: selection has passed the registry page validator before binding.\n\t\t\t\t\tconst binding: CursorBinding = {\n\t\t\t\t\t\ttable: route?.table ?? table,\n\t\t\t\t\t\tindex: plan.index?.name ?? 'by_creation_time',\n\t\t\t\t\t\torder: plan.args.order ?? 'asc',\n\t\t\t\t\t\tclientFilters: plan.clientIdentity,\n\t\t\t\t\t\t// Include output selection and the complete immutable scope in trusted identity.\n\t\t\t\t\t\tserverFilters: {\n\t\t\t\t\t\t\tpredicates: plan.serverIdentity,\n\t\t\t\t\t\t\tselection: selection as import('convex/values').Value,\n\t\t\t\t\t\t\tscope: { ...request.scope },\n\t\t\t\t\t\t},\n\t\t\t\t\t\trelationPath: [],\n\t\t\t\t\t\tprincipal: JSON.stringify(request.scope),\n\t\t\t\t\t\ttenant: request.scope.tenant,\n\t\t\t\t\t\tpolicyVersion,\n\t\t\t\t\t\tengineVersion,\n\t\t\t\t\t\tregistryVersion: registryDigest,\n\t\t\t\t\t}\n\t\t\t\t\t// The executor accepts registry-selected native tables, rather than a static DM key.\n\t\t\t\t\tconst executorRequest: unknown = request\n\t\t\t\t\t// SAFETY: only native DM types are erased at this private executor boundary.\n\t\t\t\t\tconst executor = relationshipExecutor(\n\t\t\t\t\t\tschema,\n\t\t\t\t\t\texecutorRequest as ExecutionRequest,\n\t\t\t\t\t\tplanner,\n\t\t\t\t\t\tcodec,\n\t\t\t\t\t\tbinding,\n\t\t\t\t\t)\n\t\t\t\t\tif (route)\n\t\t\t\t\t\treturn executor.continuation(\n\t\t\t\t\t\t\troute.table,\n\t\t\t\t\t\t\troute.parent,\n\t\t\t\t\t\t\troute.path,\n\t\t\t\t\t\t\tselection,\n\t\t\t\t\t\t\tplan.args,\n\t\t\t\t\t\t\tconditions,\n\t\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\t)\n\t\t\t\t\tconst result = await executeQuery(\n\t\t\t\t\t\tschema,\n\t\t\t\t\t\trequest,\n\t\t\t\t\t\ttable,\n\t\t\t\t\t\tplan,\n\t\t\t\t\t\tplanner,\n\t\t\t\t\t\tcodec,\n\t\t\t\t\t\tbinding,\n\t\t\t\t\t\tasync (row, permitted) => {\n\t\t\t\t\t\t\t// SAFETY: metered native stream rows contain the validated system fields.\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!permitted ||\n\t\t\t\t\t\t\t\t!(await executor.matches(table, row as RelationDocument, plan.residual))\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t\t// SAFETY: the same native row is authorized and matches the declared predicates.\n\t\t\t\t\t\t\treturn executor.output(table, row as RelationDocument, selection, plan.args)\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\treturn pageSchema.parse(result)\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\t// SAFETY: each key comes from the exact validated registry; the planner\n\t\t// validates all input and projection while mapped types retain table brands.\n\t\treturn Object.freeze(\n\t\t\tObject.fromEntries(Object.keys(relations.tables).map((table) => [table, tableQuery(table)])),\n\t\t) as { [T in keyof Tables]: TableQuery<Tables, D, T> }\n\t}\n\treturn Object.assign(querying, { tables: relations.tables })\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { FieldReference, RelationBuilder, RelationDeclarations, RelationEdge, RelationFilterSelection, RelationFilters, RelationRegistry, RelationRegistryIdentity, RelationRow, RelationSelection, RelationTable, RelationTableHelpers, RelationTables, ThroughReference, defineRelations } from "./relations.mjs";
2
+ export { type FieldReference, type RelationBuilder, type RelationDeclarations, type RelationEdge, type RelationFilterSelection, type RelationFilters, type RelationRegistry, type RelationRegistryIdentity, type RelationRow, type RelationSelection, type RelationTable, type RelationTableHelpers, type RelationTables, type ThroughReference, defineRelations };
@@ -0,0 +1 @@
1
+ import{defineRelations as e}from"./relations.mjs";export{e as defineRelations};