convex-ents 0.7.7-alpha.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ type Origin = {
13
13
  table: string;
14
14
  deletionTime: number;
15
15
  };
16
- declare const vApproach: convex_values.Validator<"cascade" | "paginate", false, never>;
16
+ declare const vApproach: convex_values.VUnion<"cascade" | "paginate", [convex_values.VLiteral<"cascade", "required">, convex_values.VLiteral<"paginate", "required">], "required", never>;
17
17
  type Approach = Infer<typeof vApproach>;
18
18
  declare function scheduledDeleteFactory<EntsDataModel extends GenericEntsDataModel>(entDefinitions: EntsDataModel, options?: {
19
19
  scheduledDelete: ScheduledDeleteFuncRef;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/deletion.ts","../src/writer.ts","../src/functions.ts"],"sourcesContent":["import {\n FunctionReference,\n GenericMutationCtx,\n IndexRangeBuilder,\n RegisteredMutation,\n internalMutationGeneric as internalMutation,\n makeFunctionReference,\n} from \"convex/server\";\nimport { GenericId, Infer, convexToJson, v } from \"convex/values\";\nimport { getEdgeDefinitions } from \"./functions\";\nimport { GenericEntsDataModel } from \"./schema\";\n\nexport type ScheduledDeleteFuncRef = FunctionReference<\n \"mutation\",\n \"internal\",\n {\n origin: Origin;\n stack: Stack;\n inProgress: boolean;\n },\n void\n>;\n\ntype Origin = {\n id: string;\n table: string;\n deletionTime: number;\n};\n\nconst vApproach = v.union(v.literal(\"cascade\"), v.literal(\"paginate\"));\n\ntype Approach = Infer<typeof vApproach>;\n\nexport function scheduledDeleteFactory<\n EntsDataModel extends GenericEntsDataModel,\n>(\n entDefinitions: EntsDataModel,\n options?: {\n scheduledDelete: ScheduledDeleteFuncRef;\n },\n): RegisteredMutation<\n \"internal\",\n { origin: Origin; stack: Stack; inProgress: boolean },\n Promise<void>\n> {\n const selfRef =\n options?.scheduledDelete ??\n (makeFunctionReference(\n \"functions:scheduledDelete\",\n ) as unknown as ScheduledDeleteFuncRef);\n return internalMutation({\n args: {\n origin: v.object({\n id: v.string(),\n table: v.string(),\n deletionTime: v.number(),\n }),\n stack: v.array(\n v.union(\n v.object({\n id: v.string(),\n table: v.string(),\n edges: v.array(\n v.object({\n approach: vApproach,\n table: v.string(),\n indexName: v.string(),\n }),\n ),\n }),\n v.object({\n approach: vApproach,\n cursor: v.union(v.string(), v.null()),\n table: v.string(),\n indexName: v.string(),\n fieldValue: v.any(),\n }),\n ),\n ),\n inProgress: v.boolean(),\n },\n handler: async (ctx, { origin, stack, inProgress }) => {\n const originId = ctx.db.normalizeId(origin.table, origin.id);\n if (originId === null) {\n throw new Error(`Invalid ID \"${origin.id}\" for table ${origin.table}`);\n }\n // Check that we still want to delete\n // Note: Doesn't support scheduled deletion starting with system table\n const doc = await ctx.db.get(originId);\n if (doc.deletionTime !== origin.deletionTime) {\n if (inProgress) {\n console.error(\n `[Ents] Already in-progress scheduled deletion for \"${origin.id}\" was cancelled!`,\n );\n } else {\n console.log(\n `[Ents] Scheduled deletion for \"${origin.id}\" was cancelled`,\n );\n }\n return;\n }\n await progressScheduledDeletion(\n { ctx, entDefinitions, selfRef, origin },\n newCounter(),\n inProgress\n ? stack\n : [\n {\n id: originId,\n table: origin.table,\n edges: getEdgeArgs(entDefinitions, origin.table),\n },\n ],\n );\n },\n });\n}\n\n// Heuristic:\n// Ent at the end of an edge\n// has soft or scheduled deletion behavior && has cascading edges: schedule individually\n// has cascading edges: paginate by 1\n// else: paginate by decent number\nfunction getEdgeArgs(entDefinitions: GenericEntsDataModel, table: string) {\n const edges = getEdgeDefinitions(entDefinitions, table);\n return Object.values(edges).flatMap((edgeDefinition) => {\n if (\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"ref\") ||\n (edgeDefinition.cardinality === \"multiple\" &&\n edgeDefinition.type === \"field\")\n ) {\n const table = edgeDefinition.to;\n const targetEdges = getEdgeDefinitions(entDefinitions, table);\n const hasCascadingEdges = Object.values(targetEdges).some(\n (edgeDefinition) =>\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"ref\") ||\n edgeDefinition.cardinality === \"multiple\",\n );\n const approach = hasCascadingEdges ? \"cascade\" : \"paginate\";\n\n const indexName = edgeDefinition.ref;\n return [{ table, indexName, approach } as const];\n } else if (edgeDefinition.cardinality === \"multiple\") {\n const table = edgeDefinition.table;\n return [\n {\n table,\n indexName: edgeDefinition.field,\n approach: \"paginate\",\n } as const,\n ...(edgeDefinition.symmetric\n ? [\n {\n table,\n indexName: edgeDefinition.ref,\n approach: \"paginate\",\n } as const,\n ]\n : []),\n ];\n } else {\n return [];\n }\n });\n}\n\ntype PaginationArgs = {\n approach: Approach;\n table: string;\n cursor: string | null;\n indexName: string;\n fieldValue: any;\n};\n\ntype EdgeArgs = {\n approach: Approach;\n table: string;\n indexName: string;\n};\n\ntype Stack = (\n | { id: string; table: string; edges: EdgeArgs[] }\n | PaginationArgs\n)[];\n\ntype CascadeCtx = {\n ctx: GenericMutationCtx<any>;\n entDefinitions: GenericEntsDataModel;\n selfRef: ScheduledDeleteFuncRef;\n origin: Origin;\n};\n\nasync function progressScheduledDeletion(\n cascade: CascadeCtx,\n counter: Counter,\n stack: Stack,\n) {\n const { ctx } = cascade;\n const last = stack[stack.length - 1];\n\n if (\"id\" in last) {\n const edgeArgs = last.edges[0];\n if (edgeArgs === undefined) {\n await ctx.db.delete(last.id as GenericId<any>);\n if (stack.length > 1) {\n await continueOrSchedule(cascade, counter, stack.slice(0, -1));\n }\n } else {\n const updated = { ...last, edges: last.edges.slice(1) };\n await paginateOrCascade(\n cascade,\n counter,\n stack.slice(0, -1).concat(updated),\n {\n cursor: null,\n fieldValue: last.id,\n ...edgeArgs,\n },\n );\n }\n } else {\n await paginateOrCascade(cascade, counter, stack, last);\n }\n}\n\nconst MAXIMUM_DOCUMENTS_READ = 8192 / 4;\nconst MAXIMUM_BYTES_READ = 2 ** 18;\n\nasync function paginateOrCascade(\n cascade: CascadeCtx,\n counter: Counter,\n stack: Stack,\n { table, approach, indexName, fieldValue, cursor }: PaginationArgs,\n) {\n const { ctx, entDefinitions } = cascade;\n const { page, continueCursor, isDone, bytesRead } = await paginate(\n ctx,\n { table, indexName, fieldValue },\n {\n cursor,\n ...limitsBasedOnCounter(\n counter,\n approach === \"paginate\"\n ? { numItems: MAXIMUM_DOCUMENTS_READ }\n : { numItems: 1 },\n ),\n },\n );\n\n const updatedCounter = incrementCounter(counter, page.length, bytesRead);\n const updated = {\n approach,\n table,\n cursor: continueCursor,\n indexName,\n fieldValue,\n };\n const relevantStack = cursor === null ? stack : stack.slice(0, -1);\n const updatedStack =\n isDone && (approach === \"paginate\" || page.length === 0)\n ? relevantStack\n : relevantStack.concat(\n approach === \"cascade\"\n ? [\n updated,\n {\n id: page[0]._id,\n table,\n edges: getEdgeArgs(entDefinitions, table),\n },\n ]\n : [updated],\n );\n if (approach === \"paginate\") {\n await Promise.all(page.map((doc) => ctx.db.delete(doc._id)));\n }\n await continueOrSchedule(cascade, updatedCounter, updatedStack);\n}\n\nasync function continueOrSchedule(\n cascade: CascadeCtx,\n counter: Counter,\n stack: Stack,\n) {\n if (shouldSchedule(counter)) {\n const { ctx, selfRef, origin } = cascade;\n await ctx.scheduler.runAfter(0, selfRef, {\n origin,\n stack,\n inProgress: true,\n });\n } else {\n await progressScheduledDeletion(cascade, counter, stack);\n }\n}\n\ntype Counter = {\n numDocuments: number;\n numBytesRead: number;\n};\n\nfunction newCounter() {\n return {\n numDocuments: 0,\n numBytesRead: 0,\n };\n}\n\nfunction incrementCounter(\n counter: Counter,\n numDocuments: number,\n numBytesRead: number,\n) {\n return {\n numDocuments: counter.numDocuments + numDocuments,\n numBytesRead: counter.numBytesRead + numBytesRead,\n };\n}\n\nfunction limitsBasedOnCounter(\n counter: Counter,\n { numItems }: { numItems: number },\n) {\n return {\n numItems: Math.max(1, numItems - counter.numDocuments),\n maximumBytesRead: Math.max(1, MAXIMUM_BYTES_READ - counter.numBytesRead),\n };\n}\n\nfunction shouldSchedule(counter: Counter) {\n return (\n counter.numDocuments >= MAXIMUM_DOCUMENTS_READ ||\n counter.numBytesRead >= MAXIMUM_BYTES_READ\n );\n}\n\nasync function paginate(\n ctx: GenericMutationCtx<any>,\n {\n table,\n indexName,\n fieldValue,\n }: { table: string; indexName: string; fieldValue: any },\n {\n cursor,\n numItems,\n maximumBytesRead,\n }: {\n cursor: string | null;\n numItems: number;\n maximumBytesRead: number;\n },\n) {\n const query = ctx.db\n .query(table)\n .withIndex(indexName, (q) =>\n (q.eq(indexName, fieldValue) as IndexRangeBuilder<any, any, any>).gt(\n \"_creationTime\",\n cursor === null ? cursor : +cursor,\n ),\n );\n\n let bytesRead = 0;\n const results = [];\n let isDone = true;\n\n for await (const doc of query) {\n if (results.length >= numItems) {\n isDone = false;\n break;\n }\n const size = JSON.stringify(convexToJson(doc)).length * 8;\n\n results.push(doc);\n bytesRead += size;\n\n // Check this after we read the doc, since reading it already\n // happened anyway, and to make sure we return at least one\n // result.\n if (bytesRead > maximumBytesRead) {\n isDone = false;\n break;\n }\n }\n return {\n page: results,\n continueCursor:\n results.length === 0\n ? cursor\n : \"\" + results[results.length - 1]._creationTime,\n isDone,\n bytesRead,\n };\n}\n","import {\n DocumentByName,\n GenericDocument,\n TableNamesInDataModel,\n makeFunctionReference,\n} from \"convex/server\";\nimport { GenericId } from \"convex/values\";\nimport {\n EntMutationCtx,\n entWrapper,\n getDeletionConfig,\n getEdgeDefinitions,\n getReadRule,\n getWriteRule,\n} from \"./functions\";\nimport { FieldConfig, GenericEdgeConfig, GenericEntsDataModel } from \"./schema\";\nimport { ScheduledDeleteFuncRef } from \"./deletion\";\n\nexport class WriterImplBase<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> {\n constructor(\n protected ctx: EntMutationCtx<EntsDataModel>,\n protected entDefinitions: EntsDataModel,\n protected table: Table,\n ) {}\n\n async deleteId(id: GenericId<any>, behavior: \"default\" | \"soft\" | \"hard\") {\n await this.checkReadAndWriteRule(\"delete\", id, undefined);\n\n const deletionConfig = getDeletionConfig(this.entDefinitions, this.table);\n\n const isDeletingSoftly =\n behavior !== \"hard\" &&\n deletionConfig !== undefined &&\n (deletionConfig.type === \"soft\" || deletionConfig.type === \"scheduled\");\n\n if (behavior === \"soft\" && !isDeletingSoftly) {\n throw new Error(\n `Cannot soft delete document with ID \"${id}\" in ` +\n `table \"${this.table}\" because it does not have an ` +\n `\"allowSoft\", \"soft\" or \"scheduled\" deletion behavior configured.`,\n );\n }\n const edges: EdgeChanges = {};\n await Promise.all(\n Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(\n async (edgeDefinition) => {\n const key = edgeDefinition.name;\n if (\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"ref\") ||\n (edgeDefinition.cardinality === \"multiple\" &&\n edgeDefinition.type === \"field\")\n ) {\n if (!isDeletingSoftly || edgeDefinition.deletion === \"soft\") {\n const remove = (\n await this.ctx.db\n .query(edgeDefinition.to)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, id as any),\n )\n .collect()\n ).map((doc) => doc._id as GenericId<any>);\n edges[key] = { remove };\n }\n } else if (edgeDefinition.cardinality === \"multiple\") {\n if (!isDeletingSoftly) {\n const removeEdges = (\n await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n q.eq(edgeDefinition.field, id as any),\n )\n .collect()\n )\n .concat(\n edgeDefinition.symmetric\n ? await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, id as any),\n )\n .collect()\n : [],\n )\n .map((doc) => doc._id as GenericId<any>);\n edges[key] = { removeEdges };\n }\n }\n },\n ),\n );\n const deletionTime = +new Date();\n if (isDeletingSoftly) {\n await this.ctx.db.patch(id, { deletionTime });\n } else {\n try {\n await this.ctx.db.delete(id);\n } catch (e) {\n // TODO:\n // For now we're gonna ignore errors here,\n // because we assume that the only error\n // is \"document not found\", which\n // can be caused by concurrent deletions.\n // In the future we could track which\n // edges are being deleted by this mutation,\n // and skip the call to delete altogether\n // - or Convex could implement this.\n }\n }\n await this.writeEdges(id, edges, isDeletingSoftly);\n if (deletionConfig !== undefined && deletionConfig.type === \"scheduled\") {\n const fnRef = ((this.ctx as any).scheduledDelete ??\n makeFunctionReference(\n \"functions:scheduledDelete\",\n )) as ScheduledDeleteFuncRef;\n await this.ctx.scheduler.runAfter(deletionConfig.delayMs ?? 0, fnRef, {\n origin: {\n id,\n table: this.table,\n deletionTime,\n },\n inProgress: false,\n stack: [],\n });\n }\n return id;\n }\n\n async deletedIdIn(id: GenericId<any>, table: string, cascadingSoft: boolean) {\n await new WriterImplBase(this.ctx, this.entDefinitions, table).deleteId(\n id,\n cascadingSoft ? \"soft\" : \"hard\",\n );\n }\n\n async writeEdges(\n docId: GenericId<any>,\n changes: EdgeChanges,\n deleteSoftly?: boolean,\n ) {\n await Promise.all(\n Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(\n async (edgeDefinition) => {\n const idOrIds = changes[edgeDefinition.name];\n if (idOrIds === undefined) {\n return;\n }\n if (\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"ref\") ||\n (edgeDefinition.cardinality === \"multiple\" &&\n edgeDefinition.type === \"field\")\n ) {\n if (idOrIds.remove !== undefined && idOrIds.remove.length > 0) {\n // Cascading delete because 1:many edges are not optional\n // on the stored field end.\n await Promise.all(\n idOrIds.remove.map((id) =>\n this.deletedIdIn(\n id,\n edgeDefinition.to,\n (deleteSoftly ?? false) &&\n edgeDefinition.deletion === \"soft\",\n ),\n ),\n );\n // This would be behavior for optional edge:\n // await Promise.all(\n // idsToDelete.map((id) =>\n // this.ctx.db.patch(id, {\n // [edgeDefinition.ref]: undefined,\n // } as any)\n // )\n // );\n }\n if (idOrIds.add !== undefined && idOrIds.add.length > 0) {\n await Promise.all(\n idOrIds.add.map(async (id) =>\n this.ctx.db.patch(id, {\n [edgeDefinition.ref]: docId,\n } as any),\n ),\n );\n }\n } else if (edgeDefinition.cardinality === \"multiple\") {\n if ((idOrIds.removeEdges ?? []).length > 0) {\n await Promise.all(\n idOrIds.removeEdges!.map(async (id) => {\n try {\n await this.ctx.db.delete(id);\n } catch (e) {\n // TODO:\n // For now we're gonna ignore errors here,\n // because we assume that the only error\n // is \"document not found\", which\n // can be caused by concurrent deletions.\n // In the future we could track which\n // edges are being deleted by this mutation,\n // and skip the call to delete altogether\n // - or Convex could implement this.\n }\n }),\n );\n }\n\n if (idOrIds.add !== undefined) {\n await Promise.all(\n idOrIds.add.map(async (id) => {\n const existing = await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n (q.eq(edgeDefinition.field, docId as any) as any).eq(\n edgeDefinition.ref,\n id,\n ),\n )\n .first();\n if (existing === null) {\n await this.ctx.db.insert(edgeDefinition.table, {\n [edgeDefinition.field]: docId,\n [edgeDefinition.ref]: id,\n } as any);\n if (edgeDefinition.symmetric) {\n await this.ctx.db.insert(edgeDefinition.table, {\n [edgeDefinition.field]: id,\n [edgeDefinition.ref]: docId,\n } as any);\n }\n }\n }),\n );\n }\n }\n },\n ),\n );\n }\n\n async checkUniqueness(value: Partial<GenericDocument>, id?: GenericId<any>) {\n await Promise.all(\n Object.values(\n (this.entDefinitions[this.table] as any).fields as Record<\n string,\n FieldConfig\n >,\n ).map(async (fieldDefinition) => {\n if (fieldDefinition.unique) {\n const key = fieldDefinition.name;\n const fieldValue = value[key];\n const existing = await this.ctx.db\n .query(this.table)\n .withIndex(key, (q) => q.eq(key, value[key] as any))\n .unique();\n if (existing !== null && (id === undefined || existing._id !== id)) {\n throw new Error(\n `In table \"${\n this.table\n }\" cannot create a duplicate document with field \"${key}\" of value \\`${\n fieldValue as string\n }\\`, existing document with ID \"${\n existing._id as string\n }\" already has it.`,\n );\n }\n }\n }),\n );\n await Promise.all(\n Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(\n async (edgeDefinition) => {\n if (\n edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"field\" &&\n edgeDefinition.unique\n ) {\n const key = edgeDefinition.field;\n if (value[key] === undefined) {\n return;\n }\n // Enforce uniqueness\n const existing = await this.ctx.db\n .query(this.table)\n .withIndex(key, (q) => q.eq(key, value[key] as any))\n .unique();\n if (\n existing !== null &&\n (id === undefined || existing._id !== id)\n ) {\n throw new Error(\n `In table \"${this.table}\" cannot create a duplicate 1:1 edge \"${\n edgeDefinition.name\n }\" to ID \"${\n value[key] as string\n }\", existing document with ID \"${\n existing._id as string\n }\" already has it.`,\n );\n }\n }\n },\n ),\n );\n }\n\n fieldsOnly(\n value: Partial<\n WithEdgePatches<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ) {\n const fields: GenericDocument = {};\n Object.keys(value).forEach((key) => {\n const edgeDefinition = getEdgeDefinitions(\n this.entDefinitions,\n this.table,\n )[key];\n if (\n edgeDefinition === undefined\n // This doesn't do anything because the edge name doesn't match the field name\n // ||\n // (edgeDefinition.cardinality === \"single\" &&\n // edgeDefinition.type === \"field\")\n ) {\n fields[key] = value[key]!;\n }\n });\n return fields;\n }\n\n async checkReadAndWriteRule(\n operation: \"create\" | \"update\" | \"delete\",\n id: GenericId<Table> | undefined,\n value: Partial<GenericDocument> | undefined,\n ) {\n if (id !== undefined) {\n const readPolicy = getReadRule(this.entDefinitions, this.table);\n if (readPolicy !== undefined) {\n const doc = await this.ctx.db.get(id);\n if (doc === null) {\n throw new Error(\n `Cannot update document with ID \"${id}\" in table \"${this.table} because it does not exist\"`,\n );\n }\n const decision = await readPolicy(doc);\n if (!decision) {\n throw new Error(\n `Cannot update document with ID \"${id}\" from table \"${this.table}\"`,\n );\n }\n }\n }\n const writePolicy = getWriteRule(this.entDefinitions, this.table);\n if (writePolicy === undefined) {\n return;\n }\n const ent =\n id === undefined\n ? undefined\n : entWrapper(\n (await this.ctx.db.get(id))!,\n this.ctx,\n this.entDefinitions,\n this.table,\n );\n // Replace allows _id and _creationTime, but rules should not\n // rely on them.\n const { _id, _creationTime, ...safeValue } = value ?? {};\n const decision = await writePolicy({\n operation,\n ent: ent as any,\n value: value !== undefined ? (safeValue as any) : undefined,\n });\n if (!decision) {\n if (id === undefined) {\n throw new Error(\n `Cannot insert into table \"${this.table}\": \\`${JSON.stringify(\n value,\n )}\\``,\n );\n } else if (value === undefined) {\n throw new Error(\n `Cannot delete from table \"${this.table}\" with ID \"${id}\"`,\n );\n } else {\n throw new Error(\n `Cannot update document with ID \"${id}\" in table \"${\n this.table\n }\" with: \\`${JSON.stringify(value)}\\``,\n );\n }\n }\n }\n}\n\nexport type WithEdgeInserts<\n Document extends GenericDocument,\n Edges extends Record<string, GenericEdgeConfig>,\n> = Document & {\n [key in keyof Edges as Edges[key][\"cardinality\"] extends \"single\"\n ? Edges[key][\"type\"] extends \"field\"\n ? never\n : key\n : key]?: Edges[key][\"cardinality\"] extends \"single\"\n ? GenericId<Edges[key][\"to\"]>\n : GenericId<Edges[key][\"to\"]>[];\n};\n\nexport type WithEdges<\n Document extends GenericDocument,\n Edges extends Record<string, GenericEdgeConfig>,\n> = Document & {\n [key in keyof Edges as Edges[key][\"cardinality\"] extends \"multiple\"\n ? Edges[key][\"type\"] extends \"ref\"\n ? key\n : never\n : never]?: GenericId<Edges[key][\"to\"]>[];\n};\n\nexport type WithEdgePatches<\n Document extends GenericDocument,\n Edges extends Record<string, GenericEdgeConfig>,\n> = Document & {\n [key in keyof Edges as Edges[key][\"cardinality\"] extends \"multiple\"\n ? Edges[key][\"type\"] extends \"ref\"\n ? key\n : never\n : never]?: {\n add?: GenericId<Edges[key][\"to\"]>[];\n remove?: GenericId<Edges[key][\"to\"]>[];\n };\n};\n\nexport type EdgeChanges = Record<\n string,\n {\n add?: GenericId<any>[];\n remove?: GenericId<any>[];\n removeEdges?: GenericId<any>[];\n }\n>;\n","import {\n DocumentByName,\n ExpressionOrValue,\n FieldTypeFromFieldPath,\n FilterBuilder,\n GenericDataModel,\n GenericDatabaseReader,\n GenericDatabaseWriter,\n GenericDocument,\n IndexNames,\n IndexRange,\n IndexRangeBuilder,\n NamedIndex,\n NamedSearchIndex,\n NamedTableInfo,\n PaginationOptions,\n PaginationResult,\n Query,\n QueryInitializer,\n Scheduler,\n SearchFilter,\n SearchFilterBuilder,\n SearchIndexNames,\n SystemDataModel,\n TableNamesInDataModel,\n WithOptionalSystemFields,\n WithoutSystemFields,\n} from \"convex/server\";\nimport { GenericId } from \"convex/values\";\nimport {\n DeletionConfig,\n EdgeConfig,\n Expand,\n GenericEdgeConfig,\n GenericEntsDataModel,\n} from \"./schema\";\nimport {\n EdgeChanges,\n WithEdgeInserts,\n WithEdgePatches,\n WithEdges,\n WriterImplBase,\n} from \"./writer\";\nimport { ScheduledDeleteFuncRef } from \"./deletion\";\n\nexport interface PromiseOrderedQueryOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n > {\n filter(\n predicate: (\n q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>,\n ) => ExpressionOrValue<boolean>,\n ): this;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArrayOrNull<TOutput>;\n\n paginate(\n paginationOpts: PaginationOptions,\n ): PromisePaginationResultOrNull<EntsDataModel, Table>;\n\n take(n: number): PromiseEntsOrNull<EntsDataModel, Table>;\n\n first(): PromiseEntOrNull<EntsDataModel, Table>;\n\n unique(): PromiseEntOrNull<EntsDataModel, Table>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>;\n}\n\nexport interface PromiseOrderedQueryWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n > {\n filter(\n predicate: (\n q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>,\n ) => ExpressionOrValue<boolean>,\n ): this;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArrayOrNull<TOutput>;\n\n paginate(\n paginationOpts: PaginationOptions,\n ): PromisePaginationResultOrNull<EntsDataModel, Table>;\n\n take(n: number): PromiseEntsWriterOrNull<EntsDataModel, Table>;\n\n first(): PromiseEntWriterOrNull<EntsDataModel, Table>;\n\n unique(): PromiseEntWriterOrNull<EntsDataModel, Table>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>;\n}\n\nexport interface PromiseQueryOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseOrderedQueryOrNull<EntsDataModel, Table> {\n // TODO: The index variant should not be allowed if\n // this query already used an index\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): PromiseOrderedQueryOrNull<EntsDataModel, Table>;\n}\n\nexport interface PromiseQueryWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseOrderedQueryWriterOrNull<EntsDataModel, Table> {\n // TODO: The index variant should not be allowed if\n // this query already used an index\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): PromiseOrderedQueryWriterOrNull<EntsDataModel, Table>;\n}\n\nexport interface PromiseTableBase<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> {\n getMany<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n values: FieldTypeFromFieldPath<\n DocumentByName<EntsDataModel, Table>,\n Indexes[Index][0]\n >[],\n ): PromiseEntsOrNulls<EntsDataModel, Table>;\n getMany(ids: GenericId<Table>[]): PromiseEntsOrNulls<EntsDataModel, Table>;\n getManyX<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n values: FieldTypeFromFieldPath<\n DocumentByName<EntsDataModel, Table>,\n Indexes[Index][0]\n >[],\n ): PromiseEnts<EntsDataModel, Table>;\n getManyX(ids: GenericId<Table>[]): PromiseEnts<EntsDataModel, Table>;\n /**\n * Returns the string ID format for the ID in a given table, or null if the ID\n * is from a different table or is not a valid ID.\n *\n * This does not guarantee that the ID exists (i.e. `table(\"foo\").get(id)` may return `null`).\n *\n * @param tableName - The name of the table.\n * @param id - The ID string.\n */\n normalizeId(id: string): GenericId<Table> | null;\n}\n\nexport interface PromiseTable<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseQuery<EntsDataModel, Table>,\n PromiseTableBase<EntsDataModel, Table> {\n get<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n ...values: IndexFieldTypesForEq<EntsDataModel, Table, Indexes[Index]>\n ): PromiseEntOrNull<EntsDataModel, Table>;\n get(id: GenericId<Table>): PromiseEntOrNull<EntsDataModel, Table>;\n /**\n * Fetch a unique document from the DB using given index, throw if it doesn't exist.\n */\n getX<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n ...values: IndexFieldTypesForEq<EntsDataModel, Table, Indexes[Index]>\n ): PromiseEnt<EntsDataModel, Table>;\n /**\n * Fetch a document from the DB for a given ID, throw if it doesn't exist.\n */\n getX(id: GenericId<Table>): PromiseEnt<EntsDataModel, Table>;\n /**\n * Query by running a full text search against a search index.\n *\n * Search queries must always search for some text within the index's\n * `searchField`. This query can optionally add equality filters for any\n * `filterFields` specified in the index.\n *\n * Documents will be returned in relevance order based on how well they\n * match the search text.\n *\n * To learn about full text search, see [Indexes](https://docs.convex.dev/text-search).\n *\n * @param indexName - The name of the search index to query.\n * @param searchFilter - A search filter expression constructed with the\n * supplied {@link SearchFilterBuilder}. This defines the full text search to run\n * along with equality filtering to run within the search index.\n * @returns - A query that searches for matching documents, returning them\n * in relevancy order.\n */\n search<\n IndexName extends SearchIndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n indexName: IndexName,\n searchFilter: (\n q: SearchFilterBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedSearchIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => SearchFilter,\n ): PromiseOrderedQuery<EntsDataModel, Table>;\n}\n\nexport interface PromiseOrderedQueryBase<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> {\n filter(\n predicate: (\n q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>,\n ) => ExpressionOrValue<boolean>,\n ): this;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[]>;\n}\n\nexport interface PromiseOrderedQuery<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n >,\n PromiseOrderedQueryBase<EntsDataModel, Table> {\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArray<TOutput>;\n\n paginate(\n paginationOpts: PaginationOptions,\n ): PromisePaginationResult<EntsDataModel, Table>;\n\n take(n: number): PromiseEnts<EntsDataModel, Table>;\n\n first(): PromiseEntOrNull<EntsDataModel, Table>;\n\n firstX(): PromiseEnt<EntsDataModel, Table>;\n\n unique(): PromiseEntOrNull<EntsDataModel, Table>;\n\n uniqueX(): PromiseEnt<EntsDataModel, Table>;\n}\n\nexport interface PromiseQuery<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseOrderedQuery<EntsDataModel, Table> {\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): PromiseOrderedQuery<EntsDataModel, Table>;\n}\n\nclass PromiseQueryOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n >\n implements PromiseQueryOrNull<EntsDataModel, Table>\n{\n constructor(\n protected ctx: EntQueryCtx<EntsDataModel>,\n protected entDefinitions: EntsDataModel,\n protected table: Table,\n protected retrieve: () => Promise<Query<\n NamedTableInfo<EntsDataModel, Table>\n > | null>,\n ) {\n super(() => {});\n }\n\n filter(\n predicate: (\n q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>,\n ) => ExpressionOrValue<boolean>,\n ): any {\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n return query.filter(predicate);\n },\n );\n }\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ) {\n return new PromiseArrayImpl(async () => {\n const array = await this;\n if (array === null) {\n return null as TOutput[] | null;\n }\n return await Promise.all(array.map(callbackFn));\n });\n }\n\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): any {\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n if (indexName !== undefined) {\n return (\n query as QueryInitializer<NamedTableInfo<EntsDataModel, Table>>\n )\n .withIndex(indexName)\n .order(order);\n }\n return query.order(order) as any;\n },\n );\n }\n\n paginate(paginationOpts: PaginationOptions) {\n return new PromisePaginationResultOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n this.retrieve,\n paginationOpts,\n );\n }\n\n take(n: number) {\n return new PromiseEntsOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n return await this._take(n);\n },\n false,\n );\n }\n\n first() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this._take(1);\n if (docs === null) {\n return nullRetriever;\n }\n const [doc] = docs;\n return loadedRetriever(doc);\n },\n false,\n );\n }\n\n firstX() {\n return new PromiseEntWriterImpl(\n this.ctx as any,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this._take(1);\n if (docs === null) {\n return nullRetriever;\n }\n const [doc] = docs;\n if (doc === undefined) {\n throw new Error(\"Query returned no documents\");\n }\n return loadedRetriever(doc);\n },\n false,\n );\n }\n\n unique() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this._take(2);\n if (docs === null) {\n return nullRetriever;\n }\n if (docs.length === 0) {\n return nullRetriever;\n }\n if (docs.length === 2) {\n throw new Error(\"unique() query returned more than one result\");\n }\n const [doc] = docs;\n return loadedRetriever(doc);\n },\n false,\n );\n }\n\n uniqueX() {\n return new PromiseEntWriterImpl(\n this.ctx as any,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this._take(2);\n if (docs === null) {\n return nullRetriever;\n }\n if (docs.length === 0) {\n throw new Error(\"Query returned no documents\");\n }\n if (docs.length === 2) {\n throw new Error(\"unique() query returned more than one result\");\n }\n const [doc] = docs;\n return loadedRetriever(doc);\n },\n true,\n );\n }\n\n async docs() {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n const docs = await query.collect();\n return filterByReadRule(\n this.ctx,\n this.entDefinitions,\n this.table,\n docs,\n false,\n );\n }\n\n then<\n TResult1 =\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n TResult2 = never,\n >(\n onfulfilled?:\n | ((\n value:\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n ) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.docs()\n .then((documents) =>\n documents === null\n ? null\n : documents.map((doc) =>\n entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n ),\n )\n .then(onfulfilled, onrejected);\n }\n\n async _take(n: number) {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n const readPolicy = getReadRule(this.entDefinitions, this.table);\n if (readPolicy === undefined) {\n return await query.take(n);\n }\n let numItems = n;\n const docs = [];\n let hasMore = true;\n const iterator = query[Symbol.asyncIterator]();\n while (hasMore && docs.length < n) {\n const page = [];\n for (let i = 0; i < numItems; i++) {\n const { done, value } = await iterator.next();\n if (done) {\n hasMore = false;\n break;\n }\n page.push(value);\n }\n docs.push(\n ...(await filterByReadRule(\n this.ctx,\n this.entDefinitions,\n this.table,\n page,\n false,\n ))!.slice(0, n - docs.length),\n );\n numItems = Math.min(64, numItems * 2);\n }\n return docs;\n }\n}\n\nexport interface PromisePaginationResultOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<PaginationResult<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > | null> {\n docs(): Promise<PaginationResult<\n DocumentByName<EntsDataModel, Table>\n > | null>;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): Promise<PaginationResult<TOutput> | null>;\n}\n\nexport interface PromisePaginationResult<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n PaginationResult<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n >\n > {\n docs(): Promise<PaginationResult<DocumentByName<EntsDataModel, Table>>>;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): Promise<PaginationResult<TOutput>>;\n}\n\nclass PromisePaginationResultOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<PaginationResult<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > | null>\n implements PromisePaginationResultOrNull<EntsDataModel, Table>\n{\n constructor(\n private ctx: EntQueryCtx<EntsDataModel>,\n private entDefinitions: EntsDataModel,\n private table: Table,\n protected retrieve: () => Promise<Query<\n NamedTableInfo<EntsDataModel, Table>\n > | null>,\n protected paginationOpts: PaginationOptions,\n ) {\n super(() => {});\n }\n\n async map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ) {\n const result = await this;\n if (result === null) {\n return null;\n }\n return {\n ...result,\n page: await Promise.all(result.page.map(callbackFn)),\n };\n }\n\n async docs() {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n const result = await query.paginate(this.paginationOpts);\n return {\n ...result,\n page: (await filterByReadRule(\n this.ctx,\n this.entDefinitions,\n this.table,\n result.page,\n false,\n ))!,\n };\n }\n\n then<\n TResult1 =\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n TResult2 = never,\n >(\n onfulfilled?:\n | ((\n value: PaginationResult<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > | null,\n ) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.docs()\n .then((result) =>\n result === null\n ? null\n : {\n ...result,\n page: result.page.map((doc) =>\n entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n ),\n },\n )\n .then(onfulfilled, onrejected);\n }\n}\n\nclass PromiseTableImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseQueryOrNullImpl<EntsDataModel, Table> {\n constructor(\n ctx: EntQueryCtx<EntsDataModel>,\n entDefinitions: EntsDataModel,\n table: Table,\n ) {\n super(ctx, entDefinitions, table, async () =>\n isSystemTable(table)\n ? (ctx.db.system.query(table as any) as any)\n : ctx.db.query(table),\n );\n }\n\n get(...args: any[]) {\n return this.getImpl(args);\n }\n\n getX(...args: any[]) {\n return this.getImpl(args, true);\n }\n\n getMany(...args: any[]) {\n return this.getManyImpl(args);\n }\n\n getManyX(...args: any[]) {\n return this.getManyImpl(args, true);\n }\n\n getImpl(args: any[], throwIfNull = false) {\n return new PromiseEntWriterImpl(\n this.ctx as any,\n this.entDefinitions,\n this.table,\n args.length === 1\n ? async () => {\n const id = args[0] as GenericId<Table>;\n if (this.ctx.db.normalizeId(this.table, id) === null) {\n throw new Error(`Invalid id \\`${id}\\` for table \"${this.table}\"`);\n }\n return {\n id,\n doc: async () => {\n const doc = await (isSystemTable(this.table)\n ? this.ctx.db.system.get(id as any)\n : this.ctx.db.get(id));\n if (throwIfNull && doc === null) {\n throw new Error(\n `Document not found with id \\`${id}\\` in table \"${this.table}\"`,\n );\n }\n return doc;\n },\n } as any; // any because PromiseEntWriterImpl expects non-nullable\n }\n : async () => {\n const [indexName, ...values] = args;\n const fieldNames = getIndexFields(\n this.entDefinitions,\n this.table,\n indexName,\n );\n const doc = await this.ctx.db\n .query(this.table)\n .withIndex(indexName, (q) =>\n values.reduce((q, value, i) => q.eq(fieldNames[i], value), q),\n )\n .unique();\n if (throwIfNull && doc === null) {\n throw new Error(\n `Table \"${this.table}\" does not contain document with field${values.reduce(\n (message, value, i) =>\n `${message} \"${fieldNames[i]}\" = \\`${value}\\``,\n \"\",\n )}`,\n );\n }\n return loadedRetriever(doc);\n },\n throwIfNull,\n );\n }\n\n getManyImpl(args: any[], throwIfNull = false) {\n return new PromiseEntsOrNullImpl(\n this.ctx as any,\n this.entDefinitions as any,\n this.table,\n args.length === 1\n ? async () => {\n const ids = args[0] as GenericId<Table>[];\n ids.forEach((id) => {\n if (this.ctx.db.normalizeId(this.table, id) === null) {\n throw new Error(\n `Invalid id \\`${id}\\` for table \"${this.table}\"`,\n );\n }\n });\n return await Promise.all(\n ids.map(async (id) => {\n const doc = await (isSystemTable(this.table)\n ? this.ctx.db.system.get(id as any)\n : this.ctx.db.get(id));\n if (doc === null) {\n throw new Error(\n `Document not found with id \\`${id}\\` in table \"${this.table}\"`,\n );\n }\n return doc;\n }),\n );\n }\n : async () => {\n const [indexName, values] = args;\n return (await Promise.all(\n (values as any[]).map(async (value) => {\n const doc = await this.ctx.db\n .query(this.table)\n .withIndex(indexName, (q) => q.eq(indexName, value))\n .unique();\n if (throwIfNull && doc === null) {\n throw new Error(\n `Table \"${this.table}\" does not contain document with field \"${indexName}\" = \\`${value}\\``,\n );\n }\n return doc;\n }),\n )) as any;\n },\n throwIfNull,\n );\n }\n\n normalizeId(id: string): GenericId<Table> | null {\n return this.ctx.db.normalizeId(this.table, id);\n }\n\n // normalizeId or throw\n normalizeIdX(id: string): GenericId<Table> {\n const normalized = this.normalizeId(id);\n if (normalized === null) {\n throw new Error(`Invalid id \\`${id}\\` for table \"${this.table}\"`);\n }\n return normalized;\n }\n\n withIndex(\n indexName: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n indexRange?: (\n q: IndexRangeBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedIndex<NamedTableInfo<EntsDataModel, Table>, typeof indexName>\n >,\n ) => IndexRange,\n ) {\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const query = await this.retrieve();\n return (\n query as QueryInitializer<NamedTableInfo<EntsDataModel, Table>>\n ).withIndex(indexName, indexRange);\n },\n );\n }\n\n search<\n IndexName extends SearchIndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n indexName: IndexName,\n searchFilter: (\n q: SearchFilterBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedSearchIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => SearchFilter,\n ) {\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const query = await this.retrieve();\n return (\n query as QueryInitializer<NamedTableInfo<EntsDataModel, Table>>\n ).withSearchIndex(indexName, searchFilter) as any;\n },\n );\n }\n}\n\n// This lazy promise materializes objects, so chaining to this type of\n// lazy promise performs one operation for each\n// retrieved document in JavaScript, basically as if using `Promise.all()`.\nexport interface PromiseEntsOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n > {\n // TODO: At this point there is nothing query specific here, and we can either:\n // 1. Return a generic lazy promise of the list.\n // 2. Not give any methods, because they might lead devs down the wrong path.\n // // This just returns the first retrieved document, it does not optimize\n // // the previous steps in the query.\n // first(): PromiseEntOrNull<EntsDataModel, Table>;\n // // This just returns the unique retrieved document, it does not optimize\n // // the previous steps in the query. Otherwise it behaves like db.query().unique().\n // unique(): PromiseEntOrNull<EntsDataModel, Table>;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArrayOrNull<TOutput>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>;\n}\n\nexport interface PromiseEntsWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n | EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null\n > {\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArrayOrNull<TOutput>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>;\n}\n\n// This lazy promise materializes objects, so chaining to this type of\n// lazy promise performs one operation for each\n// retrieved document in JavaScript, basically as if using\n// `Promise.all()`.\nexport interface PromiseEnts<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n > {\n // TODO: At this point there is nothing query specific here, and we can either:\n // 1. Return a generic lazy promise of the list.\n // 2. Not give any methods, because they might lead devs down the wrong path.\n // // This just returns the first retrieved document, it does not optimize\n // // the previous steps in the query.\n // first(): PromiseEntOrNull<EntsDataModel, Table>;\n // // This just returns the first retrieved document, or throws if there\n // // are no documents. It does not optimize the previous steps in the query.\n // firstX(): PromiseEnt<EntsDataModel, Table>;\n // // This just returns the unique retrieved document, it does not optimize\n // // the previous steps in the query. Otherwise it behaves like db.query().unique().\n // unique(): PromiseEntOrNull<EntsDataModel, Table>;\n // // This just returns the unique retrieved document, or throws if there\n // // are no documents. It does not optimize the previous steps in the query.\n // // Otherwise it behaves like db.query().unique().\n // uniqueX(): PromiseEnt<EntsDataModel, Table>;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArray<TOutput>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[]>;\n}\n\nclass PromiseEntsOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n >\n implements PromiseEntsOrNull<EntsDataModel, Table>\n{\n constructor(\n private ctx: EntQueryCtx<EntsDataModel>,\n private entDefinitions: EntsDataModel,\n private table: Table,\n private retrieve: () => Promise<\n DocumentByName<EntsDataModel, Table>[] | null\n >,\n private throwIfNull: boolean,\n ) {\n super(() => {});\n }\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ) {\n return new PromiseArrayImpl(async () => {\n const array = await this;\n if (array === null) {\n return null as TOutput[] | null;\n }\n return await Promise.all(array.map(callbackFn));\n });\n }\n\n first() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this.retrieve();\n if (docs === null) {\n return nullRetriever;\n }\n return loadedRetriever(docs[0] ?? null);\n },\n false,\n );\n }\n\n firstX() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this.retrieve();\n if (docs === null) {\n return nullRetriever;\n }\n const doc = docs[0] ?? undefined;\n if (doc === undefined) {\n throw new Error(\"Query returned no documents\");\n }\n return loadedRetriever(doc);\n },\n true,\n );\n }\n\n unique() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this.retrieve();\n if (docs === null) {\n return nullRetriever;\n }\n if (docs.length > 1) {\n throw new Error(\"unique() query returned more than one result\");\n }\n return loadedRetriever(docs[0] ?? null);\n },\n false,\n );\n }\n\n uniqueX() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this.retrieve();\n if (docs === null) {\n return nullRetriever;\n }\n if (docs.length > 1) {\n throw new Error(\"unique() query returned more than one result\");\n }\n if (docs.length < 1) {\n throw new Error(\"unique() query returned no documents\");\n }\n return loadedRetriever(docs[0]);\n },\n true,\n );\n }\n\n async docs() {\n const docs = await this.retrieve();\n return filterByReadRule(\n this.ctx,\n this.entDefinitions,\n this.table,\n docs,\n this.throwIfNull,\n );\n }\n\n then<\n TResult1 =\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n TResult2 = never,\n >(\n onfulfilled?:\n | ((\n value:\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n ) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.docs()\n .then((docs) =>\n docs === null\n ? null\n : docs.map((doc) =>\n entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n ),\n )\n .then(onfulfilled, onrejected);\n }\n}\n\n// This lazy promise materializes objects, so chaining to this type of\n// lazy promise performs one operation for each\n// retrieved document in JavaScript, basically as if using\n// `Promise.all()`.\nexport interface PromiseEntsOrNulls<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n (Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel> | null)[]\n > {}\n\nexport interface PromiseEdgeEntsOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEntsOrNull<EntsDataModel, Table> {\n /**\n * Returns whether there is an ent with given ID on the other side\n * the edge. Returns null if chained to a null result.\n * @param id The ID of the ent on the other end of the edge\n */\n has(id: GenericId<Table>): Promise<boolean | null>;\n}\n\nexport interface PromiseEdgeEntsWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEntsWriterOrNull<EntsDataModel, Table> {\n /**\n * Returns whether there is an ent with given ID on the other side\n * the edge. Returns null if chained to a null result.\n * @param id The ID of the ent on the other end of the edge\n */\n has(id: GenericId<Table>): Promise<boolean | null>;\n}\n\nexport interface PromiseEdgeEnts<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEnts<EntsDataModel, Table> {\n /**\n * Returns whether there is an ent with given ID on the other side\n * the edge.\n * @param id The ID of the ent on the other end of the edge\n */\n has(id: GenericId<Table>): Promise<boolean>;\n}\n\nexport interface PromiseEdgeEntsWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEntsWriter<EntsDataModel, Table> {\n /**\n * Returns whether there is an ent with given ID on the other side\n * the edge.\n * @param id The ID of the ent on the other end of the edge\n */\n has(id: GenericId<Table>): Promise<boolean>;\n}\n\nclass PromiseEdgeOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends PromiseEntsOrNullImpl<EntsDataModel, Table>\n implements PromiseEdgeEntsOrNull<EntsDataModel, Table>\n{\n constructor(\n ctx: EntQueryCtx<EntsDataModel>,\n entDefinitions: EntsDataModel,\n table: Table,\n private field: string,\n private retrieveRange: (\n indexRange: (\n q: IndexRangeBuilder<DocumentByName<EntsDataModel, Table>, any>,\n ) => any,\n ) => Promise<DocumentByName<EntsDataModel, Table>[] | null>,\n ) {\n super(ctx, entDefinitions, table, () => retrieveRange((q) => q), false);\n }\n\n async has(id: GenericId<Table>) {\n const docs = await this.retrieveRange((q) => q.eq(this.field, id as any));\n return (docs?.length ?? 0) > 0;\n }\n}\n\nexport interface PromiseEntOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null> {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeOrNull<EntsDataModel, Table, Edge>;\n\n doc(): Promise<DocumentByName<EntsDataModel, Table> | null>;\n}\n\nexport interface PromiseEnt<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdge<EntsDataModel, Table, Edge>;\n\n edgeX<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeOrThrow<EntsDataModel, Table, Edge>;\n\n doc(): Promise<DocumentByName<EntsDataModel, Table>>;\n}\n\nclass PromiseEntOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null>\n implements PromiseEntOrNull<EntsDataModel, Table>\n{\n constructor(\n protected ctx: EntQueryCtx<EntsDataModel>,\n protected entDefinitions: EntsDataModel,\n protected table: Table,\n protected retrieve: DocRetriever<\n GenericId<Table> | null,\n DocumentByName<EntsDataModel, Table> | null\n >,\n protected throwIfNull: boolean,\n ) {\n super(() => {});\n }\n\n async doc() {\n const { id, doc: getDoc } = await this.retrieve();\n if (id === null) {\n return null;\n }\n const doc = await getDoc();\n if (doc === null) {\n return null;\n }\n const readPolicy = getReadRule(this.entDefinitions, this.table);\n if (readPolicy !== undefined) {\n const decision = await readPolicy(\n entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n );\n if (this.throwIfNull && !decision) {\n throw new Error(\n `Document cannot be read with id \\`${doc._id as string}\\` in table \"${\n this.table\n }\"`,\n );\n }\n return decision ? doc : null;\n }\n return doc;\n }\n\n then<\n TResult1 = Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null,\n TResult2 = never,\n >(\n onfulfilled?:\n | ((\n value: Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null,\n ) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.doc()\n .then((doc) =>\n doc === null\n ? null\n : entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n )\n .then(onfulfilled, onrejected);\n }\n\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(edge: Edge) {\n return this.edgeImpl(edge);\n }\n\n edgeX<Edge extends keyof EntsDataModel[Table][\"edges\"]>(edge: Edge) {\n return this.edgeImpl(edge, true);\n }\n\n edgeImpl<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n throwIfNull = false,\n ) {\n const edgeDefinition = getEdgeDefinitions(this.entDefinitions, this.table)[\n edge\n ];\n\n if (edgeDefinition.cardinality === \"multiple\") {\n if (edgeDefinition.type === \"ref\") {\n return new PromiseEdgeOrNullImpl(\n this.ctx,\n this.entDefinitions,\n edgeDefinition.to,\n edgeDefinition.ref,\n async (indexRange) => {\n const { id } = await this.retrieve();\n if (id === null) {\n return null;\n }\n const edgeDocs = await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n indexRange(q.eq(edgeDefinition.field, id as any) as any),\n )\n .collect();\n return (\n await Promise.all(\n edgeDocs.map((edgeDoc) =>\n this.ctx.db.get(edgeDoc[edgeDefinition.ref] as any),\n ),\n )\n ).filter(<TValue>(doc: TValue | null, i: number): doc is TValue => {\n if (doc === null) {\n throw new Error(\n `Dangling reference for edge \"${edgeDefinition.name}\" in ` +\n `table \"${this.table}\" for document with ID \"${id}\": ` +\n `Could not find a document with ID \"${\n edgeDocs[i][edgeDefinition.field] as string\n }\"` +\n ` in table \"${edgeDefinition.to}\" (edge document ID is \"${\n edgeDocs[i]._id as string\n }\").`,\n );\n }\n return true;\n });\n },\n ) as any;\n }\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n edgeDefinition.to,\n async () => {\n const { id } = await this.retrieve();\n if (id === null) {\n return null;\n }\n return this.ctx.db\n .query(edgeDefinition.to)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, id as any),\n );\n },\n ) as any;\n }\n\n return new PromiseEntWriterImpl(\n this.ctx as any,\n this.entDefinitions,\n edgeDefinition.to,\n async () => {\n const { id, doc: getDoc } = await this.retrieve();\n if (id === null) {\n return nullRetriever;\n }\n\n if (edgeDefinition.type === \"ref\") {\n const otherDoc = await this.ctx.db\n .query(edgeDefinition.to)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, id as any),\n )\n .unique();\n if (throwIfNull && otherDoc === null) {\n throw new Error(\n `Edge \"${\n edgeDefinition.name\n }\" does not exist for document with ID \"${id as string}\"`,\n );\n }\n return loadedRetriever(otherDoc);\n }\n const doc = (await getDoc())!;\n const otherId = doc[edgeDefinition.field] as any;\n return {\n id: otherId,\n doc: async () => {\n const otherDoc = await this.ctx.db.get(otherId);\n if (otherDoc === null) {\n throw new Error(\n `Dangling reference for edge \"${edgeDefinition.name}\" in ` +\n `table \"${this.table}\" for document with ID \"${id}\": ` +\n `Could not find a document with ID \"${otherId}\"` +\n ` in table \"${edgeDefinition.to}\".`,\n );\n }\n return otherDoc;\n },\n };\n },\n throwIfNull,\n ) as any;\n }\n}\n\nexport interface PromiseArrayOrNull<T> extends Promise<T[] | null> {\n filter<S extends T>(\n predicate: (value: T, index: number, array: T[] | null) => value is S,\n ): Promise<S[] | null>;\n\n filter(\n predicate: (value: T, index: number, array: T[] | null) => unknown,\n ): Promise<T[] | null>;\n}\n\nexport interface PromiseArray<T> extends Promise<T[]> {\n filter<S extends T>(\n predicate: (value: T, index: number, array: T[]) => value is S,\n ): Promise<S[]>;\n\n filter(\n predicate: (value: T, index: number, array: T[]) => unknown,\n ): Promise<T[]>;\n}\n\nclass PromiseArrayImpl<T>\n extends Promise<T[] | null>\n implements PromiseArrayOrNull<T>\n{\n constructor(protected retrieve: () => Promise<T[] | null>) {\n super(() => {});\n }\n\n async filter<S extends T>(\n predicate: (value: T, index: number, array: T[] | null) => value is S,\n ) {\n const array = await this.retrieve();\n if (array === null) {\n return null;\n }\n return array.filter(predicate);\n }\n\n then<TResult1 = T[] | null, TResult2 = never>(\n onfulfilled?:\n | ((value: T[] | null) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.retrieve().then(onfulfilled, onrejected);\n }\n}\n\nexport function entWrapper<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n>(\n fields: DocumentByName<EntsDataModel, Table>,\n ctx: EntQueryCtx<EntsDataModel>,\n entDefinitions: EntsDataModel,\n table: Table,\n): Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel> {\n const doc = { ...fields };\n const queryInterface = new PromiseEntWriterImpl(\n ctx as any,\n entDefinitions as any,\n table,\n async () => ({ id: doc._id as any, doc: async () => doc }),\n // this `true` doesn't matter, the queryInterface cannot be awaited\n true,\n );\n Object.defineProperty(doc, \"edge\", {\n value: (edge: any) => {\n return queryInterface.edge(edge);\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"edgeX\", {\n value: (edge: any) => {\n return queryInterface.edgeX(edge);\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"doc\", {\n value: () => {\n return doc;\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"patch\", {\n value: (value: any) => {\n return queryInterface.patch(value);\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"replace\", {\n value: (value: any) => {\n return queryInterface.replace(value);\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"delete\", {\n value: () => {\n return queryInterface.delete();\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.entries((entDefinitions as any)[table]?.defaults ?? []).map(\n ([field, value]) => {\n if (doc[field] === undefined) {\n (doc as any)[field] = value;\n }\n },\n );\n return doc as any;\n}\n\nexport function entsTableFactory<\n Ctx extends EntQueryCtx<any>,\n EntsDataModel extends GenericEntsDataModel,\n>(\n ctx: Ctx,\n entDefinitions: EntsDataModel,\n options?: {\n scheduledDelete: ScheduledDeleteFuncRef;\n },\n): Ctx extends EntMutationCtx<any>\n ? EntsTableWriter<EntsDataModel>\n : EntsTable<EntsDataModel> {\n const enrichedCtx = options !== undefined ? { ...ctx, ...options } : ctx;\n const table = (\n table: TableNamesInDataModel<EntsDataModel>,\n indexName?: string,\n indexRange?: any,\n ) => {\n // Consider being strict here if people struggle with setup:\n // if (typeof ctx.db?.query !== \"function\") {\n // throw new Error(\n // `Expected context with \\`db\\`, got \\`${JSON.stringify(ctx)}\\``\n // );\n // }\n if (typeof table !== \"string\") {\n throw new Error(`Expected table name, got \\`${table as any}\\``);\n }\n if (indexName !== undefined) {\n return new PromiseTableImpl(\n enrichedCtx as any,\n entDefinitions,\n table,\n ).withIndex(indexName, indexRange);\n }\n if ((ctx.db as any).insert !== undefined) {\n return new PromiseTableWriterImpl(\n enrichedCtx as any,\n entDefinitions,\n table,\n ) as any;\n }\n return new PromiseTableImpl(enrichedCtx as any, entDefinitions, table);\n };\n table.system = table;\n return table;\n}\n\ntype EntsTableReader<EntsDataModel extends GenericEntsDataModel> = {\n <\n Table extends TableNamesInDataModel<EntsDataModel>,\n IndexName extends IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n table: Table,\n indexName: IndexName,\n indexRange?: (\n q: IndexRangeBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => IndexRange,\n ): PromiseQuery<EntsDataModel, Table>;\n <Table extends TableNamesInDataModel<EntsDataModel>>(\n table: Table,\n ): PromiseTable<EntsDataModel, Table>;\n};\n\nexport type EntsTable<EntsDataModel extends GenericEntsDataModel> =\n EntsTableReader<EntsDataModel> & {\n system: EntsTableReader<EntsSystemDataModel>;\n };\n\ntype EntsSystemDataModel = {\n [key in keyof SystemDataModel]: SystemDataModel[key] & {\n edges: Record<string, never>;\n };\n};\n\nexport type EntsTableWriter<EntsDataModel extends GenericEntsDataModel> = {\n <\n Table extends TableNamesInDataModel<EntsDataModel>,\n IndexName extends IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n table: Table,\n indexName: IndexName,\n indexRange?: (\n q: IndexRangeBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => IndexRange,\n ): PromiseTableWriter<Table, EntsDataModel>;\n <Table extends TableNamesInDataModel<EntsDataModel>>(\n table: Table,\n ): PromiseTableWriter<Table, EntsDataModel>;\n\n system: EntsTableReader<EntsSystemDataModel>;\n};\n\ndeclare class EntInstance<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdge<EntsDataModel, Table, Edge>;\n edgeX<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeOrThrow<EntsDataModel, Table, Edge>;\n doc(): DocumentByName<EntsDataModel, Table>;\n}\n\nexport type Ent<\n Table extends TableNamesInDataModel<EntsDataModel>,\n Doc extends DocumentByName<EntsDataModel, Table>,\n EntsDataModel extends GenericEntsDataModel,\n> = Doc & EntInstance<EntsDataModel, Table>;\n\nexport type GenericEnt<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> = Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>;\n\ntype PromiseEdgeResult<\n EdgeConfig extends GenericEdgeConfig,\n MultipleRef,\n MultipleField,\n SingleRef,\n SingleField,\n> = EdgeConfig[\"cardinality\"] extends \"multiple\"\n ? EdgeConfig[\"type\"] extends \"ref\"\n ? MultipleRef\n : MultipleField\n : EdgeConfig[\"type\"] extends \"ref\"\n ? SingleRef\n : SingleField;\n\nexport type PromiseEdge<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEnts<EntsDataModel, ToTable>,\n PromiseQuery<EntsDataModel, ToTable>,\n PromiseEntOrNull<EntsDataModel, ToTable>,\n PromiseEnt<EntsDataModel, ToTable>\n>;\n\nexport type PromiseEdgeOrThrow<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEnts<EntsDataModel, ToTable>,\n PromiseQuery<EntsDataModel, ToTable>,\n PromiseEnt<EntsDataModel, ToTable>,\n PromiseEnt<EntsDataModel, ToTable>\n>;\n\ntype PromiseEdgeOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEntsOrNull<EntsDataModel, ToTable>,\n PromiseQueryOrNull<EntsDataModel, ToTable>,\n PromiseEntOrNull<EntsDataModel, ToTable>,\n PromiseEntOrNull<EntsDataModel, ToTable>\n>;\n\nexport type PromiseEdgeWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEntsWriter<EntsDataModel, ToTable>,\n PromiseQueryWriter<EntsDataModel, ToTable>,\n PromiseEntWriterOrNull<EntsDataModel, ToTable>,\n PromiseEntWriter<EntsDataModel, ToTable>\n>;\n\nexport type PromiseEdgeWriterOrThrow<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEntsWriter<EntsDataModel, ToTable>,\n PromiseQueryWriter<EntsDataModel, ToTable>,\n PromiseEntWriter<EntsDataModel, ToTable>,\n PromiseEntWriter<EntsDataModel, ToTable>\n>;\n\nexport type PromiseEdgeWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEntsWriterOrNull<EntsDataModel, ToTable>,\n PromiseQueryWriterOrNull<EntsDataModel, ToTable>,\n PromiseEntWriterOrNull<EntsDataModel, ToTable>,\n PromiseEntWriterOrNull<EntsDataModel, ToTable>\n>;\n\nexport interface PromiseOrderedQueryWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n >,\n PromiseOrderedQueryBase<EntsDataModel, Table> {\n paginate(\n paginationOpts: PaginationOptions,\n ): PromisePaginationResultWriter<EntsDataModel, Table>;\n\n map<TOutput>(\n callbackFn: (\n value: EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >,\n index: number,\n array: EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArray<TOutput>;\n\n take(n: number): PromiseEntsWriter<EntsDataModel, Table>;\n\n first(): PromiseEntWriterOrNull<EntsDataModel, Table>;\n\n firstX(): PromiseEntWriter<EntsDataModel, Table>;\n\n unique(): PromiseEntWriterOrNull<EntsDataModel, Table>;\n\n uniqueX(): PromiseEntWriter<EntsDataModel, Table>;\n}\n\nexport interface PromiseQueryWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseOrderedQueryWriter<EntsDataModel, Table> {\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): PromiseOrderedQueryWriter<EntsDataModel, Table>;\n}\n\n// This lazy promise materializes objects, so chaining to this type of\n// lazy promise performs one operation for each\n// retrieved document in JavaScript, basically as if using\n// `Promise.all()`.\nexport interface PromiseEntsWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEnts<EntsDataModel, Table> {\n // This just returns the first retrieved document, or throws if there\n // are no documents. It does not optimize the previous steps in the query.\n firstX(): PromiseEntWriter<EntsDataModel, Table>;\n\n // This just returns the unique retrieved document, or throws if there\n // are no documents. It does not optimize the previous steps in the query.\n // Otherwise it behaves like db.query().unique().\n uniqueX(): PromiseEntWriter<EntsDataModel, Table>;\n}\n\nexport interface PromisePaginationResultWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n PaginationResult<\n EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n >\n > {\n docs(): Promise<PaginationResult<DocumentByName<EntsDataModel, Table>>>;\n\n map<TOutput>(\n callbackFn: (\n value: EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >,\n index: number,\n array: EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >[],\n ) => Promise<TOutput> | TOutput,\n ): Promise<PaginationResult<TOutput>>;\n}\n\nexport interface PromiseTableWriter<\n Table extends TableNamesInDataModel<EntsDataModel>,\n EntsDataModel extends GenericEntsDataModel,\n> extends PromiseQueryWriter<EntsDataModel, Table>,\n PromiseTableBase<EntsDataModel, Table> {\n get<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n ...values: IndexFieldTypesForEq<EntsDataModel, Table, Indexes[Index]>\n ): PromiseEntWriterOrNull<EntsDataModel, Table>;\n get(id: GenericId<Table>): PromiseEntWriterOrNull<EntsDataModel, Table>;\n /**\n * Fetch a unique document from the DB using given index, throw if it doesn't exist.\n */\n getX<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n ...values: IndexFieldTypesForEq<EntsDataModel, Table, Indexes[Index]>\n ): PromiseEntWriter<EntsDataModel, Table>;\n /**\n * Fetch a document from the DB for a given ID, throw if it doesn't exist.\n */\n getX(id: GenericId<Table>): PromiseEntWriter<EntsDataModel, Table>;\n /**\n * Query by running a full text search against a search index.\n *\n * Search queries must always search for some text within the index's\n * `searchField`. This query can optionally add equality filters for any\n * `filterFields` specified in the index.\n *\n * Documents will be returned in relevance order based on how well they\n * match the search text.\n *\n * To learn about full text search, see [Indexes](https://docs.convex.dev/text-search).\n *\n * @param indexName - The name of the search index to query.\n * @param searchFilter - A search filter expression constructed with the\n * supplied {@link SearchFilterBuilder}. This defines the full text search to run\n * along with equality filtering to run within the search index.\n * @returns - A query that searches for matching documents, returning them\n * in relevancy order.\n */\n search<\n IndexName extends SearchIndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n indexName: IndexName,\n searchFilter: (\n q: SearchFilterBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedSearchIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => SearchFilter,\n ): PromiseOrderedQueryWriter<EntsDataModel, Table>;\n /**\n * Insert a new document into a table.\n *\n * @param table - The name of the table to insert a new document into.\n * @param value - The {@link Value} to insert into the given table.\n * @returns - {@link GenericId} of the new document.\n */\n // TODO: Chain methods to get the written document?\n insert(\n value: Expand<\n WithoutSystemFields<\n WithEdgeInserts<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n /**\n * Insert new documents into a table.\n *\n * @param table - The name of the table to insert a new document into.\n * @param value - The {@link Value} to insert into the given table.\n * @returns - {@link GenericId} of the new document.\n */\n // TODO: Chain methods to get the written documents?\n insertMany(\n values: Expand<\n WithoutSystemFields<\n WithEdgeInserts<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >\n >[],\n ): Promise<GenericId<Table>[]>;\n}\n\nclass PromiseTableWriterImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseTableImpl<EntsDataModel, Table> {\n private base: WriterImplBase<EntsDataModel, Table>;\n\n constructor(\n protected ctx: EntMutationCtx<EntsDataModel>,\n entDefinitions: EntsDataModel,\n table: Table,\n ) {\n super(ctx, entDefinitions, table);\n this.base = new WriterImplBase(ctx, entDefinitions, table);\n }\n\n insert(\n value: WithoutSystemFields<\n WithEdgeInserts<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ) {\n return new PromiseEntIdImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n await this.base.checkReadAndWriteRule(\"create\", undefined, value);\n await this.base.checkUniqueness(value);\n const fields = this.base.fieldsOnly(value as any);\n const docId = await this.ctx.db.insert(this.table, fields as any);\n const edges: EdgeChanges = {};\n Object.keys(value).forEach((key) => {\n const edgeDefinition = getEdgeDefinitions(\n this.entDefinitions,\n this.table,\n )[key];\n if (\n edgeDefinition === undefined ||\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"field\")\n ) {\n return;\n }\n\n edges[key] = {\n add:\n edgeDefinition.cardinality === \"single\"\n ? [value[key] as GenericId<any>]\n : (value[key] as GenericId<any>[]),\n };\n });\n await this.base.writeEdges(docId, edges);\n return docId;\n },\n );\n }\n\n // TODO: fluent API\n async insertMany(\n values: WithoutSystemFields<\n WithEdgeInserts<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >[],\n ) {\n return await Promise.all(values.map((value) => this.insert(value)));\n }\n}\n\nexport interface PromiseEntWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null> {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeWriterOrNull<EntsDataModel, Table, Edge>;\n\n doc(): Promise<DocumentByName<EntsDataModel, Table> | null>;\n}\n\nexport interface PromiseEntWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeWriter<EntsDataModel, Table, Edge>;\n\n edgeX<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeWriterOrThrow<EntsDataModel, Table, Edge>;\n\n doc(): Promise<DocumentByName<EntsDataModel, Table>>;\n\n /**\n * Patch this existing document, shallow merging it with the given partial\n * document.\n *\n * New fields are added. Existing fields are overwritten. Fields set to\n * `undefined` are removed.\n *\n * @param value - The partial {@link GenericDocument} to merge into this document. If this new value\n * specifies system fields like `_id`, they must match the document's existing field values.\n */\n patch(\n value: Partial<\n Expand<\n WithEdgePatches<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n\n /**\n * Replace the value of an existing document, overwriting its old value.\n *\n * @param value - The new {@link GenericDocument} for the document. This value can omit the system fields,\n * and the database will preserve them in.\n */\n replace(\n value: Expand<\n WithOptionalSystemFields<\n WithEdges<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n\n /**\n * Delete this existing document.\n *\n * @param id - The {@link GenericId} of the document to remove.\n */\n delete(): Promise<GenericId<Table>>;\n}\n\nclass PromiseEntWriterImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEntOrNullImpl<EntsDataModel, Table> {\n private base: WriterImplBase<EntsDataModel, Table>;\n\n constructor(\n protected ctx: EntMutationCtx<EntsDataModel>,\n protected entDefinitions: EntsDataModel,\n protected table: Table,\n protected retrieve: DocRetriever<\n GenericId<Table> | null,\n DocumentByName<EntsDataModel, Table> | null\n >,\n protected throwIfNull: boolean,\n ) {\n super(ctx, entDefinitions, table, retrieve, throwIfNull);\n this.base = new WriterImplBase(ctx, entDefinitions, table);\n }\n\n patch(\n value: Partial<\n WithEdgePatches<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ) {\n return new PromiseEntIdImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const { id: docId } = await this.retrieve();\n const id = docId!;\n await this.base.checkReadAndWriteRule(\"update\", id, value);\n await this.base.checkUniqueness(value, id);\n const fields = this.base.fieldsOnly(value);\n await this.ctx.db.patch(id, fields);\n\n const edges: EdgeChanges = {};\n await Promise.all(\n Object.keys(value).map(async (key) => {\n const edgeDefinition = getEdgeDefinitions(\n this.entDefinitions,\n this.table,\n )[key];\n if (\n edgeDefinition === undefined ||\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"field\")\n ) {\n // The built-in patch takes care of updating the field\n return;\n }\n if (edgeDefinition.cardinality === \"single\") {\n throw new Error(\n `Cannot set 1:1 edge \"${edgeDefinition.name}\" on ent in table ` +\n `\"${this.table}\", update the ent in \"${edgeDefinition.to}\" ` +\n `table instead.`,\n );\n // const existing = await this.ctx.db\n // .query(edgeDefinition.to)\n // .withIndex(edgeDefinition.ref, (q) =>\n // q.eq(edgeDefinition.ref, docId as any)\n // )\n // .unique();\n\n // edges[key] = {\n // add: value[key] as GenericId<any>,\n // remove: existing?._id as GenericId<any> | undefined,\n // };\n } else {\n if (edgeDefinition.type === \"field\") {\n throw new Error(\n `Cannot set 1:many edges \"${edgeDefinition.name}\" on ent in table ` +\n `\"${this.table}\", update the ents in \"${edgeDefinition.to}\" ` +\n `table instead.`,\n );\n } else {\n const { add, remove } = value[key]!;\n const removeEdges = (\n await Promise.all(\n (remove ?? []).map(async (edgeId) =>\n (\n await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n (q.eq(edgeDefinition.field, id as any) as any).eq(\n edgeDefinition.ref,\n edgeId,\n ),\n )\n .collect()\n ).concat(\n edgeDefinition.symmetric\n ? await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.ref, (q) =>\n (q.eq(edgeDefinition.ref, id as any) as any).eq(\n edgeDefinition.field,\n edgeId,\n ),\n )\n .collect()\n : [],\n ),\n ),\n )\n )\n .flat()\n .map((edgeDoc) => edgeDoc._id as GenericId<any>);\n edges[key] = {\n add,\n removeEdges,\n };\n }\n }\n }),\n );\n await this.base.writeEdges(id, edges);\n return id;\n },\n );\n }\n\n replace(\n value: WithOptionalSystemFields<\n WithEdges<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ) {\n return new PromiseEntIdImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const { id } = await this.retrieve();\n const docId = id!;\n await this.base.checkReadAndWriteRule(\"update\", docId, value);\n await this.base.checkUniqueness(value, docId);\n const fields = this.base.fieldsOnly(value as any);\n await this.ctx.db.replace(docId, fields as any);\n\n const edges: EdgeChanges = {};\n\n await Promise.all(\n Object.values(\n getEdgeDefinitions(this.entDefinitions, this.table),\n ).map(async (edgeDefinition) => {\n const key = edgeDefinition.name;\n const idOrIds = value[key];\n if (edgeDefinition.cardinality === \"single\") {\n if (edgeDefinition.type === \"ref\") {\n const oldDoc = (await this.ctx.db.get(docId))!;\n if (oldDoc[key] !== undefined && oldDoc[key] !== idOrIds) {\n // This would be only allowed if the edge is optional\n // on the field side, which is not supported\n throw new Error(\"Cannot set 1:1 edge from optional end.\");\n // edges[key] = {\n // add: idOrIds as GenericId<any>,\n // remove: oldDoc[key] as GenericId<any> | undefined,\n // };\n }\n }\n } else {\n if (edgeDefinition.type === \"field\") {\n if (idOrIds !== undefined) {\n throw new Error(\"Cannot set 1:many edge from many end.\");\n // const existing = (\n // await this.ctx.db\n // .query(edgeDefinition.to)\n // .withIndex(edgeDefinition.ref, (q) =>\n // q.eq(edgeDefinition.ref, docId as any)\n // )\n // .collect()\n // ).map((doc) => doc._id);\n // edges[key] = {\n // add: idOrIds as GenericId<any>[],\n // remove: { remove: true },\n // };\n }\n } else {\n const requested = new Set(idOrIds ?? []);\n const removeEdges = (\n await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n q.eq(edgeDefinition.field, docId as any),\n )\n .collect()\n )\n .map((doc) => [doc._id, doc[edgeDefinition.ref]] as const)\n .concat(\n edgeDefinition.symmetric\n ? (\n await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, docId as any),\n )\n .collect()\n ).map(\n (doc) =>\n [doc._id, doc[edgeDefinition.field]] as const,\n )\n : [],\n )\n .filter(([_edgeId, otherId]) => {\n if (requested.has(otherId as any)) {\n requested.delete(otherId as any);\n return false;\n }\n return true;\n })\n .map(([edgeId]) => edgeId as GenericId<any>);\n edges[key] = {\n add: (idOrIds ?? []) as GenericId<any>[],\n removeEdges,\n };\n }\n }\n }),\n );\n await this.base.writeEdges(docId, edges);\n return docId;\n },\n );\n }\n\n async delete() {\n const { id: docId } = await this.retrieve();\n const id = docId!;\n return this.base.deleteId(id, \"default\");\n }\n}\n\ndeclare class EntWriterInstance<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeWriter<EntsDataModel, Table, Edge>;\n edgeX<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeWriterOrThrow<EntsDataModel, Table, Edge>;\n doc(): DocumentByName<EntsDataModel, Table>;\n\n /**\n * Patch this existing document, shallow merging it with the given partial\n * document.\n *\n * New fields are added. Existing fields are overwritten. Fields set to\n * `undefined` are removed.\n *\n * @param value - The partial {@link GenericDocument} to merge into this document. If this new value\n * specifies system fields like `_id`, they must match the document's existing field values.\n */\n patch(\n value: Partial<\n WithEdgePatches<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n\n /**\n * Replace the value of this existing document, overwriting its old value.\n *\n * @param value - The new {@link GenericDocument} for the document. This value can omit the system fields,\n * and the database will preserve them in.\n */\n replace(\n value: WithOptionalSystemFields<\n WithEdges<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n\n /**\n * Delete this existing document.\n *\n * @param id - The {@link GenericId} of the document to remove.\n */\n delete(): Promise<GenericId<Table>>;\n}\n\n// This type is strange: The ordering is strange,\n// and the `Doc` would not have to be generic:\n// This is all just so that the type shows useful\n// informatin when hovering values.\ntype EntWriter<\n Table extends TableNamesInDataModel<EntsDataModel>,\n Doc extends DocumentByName<EntsDataModel, Table>,\n EntsDataModel extends GenericEntsDataModel,\n> = Doc & EntWriterInstance<EntsDataModel, Table>;\n\nexport type GenericEntWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> = EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>;\n\nexport interface PromiseEntId<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<GenericId<Table>> {\n get(): PromiseEntWriter<EntsDataModel, Table>;\n}\n\nclass PromiseEntIdImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<GenericId<Table>>\n implements PromiseEntId<EntsDataModel, Table>\n{\n constructor(\n private ctx: EntMutationCtx<EntsDataModel>,\n private entDefinitions: EntsDataModel,\n private table: Table,\n private retrieve: () => Promise<GenericId<Table>>,\n ) {\n super(() => {});\n }\n\n get() {\n return new PromiseEntWriterImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const id = await this.retrieve();\n return { id, doc: async () => this.ctx.db.get(id) };\n },\n true,\n ) as any;\n }\n\n then<TResult1 = GenericId<Table>, TResult2 = never>(\n onfulfilled?:\n | ((value: GenericId<Table>) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.retrieve().then(onfulfilled, onrejected);\n }\n}\n\nexport interface EntQueryCtx<DataModel extends GenericDataModel> {\n db: GenericDatabaseReader<DataModel>;\n}\n\nexport interface EntMutationCtx<DataModel extends GenericDataModel>\n extends EntQueryCtx<DataModel> {\n db: GenericDatabaseWriter<DataModel>;\n\n scheduler: Scheduler;\n}\n\nexport type DocRetriever<ID, Doc> = () => Promise<{\n id: ID;\n doc: () => Promise<Doc>;\n}>;\n\nconst nullRetriever = {\n id: null,\n doc: async () => null,\n};\n\ntype IndexFieldTypesForEq<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n T extends string[],\n> = Pop<{\n [K in keyof T]: FieldTypeFromFieldPath<\n DocumentByName<EntsDataModel, Table>,\n T[K]\n >;\n}>;\n\ntype Pop<T extends any[]> = T extends [...infer Rest, infer _Last]\n ? Rest\n : never;\n\n// function idRetriever<\n// DataModel extends GenericDataModel,\n// Table extends TableNamesInDataModel<DataModel>\n// >(ctx: EntQueryCtx<DataModel>, id: GenericId<Table>) {\n// return {\n// id,\n// doc: async () => ctx.db.get(id),\n// };\n// }\n\nfunction loadedRetriever<\n DataModel extends GenericDataModel,\n Table extends TableNamesInDataModel<DataModel>,\n>(doc: DocumentByName<DataModel, Table> | null) {\n return {\n id: (doc?._id ?? null) as GenericId<Table> | null,\n doc: async () => doc,\n };\n}\n\ntype Rules = Record<string, RuleConfig>;\n\ntype RuleConfig = {\n read?: (doc: GenericDocument) => Promise<boolean>;\n write?: (\n args:\n | {\n operation: \"create\";\n ent: undefined;\n value: WithoutSystemFields<GenericDocument>;\n }\n | {\n operation: \"update\";\n ent: Ent<any, GenericDocument, any>;\n value: Partial<WithoutSystemFields<GenericDocument>>;\n }\n | {\n operation: \"delete\";\n ent: Ent<any, GenericDocument, any>;\n value: undefined;\n },\n ) => Promise<boolean>;\n};\n\nexport function addEntRules<EntsDataModel extends GenericEntsDataModel>(\n entDefinitions: EntsDataModel,\n rules: {\n [Table in keyof EntsDataModel]?: Table extends TableNamesInDataModel<EntsDataModel>\n ? {\n read?: (\n ent: Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >,\n ) => Promise<boolean>;\n write?: (\n args:\n | {\n operation: \"create\";\n ent: undefined;\n value: WithoutSystemFields<\n DocumentByName<EntsDataModel, Table>\n >;\n }\n | {\n operation: \"update\";\n ent: Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >;\n value: Partial<\n WithoutSystemFields<DocumentByName<EntsDataModel, Table>>\n >;\n }\n | {\n operation: \"delete\";\n ent: Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >;\n value: undefined;\n },\n ) => Promise<boolean>;\n }\n : never;\n },\n): EntsDataModel {\n return { ...entDefinitions, rules };\n}\n\nasync function filterByReadRule<Doc extends GenericDocument>(\n ctx: EntQueryCtx<any>,\n entDefinitions: GenericEntsDataModel,\n table: string,\n docs: Doc[] | null,\n throwIfNull: boolean,\n) {\n if (docs === null) {\n return null;\n }\n const readPolicy = getReadRule(entDefinitions, table);\n if (readPolicy === undefined) {\n return docs;\n }\n const decisions = await Promise.all(\n docs.map(async (doc) => {\n const decision = await readPolicy(\n entWrapper(doc, ctx, entDefinitions, table),\n );\n if (throwIfNull && !decision) {\n throw new Error(\n `Document cannot be read with id \\`${\n doc._id as string\n }\\` in table \"${table}\"`,\n );\n }\n return decision;\n }),\n );\n return docs.filter((_, i) => decisions[i]);\n}\n\nfunction getIndexFields(\n entDefinitions: GenericEntsDataModel,\n table: string,\n index: string,\n) {\n return (entDefinitions[table].indexes as unknown as Record<string, string[]>)[\n index\n ];\n}\n\nexport function getReadRule(\n entDefinitions: GenericEntsDataModel,\n table: string,\n) {\n return (entDefinitions.rules as Rules)?.[table]?.read;\n}\n\nexport function getWriteRule(\n entDefinitions: GenericEntsDataModel,\n table: string,\n) {\n return (entDefinitions.rules as Rules)?.[table]?.write;\n}\n\nexport function getEdgeDefinitions<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n>(entDefinitions: EntsDataModel, table: Table) {\n return entDefinitions[table].edges as Record<\n keyof EntsDataModel[Table][\"edges\"],\n EdgeConfig\n >;\n}\n\nexport function getDeletionConfig<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n>(entDefinitions: EntsDataModel, table: Table) {\n return (entDefinitions[table] as any).deletionConfig as\n | DeletionConfig\n | undefined;\n}\n\nfunction isSystemTable(table: string) {\n return table.startsWith(\"_\");\n}\n"],"mappings":"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","names":["import_server","internalMutation","table","edgeDefinition"]}
1
+ {"version":3,"sources":["../src/deletion.ts","../src/writer.ts","../src/functions.ts"],"sourcesContent":["import {\n FunctionReference,\n GenericMutationCtx,\n IndexRangeBuilder,\n RegisteredMutation,\n internalMutationGeneric as internalMutation,\n makeFunctionReference,\n} from \"convex/server\";\nimport { GenericId, Infer, convexToJson, v } from \"convex/values\";\nimport { getEdgeDefinitions } from \"./functions\";\nimport { GenericEntsDataModel } from \"./schema\";\n\nexport type ScheduledDeleteFuncRef = FunctionReference<\n \"mutation\",\n \"internal\",\n {\n origin: Origin;\n stack: Stack;\n inProgress: boolean;\n },\n void\n>;\n\ntype Origin = {\n id: string;\n table: string;\n deletionTime: number;\n};\n\nconst vApproach = v.union(v.literal(\"cascade\"), v.literal(\"paginate\"));\n\ntype Approach = Infer<typeof vApproach>;\n\nexport function scheduledDeleteFactory<\n EntsDataModel extends GenericEntsDataModel,\n>(\n entDefinitions: EntsDataModel,\n options?: {\n scheduledDelete: ScheduledDeleteFuncRef;\n },\n): RegisteredMutation<\n \"internal\",\n { origin: Origin; stack: Stack; inProgress: boolean },\n Promise<void>\n> {\n const selfRef =\n options?.scheduledDelete ??\n (makeFunctionReference(\n \"functions:scheduledDelete\",\n ) as unknown as ScheduledDeleteFuncRef);\n return internalMutation({\n args: {\n origin: v.object({\n id: v.string(),\n table: v.string(),\n deletionTime: v.number(),\n }),\n stack: v.array(\n v.union(\n v.object({\n id: v.string(),\n table: v.string(),\n edges: v.array(\n v.object({\n approach: vApproach,\n table: v.string(),\n indexName: v.string(),\n }),\n ),\n }),\n v.object({\n approach: vApproach,\n cursor: v.union(v.string(), v.null()),\n table: v.string(),\n indexName: v.string(),\n fieldValue: v.any(),\n }),\n ),\n ),\n inProgress: v.boolean(),\n },\n handler: async (ctx, { origin, stack, inProgress }) => {\n const originId = ctx.db.normalizeId(origin.table, origin.id);\n if (originId === null) {\n throw new Error(`Invalid ID \"${origin.id}\" for table ${origin.table}`);\n }\n // Check that we still want to delete\n // Note: Doesn't support scheduled deletion starting with system table\n const doc = await ctx.db.get(originId);\n if (doc.deletionTime !== origin.deletionTime) {\n if (inProgress) {\n console.error(\n `[Ents] Already in-progress scheduled deletion for \"${origin.id}\" was cancelled!`,\n );\n } else {\n console.log(\n `[Ents] Scheduled deletion for \"${origin.id}\" was cancelled`,\n );\n }\n return;\n }\n await progressScheduledDeletion(\n { ctx, entDefinitions, selfRef, origin },\n newCounter(),\n inProgress\n ? stack\n : [\n {\n id: originId,\n table: origin.table,\n edges: getEdgeArgs(entDefinitions, origin.table),\n },\n ],\n );\n },\n });\n}\n\n// Heuristic:\n// Ent at the end of an edge\n// has soft or scheduled deletion behavior && has cascading edges: schedule individually\n// has cascading edges: paginate by 1\n// else: paginate by decent number\nfunction getEdgeArgs(entDefinitions: GenericEntsDataModel, table: string) {\n const edges = getEdgeDefinitions(entDefinitions, table);\n return Object.values(edges).flatMap((edgeDefinition) => {\n if (\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"ref\") ||\n (edgeDefinition.cardinality === \"multiple\" &&\n edgeDefinition.type === \"field\")\n ) {\n const table = edgeDefinition.to;\n const targetEdges = getEdgeDefinitions(entDefinitions, table);\n const hasCascadingEdges = Object.values(targetEdges).some(\n (edgeDefinition) =>\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"ref\") ||\n edgeDefinition.cardinality === \"multiple\",\n );\n const approach = hasCascadingEdges ? \"cascade\" : \"paginate\";\n\n const indexName = edgeDefinition.ref;\n return [{ table, indexName, approach } as const];\n } else if (edgeDefinition.cardinality === \"multiple\") {\n const table = edgeDefinition.table;\n return [\n {\n table,\n indexName: edgeDefinition.field,\n approach: \"paginate\",\n } as const,\n ...(edgeDefinition.symmetric\n ? [\n {\n table,\n indexName: edgeDefinition.ref,\n approach: \"paginate\",\n } as const,\n ]\n : []),\n ];\n } else {\n return [];\n }\n });\n}\n\ntype PaginationArgs = {\n approach: Approach;\n table: string;\n cursor: string | null;\n indexName: string;\n fieldValue: any;\n};\n\ntype EdgeArgs = {\n approach: Approach;\n table: string;\n indexName: string;\n};\n\ntype Stack = (\n | { id: string; table: string; edges: EdgeArgs[] }\n | PaginationArgs\n)[];\n\ntype CascadeCtx = {\n ctx: GenericMutationCtx<any>;\n entDefinitions: GenericEntsDataModel;\n selfRef: ScheduledDeleteFuncRef;\n origin: Origin;\n};\n\nasync function progressScheduledDeletion(\n cascade: CascadeCtx,\n counter: Counter,\n stack: Stack,\n) {\n const { ctx } = cascade;\n const last = stack[stack.length - 1];\n\n if (\"id\" in last) {\n const edgeArgs = last.edges[0];\n if (edgeArgs === undefined) {\n await ctx.db.delete(last.id as GenericId<any>);\n if (stack.length > 1) {\n await continueOrSchedule(cascade, counter, stack.slice(0, -1));\n }\n } else {\n const updated = { ...last, edges: last.edges.slice(1) };\n await paginateOrCascade(\n cascade,\n counter,\n stack.slice(0, -1).concat(updated),\n {\n cursor: null,\n fieldValue: last.id,\n ...edgeArgs,\n },\n );\n }\n } else {\n await paginateOrCascade(cascade, counter, stack, last);\n }\n}\n\nconst MAXIMUM_DOCUMENTS_READ = 8192 / 4;\nconst MAXIMUM_BYTES_READ = 2 ** 18;\n\nasync function paginateOrCascade(\n cascade: CascadeCtx,\n counter: Counter,\n stack: Stack,\n { table, approach, indexName, fieldValue, cursor }: PaginationArgs,\n) {\n const { ctx, entDefinitions } = cascade;\n const { page, continueCursor, isDone, bytesRead } = await paginate(\n ctx,\n { table, indexName, fieldValue },\n {\n cursor,\n ...limitsBasedOnCounter(\n counter,\n approach === \"paginate\"\n ? { numItems: MAXIMUM_DOCUMENTS_READ }\n : { numItems: 1 },\n ),\n },\n );\n\n const updatedCounter = incrementCounter(counter, page.length, bytesRead);\n const updated = {\n approach,\n table,\n cursor: continueCursor,\n indexName,\n fieldValue,\n };\n const relevantStack = cursor === null ? stack : stack.slice(0, -1);\n const updatedStack =\n isDone && (approach === \"paginate\" || page.length === 0)\n ? relevantStack\n : relevantStack.concat(\n approach === \"cascade\"\n ? [\n updated,\n {\n id: page[0]._id,\n table,\n edges: getEdgeArgs(entDefinitions, table),\n },\n ]\n : [updated],\n );\n if (approach === \"paginate\") {\n await Promise.all(page.map((doc) => ctx.db.delete(doc._id)));\n }\n await continueOrSchedule(cascade, updatedCounter, updatedStack);\n}\n\nasync function continueOrSchedule(\n cascade: CascadeCtx,\n counter: Counter,\n stack: Stack,\n) {\n if (shouldSchedule(counter)) {\n const { ctx, selfRef, origin } = cascade;\n await ctx.scheduler.runAfter(0, selfRef, {\n origin,\n stack,\n inProgress: true,\n });\n } else {\n await progressScheduledDeletion(cascade, counter, stack);\n }\n}\n\ntype Counter = {\n numDocuments: number;\n numBytesRead: number;\n};\n\nfunction newCounter() {\n return {\n numDocuments: 0,\n numBytesRead: 0,\n };\n}\n\nfunction incrementCounter(\n counter: Counter,\n numDocuments: number,\n numBytesRead: number,\n) {\n return {\n numDocuments: counter.numDocuments + numDocuments,\n numBytesRead: counter.numBytesRead + numBytesRead,\n };\n}\n\nfunction limitsBasedOnCounter(\n counter: Counter,\n { numItems }: { numItems: number },\n) {\n return {\n numItems: Math.max(1, numItems - counter.numDocuments),\n maximumBytesRead: Math.max(1, MAXIMUM_BYTES_READ - counter.numBytesRead),\n };\n}\n\nfunction shouldSchedule(counter: Counter) {\n return (\n counter.numDocuments >= MAXIMUM_DOCUMENTS_READ ||\n counter.numBytesRead >= MAXIMUM_BYTES_READ\n );\n}\n\nasync function paginate(\n ctx: GenericMutationCtx<any>,\n {\n table,\n indexName,\n fieldValue,\n }: { table: string; indexName: string; fieldValue: any },\n {\n cursor,\n numItems,\n maximumBytesRead,\n }: {\n cursor: string | null;\n numItems: number;\n maximumBytesRead: number;\n },\n) {\n const query = ctx.db\n .query(table)\n .withIndex(indexName, (q) =>\n (q.eq(indexName, fieldValue) as IndexRangeBuilder<any, any, any>).gt(\n \"_creationTime\",\n cursor === null ? cursor : +cursor,\n ),\n );\n\n let bytesRead = 0;\n const results = [];\n let isDone = true;\n\n for await (const doc of query) {\n if (results.length >= numItems) {\n isDone = false;\n break;\n }\n const size = JSON.stringify(convexToJson(doc)).length * 8;\n\n results.push(doc);\n bytesRead += size;\n\n // Check this after we read the doc, since reading it already\n // happened anyway, and to make sure we return at least one\n // result.\n if (bytesRead > maximumBytesRead) {\n isDone = false;\n break;\n }\n }\n return {\n page: results,\n continueCursor:\n results.length === 0\n ? cursor\n : \"\" + results[results.length - 1]._creationTime,\n isDone,\n bytesRead,\n };\n}\n","import {\n DocumentByName,\n GenericDocument,\n TableNamesInDataModel,\n makeFunctionReference,\n} from \"convex/server\";\nimport { GenericId } from \"convex/values\";\nimport {\n EntMutationCtx,\n entWrapper,\n getDeletionConfig,\n getEdgeDefinitions,\n getReadRule,\n getWriteRule,\n} from \"./functions\";\nimport { FieldConfig, GenericEdgeConfig, GenericEntsDataModel } from \"./schema\";\nimport { ScheduledDeleteFuncRef } from \"./deletion\";\n\nexport class WriterImplBase<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> {\n constructor(\n protected ctx: EntMutationCtx<EntsDataModel>,\n protected entDefinitions: EntsDataModel,\n protected table: Table,\n ) {}\n\n async deleteId(id: GenericId<any>, behavior: \"default\" | \"soft\" | \"hard\") {\n await this.checkReadAndWriteRule(\"delete\", id, undefined);\n\n const deletionConfig = getDeletionConfig(this.entDefinitions, this.table);\n\n const isDeletingSoftly =\n behavior !== \"hard\" &&\n deletionConfig !== undefined &&\n (deletionConfig.type === \"soft\" || deletionConfig.type === \"scheduled\");\n\n if (behavior === \"soft\" && !isDeletingSoftly) {\n throw new Error(\n `Cannot soft delete document with ID \"${id}\" in ` +\n `table \"${this.table}\" because it does not have an ` +\n `\"allowSoft\", \"soft\" or \"scheduled\" deletion behavior configured.`,\n );\n }\n const edges: EdgeChanges = {};\n await Promise.all(\n Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(\n async (edgeDefinition) => {\n const key = edgeDefinition.name;\n if (\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"ref\") ||\n (edgeDefinition.cardinality === \"multiple\" &&\n edgeDefinition.type === \"field\")\n ) {\n if (!isDeletingSoftly || edgeDefinition.deletion === \"soft\") {\n const remove = (\n await this.ctx.db\n .query(edgeDefinition.to)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, id as any),\n )\n .collect()\n ).map((doc) => doc._id as GenericId<any>);\n edges[key] = { remove };\n }\n } else if (edgeDefinition.cardinality === \"multiple\") {\n if (!isDeletingSoftly) {\n const removeEdges = (\n await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n q.eq(edgeDefinition.field, id as any),\n )\n .collect()\n )\n .concat(\n edgeDefinition.symmetric\n ? await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, id as any),\n )\n .collect()\n : [],\n )\n .map((doc) => doc._id as GenericId<any>);\n edges[key] = { removeEdges };\n }\n }\n },\n ),\n );\n const deletionTime = +new Date();\n if (isDeletingSoftly) {\n await this.ctx.db.patch(id, { deletionTime });\n } else {\n try {\n await this.ctx.db.delete(id);\n } catch (e) {\n // TODO:\n // For now we're gonna ignore errors here,\n // because we assume that the only error\n // is \"document not found\", which\n // can be caused by concurrent deletions.\n // In the future we could track which\n // edges are being deleted by this mutation,\n // and skip the call to delete altogether\n // - or Convex could implement this.\n }\n }\n await this.writeEdges(id, edges, isDeletingSoftly);\n if (deletionConfig !== undefined && deletionConfig.type === \"scheduled\") {\n const fnRef = ((this.ctx as any).scheduledDelete ??\n makeFunctionReference(\n \"functions:scheduledDelete\",\n )) as ScheduledDeleteFuncRef;\n await this.ctx.scheduler.runAfter(deletionConfig.delayMs ?? 0, fnRef, {\n origin: {\n id,\n table: this.table,\n deletionTime,\n },\n inProgress: false,\n stack: [],\n });\n }\n return id;\n }\n\n async deletedIdIn(id: GenericId<any>, table: string, cascadingSoft: boolean) {\n await new WriterImplBase(this.ctx, this.entDefinitions, table).deleteId(\n id,\n cascadingSoft ? \"soft\" : \"hard\",\n );\n }\n\n async writeEdges(\n docId: GenericId<any>,\n changes: EdgeChanges,\n deleteSoftly?: boolean,\n ) {\n await Promise.all(\n Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(\n async (edgeDefinition) => {\n const idOrIds = changes[edgeDefinition.name];\n if (idOrIds === undefined) {\n return;\n }\n if (\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"ref\") ||\n (edgeDefinition.cardinality === \"multiple\" &&\n edgeDefinition.type === \"field\")\n ) {\n if (idOrIds.remove !== undefined && idOrIds.remove.length > 0) {\n // Cascading delete because 1:many edges are not optional\n // on the stored field end.\n await Promise.all(\n idOrIds.remove.map((id) =>\n this.deletedIdIn(\n id,\n edgeDefinition.to,\n (deleteSoftly ?? false) &&\n edgeDefinition.deletion === \"soft\",\n ),\n ),\n );\n // This would be behavior for optional edge:\n // await Promise.all(\n // idsToDelete.map((id) =>\n // this.ctx.db.patch(id, {\n // [edgeDefinition.ref]: undefined,\n // } as any)\n // )\n // );\n }\n if (idOrIds.add !== undefined && idOrIds.add.length > 0) {\n await Promise.all(\n idOrIds.add.map(async (id) =>\n this.ctx.db.patch(id, {\n [edgeDefinition.ref]: docId,\n } as any),\n ),\n );\n }\n } else if (edgeDefinition.cardinality === \"multiple\") {\n if ((idOrIds.removeEdges ?? []).length > 0) {\n await Promise.all(\n idOrIds.removeEdges!.map(async (id) => {\n try {\n await this.ctx.db.delete(id);\n } catch (e) {\n // TODO:\n // For now we're gonna ignore errors here,\n // because we assume that the only error\n // is \"document not found\", which\n // can be caused by concurrent deletions.\n // In the future we could track which\n // edges are being deleted by this mutation,\n // and skip the call to delete altogether\n // - or Convex could implement this.\n }\n }),\n );\n }\n\n if (idOrIds.add !== undefined) {\n await Promise.all(\n idOrIds.add.map(async (id) => {\n const existing = await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n (q.eq(edgeDefinition.field, docId as any) as any).eq(\n edgeDefinition.ref,\n id,\n ),\n )\n .first();\n if (existing === null) {\n await this.ctx.db.insert(edgeDefinition.table, {\n [edgeDefinition.field]: docId,\n [edgeDefinition.ref]: id,\n } as any);\n if (edgeDefinition.symmetric) {\n await this.ctx.db.insert(edgeDefinition.table, {\n [edgeDefinition.field]: id,\n [edgeDefinition.ref]: docId,\n } as any);\n }\n }\n }),\n );\n }\n }\n },\n ),\n );\n }\n\n async checkUniqueness(value: Partial<GenericDocument>, id?: GenericId<any>) {\n await Promise.all(\n Object.values(\n (this.entDefinitions[this.table] as any).fields as Record<\n string,\n FieldConfig\n >,\n ).map(async (fieldDefinition) => {\n if (fieldDefinition.unique) {\n const key = fieldDefinition.name;\n const fieldValue = value[key];\n const existing = await this.ctx.db\n .query(this.table)\n .withIndex(key, (q) => q.eq(key, value[key] as any))\n .unique();\n if (existing !== null && (id === undefined || existing._id !== id)) {\n throw new Error(\n `In table \"${\n this.table\n }\" cannot create a duplicate document with field \"${key}\" of value \\`${\n fieldValue as string\n }\\`, existing document with ID \"${\n existing._id as string\n }\" already has it.`,\n );\n }\n }\n }),\n );\n await Promise.all(\n Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map(\n async (edgeDefinition) => {\n if (\n edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"field\" &&\n edgeDefinition.unique\n ) {\n const key = edgeDefinition.field;\n if (value[key] === undefined) {\n return;\n }\n // Enforce uniqueness\n const existing = await this.ctx.db\n .query(this.table)\n .withIndex(key, (q) => q.eq(key, value[key] as any))\n .unique();\n if (\n existing !== null &&\n (id === undefined || existing._id !== id)\n ) {\n throw new Error(\n `In table \"${this.table}\" cannot create a duplicate 1:1 edge \"${\n edgeDefinition.name\n }\" to ID \"${\n value[key] as string\n }\", existing document with ID \"${\n existing._id as string\n }\" already has it.`,\n );\n }\n }\n },\n ),\n );\n }\n\n fieldsOnly(\n value: Partial<\n WithEdgePatches<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ) {\n const fields: GenericDocument = {};\n Object.keys(value).forEach((key) => {\n const edgeDefinition = getEdgeDefinitions(\n this.entDefinitions,\n this.table,\n )[key];\n if (\n edgeDefinition === undefined\n // This doesn't do anything because the edge name doesn't match the field name\n // ||\n // (edgeDefinition.cardinality === \"single\" &&\n // edgeDefinition.type === \"field\")\n ) {\n fields[key] = value[key]!;\n }\n });\n return fields;\n }\n\n async checkReadAndWriteRule(\n operation: \"create\" | \"update\" | \"delete\",\n id: GenericId<Table> | undefined,\n value: Partial<GenericDocument> | undefined,\n ) {\n if (id !== undefined) {\n const readPolicy = getReadRule(this.entDefinitions, this.table);\n if (readPolicy !== undefined) {\n const doc = await this.ctx.db.get(id);\n if (doc === null) {\n throw new Error(\n `Cannot update document with ID \"${id}\" in table \"${this.table} because it does not exist\"`,\n );\n }\n const decision = await readPolicy(doc);\n if (!decision) {\n throw new Error(\n `Cannot update document with ID \"${id}\" from table \"${this.table}\"`,\n );\n }\n }\n }\n const writePolicy = getWriteRule(this.entDefinitions, this.table);\n if (writePolicy === undefined) {\n return;\n }\n const ent =\n id === undefined\n ? undefined\n : entWrapper(\n (await this.ctx.db.get(id))!,\n this.ctx,\n this.entDefinitions,\n this.table,\n );\n // Replace allows _id and _creationTime, but rules should not\n // rely on them.\n const { _id, _creationTime, ...safeValue } = value ?? {};\n const decision = await writePolicy({\n operation,\n ent: ent as any,\n value: value !== undefined ? (safeValue as any) : undefined,\n });\n if (!decision) {\n if (id === undefined) {\n throw new Error(\n `Cannot insert into table \"${this.table}\": \\`${JSON.stringify(\n value,\n )}\\``,\n );\n } else if (value === undefined) {\n throw new Error(\n `Cannot delete from table \"${this.table}\" with ID \"${id}\"`,\n );\n } else {\n throw new Error(\n `Cannot update document with ID \"${id}\" in table \"${\n this.table\n }\" with: \\`${JSON.stringify(value)}\\``,\n );\n }\n }\n }\n}\n\nexport type WithEdgeInserts<\n Document extends GenericDocument,\n Edges extends Record<string, GenericEdgeConfig>,\n> = Document & {\n [key in keyof Edges as Edges[key][\"cardinality\"] extends \"single\"\n ? Edges[key][\"type\"] extends \"field\"\n ? never\n : key\n : key]?: Edges[key][\"cardinality\"] extends \"single\"\n ? GenericId<Edges[key][\"to\"]>\n : GenericId<Edges[key][\"to\"]>[];\n};\n\nexport type WithEdges<\n Document extends GenericDocument,\n Edges extends Record<string, GenericEdgeConfig>,\n> = Document & {\n [key in keyof Edges as Edges[key][\"cardinality\"] extends \"multiple\"\n ? Edges[key][\"type\"] extends \"ref\"\n ? key\n : never\n : never]?: GenericId<Edges[key][\"to\"]>[];\n};\n\nexport type WithEdgePatches<\n Document extends GenericDocument,\n Edges extends Record<string, GenericEdgeConfig>,\n> = Document & {\n [key in keyof Edges as Edges[key][\"cardinality\"] extends \"multiple\"\n ? Edges[key][\"type\"] extends \"ref\"\n ? key\n : never\n : never]?: {\n add?: GenericId<Edges[key][\"to\"]>[];\n remove?: GenericId<Edges[key][\"to\"]>[];\n };\n};\n\nexport type EdgeChanges = Record<\n string,\n {\n add?: GenericId<any>[];\n remove?: GenericId<any>[];\n removeEdges?: GenericId<any>[];\n }\n>;\n","import {\n DocumentByName,\n ExpressionOrValue,\n FieldTypeFromFieldPath,\n FilterBuilder,\n GenericDataModel,\n GenericDatabaseReader,\n GenericDatabaseWriter,\n GenericDocument,\n IndexNames,\n IndexRange,\n IndexRangeBuilder,\n NamedIndex,\n NamedSearchIndex,\n NamedTableInfo,\n PaginationOptions,\n PaginationResult,\n Query,\n QueryInitializer,\n Scheduler,\n SearchFilter,\n SearchFilterBuilder,\n SearchIndexNames,\n SystemDataModel,\n TableNamesInDataModel,\n WithOptionalSystemFields,\n WithoutSystemFields,\n} from \"convex/server\";\nimport { GenericId } from \"convex/values\";\nimport {\n DeletionConfig,\n EdgeConfig,\n Expand,\n GenericEdgeConfig,\n GenericEntsDataModel,\n} from \"./schema\";\nimport {\n EdgeChanges,\n WithEdgeInserts,\n WithEdgePatches,\n WithEdges,\n WriterImplBase,\n} from \"./writer\";\nimport { ScheduledDeleteFuncRef } from \"./deletion\";\n\nexport interface PromiseOrderedQueryOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n > {\n filter(\n predicate: (\n q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>,\n ) => ExpressionOrValue<boolean>,\n ): this;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArrayOrNull<TOutput>;\n\n paginate(\n paginationOpts: PaginationOptions,\n ): PromisePaginationResultOrNull<EntsDataModel, Table>;\n\n take(n: number): PromiseEntsOrNull<EntsDataModel, Table>;\n\n first(): PromiseEntOrNull<EntsDataModel, Table>;\n\n unique(): PromiseEntOrNull<EntsDataModel, Table>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>;\n}\n\nexport interface PromiseOrderedQueryWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n > {\n filter(\n predicate: (\n q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>,\n ) => ExpressionOrValue<boolean>,\n ): this;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArrayOrNull<TOutput>;\n\n paginate(\n paginationOpts: PaginationOptions,\n ): PromisePaginationResultOrNull<EntsDataModel, Table>;\n\n take(n: number): PromiseEntsWriterOrNull<EntsDataModel, Table>;\n\n first(): PromiseEntWriterOrNull<EntsDataModel, Table>;\n\n unique(): PromiseEntWriterOrNull<EntsDataModel, Table>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>;\n}\n\nexport interface PromiseQueryOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseOrderedQueryOrNull<EntsDataModel, Table> {\n // TODO: The index variant should not be allowed if\n // this query already used an index\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): PromiseOrderedQueryOrNull<EntsDataModel, Table>;\n}\n\nexport interface PromiseQueryWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseOrderedQueryWriterOrNull<EntsDataModel, Table> {\n // TODO: The index variant should not be allowed if\n // this query already used an index\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): PromiseOrderedQueryWriterOrNull<EntsDataModel, Table>;\n}\n\nexport interface PromiseTableBase<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> {\n getMany<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n values: FieldTypeFromFieldPath<\n DocumentByName<EntsDataModel, Table>,\n Indexes[Index][0]\n >[],\n ): PromiseEntsOrNulls<EntsDataModel, Table>;\n getMany(ids: GenericId<Table>[]): PromiseEntsOrNulls<EntsDataModel, Table>;\n getManyX<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n values: FieldTypeFromFieldPath<\n DocumentByName<EntsDataModel, Table>,\n Indexes[Index][0]\n >[],\n ): PromiseEnts<EntsDataModel, Table>;\n getManyX(ids: GenericId<Table>[]): PromiseEnts<EntsDataModel, Table>;\n /**\n * Returns the string ID format for the ID in a given table, or null if the ID\n * is from a different table or is not a valid ID.\n *\n * This does not guarantee that the ID exists (i.e. `table(\"foo\").get(id)` may return `null`).\n *\n * @param tableName - The name of the table.\n * @param id - The ID string.\n */\n normalizeId(id: string): GenericId<Table> | null;\n}\n\nexport interface PromiseTable<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseQuery<EntsDataModel, Table>,\n PromiseTableBase<EntsDataModel, Table> {\n get<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n ...values: IndexFieldTypesForEq<EntsDataModel, Table, Indexes[Index]>\n ): PromiseEntOrNull<EntsDataModel, Table>;\n get(id: GenericId<Table>): PromiseEntOrNull<EntsDataModel, Table>;\n /**\n * Fetch a unique document from the DB using given index, throw if it doesn't exist.\n */\n getX<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n ...values: IndexFieldTypesForEq<EntsDataModel, Table, Indexes[Index]>\n ): PromiseEnt<EntsDataModel, Table>;\n /**\n * Fetch a document from the DB for a given ID, throw if it doesn't exist.\n */\n getX(id: GenericId<Table>): PromiseEnt<EntsDataModel, Table>;\n /**\n * Query by running a full text search against a search index.\n *\n * Search queries must always search for some text within the index's\n * `searchField`. This query can optionally add equality filters for any\n * `filterFields` specified in the index.\n *\n * Documents will be returned in relevance order based on how well they\n * match the search text.\n *\n * To learn about full text search, see [Indexes](https://docs.convex.dev/text-search).\n *\n * @param indexName - The name of the search index to query.\n * @param searchFilter - A search filter expression constructed with the\n * supplied {@link SearchFilterBuilder}. This defines the full text search to run\n * along with equality filtering to run within the search index.\n * @returns - A query that searches for matching documents, returning them\n * in relevancy order.\n */\n search<\n IndexName extends SearchIndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n indexName: IndexName,\n searchFilter: (\n q: SearchFilterBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedSearchIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => SearchFilter,\n ): PromiseOrderedQuery<EntsDataModel, Table>;\n}\n\nexport interface PromiseOrderedQueryBase<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> {\n filter(\n predicate: (\n q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>,\n ) => ExpressionOrValue<boolean>,\n ): this;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[]>;\n}\n\nexport interface PromiseOrderedQuery<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n >,\n PromiseOrderedQueryBase<EntsDataModel, Table> {\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArray<TOutput>;\n\n paginate(\n paginationOpts: PaginationOptions,\n ): PromisePaginationResult<EntsDataModel, Table>;\n\n take(n: number): PromiseEnts<EntsDataModel, Table>;\n\n first(): PromiseEntOrNull<EntsDataModel, Table>;\n\n firstX(): PromiseEnt<EntsDataModel, Table>;\n\n unique(): PromiseEntOrNull<EntsDataModel, Table>;\n\n uniqueX(): PromiseEnt<EntsDataModel, Table>;\n}\n\nexport interface PromiseQuery<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseOrderedQuery<EntsDataModel, Table> {\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): PromiseOrderedQuery<EntsDataModel, Table>;\n}\n\nclass PromiseQueryOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n >\n implements PromiseQueryOrNull<EntsDataModel, Table>\n{\n constructor(\n protected ctx: EntQueryCtx<EntsDataModel>,\n protected entDefinitions: EntsDataModel,\n protected table: Table,\n protected retrieve: () => Promise<Query<\n NamedTableInfo<EntsDataModel, Table>\n > | null>,\n ) {\n super(() => {});\n }\n\n filter(\n predicate: (\n q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>,\n ) => ExpressionOrValue<boolean>,\n ): any {\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n return query.filter(predicate);\n },\n );\n }\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ) {\n return new PromiseArrayImpl(async () => {\n const array = await this;\n if (array === null) {\n return null as TOutput[] | null;\n }\n return await Promise.all(array.map(callbackFn));\n });\n }\n\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): any {\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n if (indexName !== undefined) {\n return (\n query as QueryInitializer<NamedTableInfo<EntsDataModel, Table>>\n )\n .withIndex(indexName)\n .order(order);\n }\n return query.order(order) as any;\n },\n );\n }\n\n paginate(paginationOpts: PaginationOptions) {\n return new PromisePaginationResultOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n this.retrieve,\n paginationOpts,\n );\n }\n\n take(n: number) {\n return new PromiseEntsOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n return await this._take(n);\n },\n false,\n );\n }\n\n first() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this._take(1);\n if (docs === null) {\n return nullRetriever;\n }\n const [doc] = docs;\n return loadedRetriever(doc);\n },\n false,\n );\n }\n\n firstX() {\n return new PromiseEntWriterImpl(\n this.ctx as any,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this._take(1);\n if (docs === null) {\n return nullRetriever;\n }\n const [doc] = docs;\n if (doc === undefined) {\n throw new Error(\"Query returned no documents\");\n }\n return loadedRetriever(doc);\n },\n false,\n );\n }\n\n unique() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this._take(2);\n if (docs === null) {\n return nullRetriever;\n }\n if (docs.length === 0) {\n return nullRetriever;\n }\n if (docs.length === 2) {\n throw new Error(\"unique() query returned more than one result\");\n }\n const [doc] = docs;\n return loadedRetriever(doc);\n },\n false,\n );\n }\n\n uniqueX() {\n return new PromiseEntWriterImpl(\n this.ctx as any,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this._take(2);\n if (docs === null) {\n return nullRetriever;\n }\n if (docs.length === 0) {\n throw new Error(\"Query returned no documents\");\n }\n if (docs.length === 2) {\n throw new Error(\"unique() query returned more than one result\");\n }\n const [doc] = docs;\n return loadedRetriever(doc);\n },\n true,\n );\n }\n\n async docs() {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n const docs = await query.collect();\n return filterByReadRule(\n this.ctx,\n this.entDefinitions,\n this.table,\n docs,\n false,\n );\n }\n\n then<\n TResult1 =\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n TResult2 = never,\n >(\n onfulfilled?:\n | ((\n value:\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n ) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.docs()\n .then((documents) =>\n documents === null\n ? null\n : documents.map((doc) =>\n entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n ),\n )\n .then(onfulfilled, onrejected);\n }\n\n async _take(n: number) {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n const readPolicy = getReadRule(this.entDefinitions, this.table);\n if (readPolicy === undefined) {\n return await query.take(n);\n }\n let numItems = n;\n const docs = [];\n let hasMore = true;\n const iterator = query[Symbol.asyncIterator]();\n while (hasMore && docs.length < n) {\n const page = [];\n for (let i = 0; i < numItems; i++) {\n const { done, value } = await iterator.next();\n if (done) {\n hasMore = false;\n break;\n }\n page.push(value);\n }\n docs.push(\n ...(await filterByReadRule(\n this.ctx,\n this.entDefinitions,\n this.table,\n page,\n false,\n ))!.slice(0, n - docs.length),\n );\n numItems = Math.min(64, numItems * 2);\n }\n return docs;\n }\n}\n\nexport interface PromisePaginationResultOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<PaginationResult<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > | null> {\n docs(): Promise<PaginationResult<\n DocumentByName<EntsDataModel, Table>\n > | null>;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): Promise<PaginationResult<TOutput> | null>;\n}\n\nexport interface PromisePaginationResult<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n PaginationResult<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n >\n > {\n docs(): Promise<PaginationResult<DocumentByName<EntsDataModel, Table>>>;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): Promise<PaginationResult<TOutput>>;\n}\n\nclass PromisePaginationResultOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<PaginationResult<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > | null>\n implements PromisePaginationResultOrNull<EntsDataModel, Table>\n{\n constructor(\n private ctx: EntQueryCtx<EntsDataModel>,\n private entDefinitions: EntsDataModel,\n private table: Table,\n protected retrieve: () => Promise<Query<\n NamedTableInfo<EntsDataModel, Table>\n > | null>,\n protected paginationOpts: PaginationOptions,\n ) {\n super(() => {});\n }\n\n async map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ) {\n const result = await this;\n if (result === null) {\n return null;\n }\n return {\n ...result,\n page: await Promise.all(result.page.map(callbackFn)),\n };\n }\n\n async docs() {\n const query = await this.retrieve();\n if (query === null) {\n return null;\n }\n const result = await query.paginate(this.paginationOpts);\n return {\n ...result,\n page: (await filterByReadRule(\n this.ctx,\n this.entDefinitions,\n this.table,\n result.page,\n false,\n ))!,\n };\n }\n\n then<\n TResult1 =\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n TResult2 = never,\n >(\n onfulfilled?:\n | ((\n value: PaginationResult<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > | null,\n ) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.docs()\n .then((result) =>\n result === null\n ? null\n : {\n ...result,\n page: result.page.map((doc) =>\n entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n ),\n },\n )\n .then(onfulfilled, onrejected);\n }\n}\n\nclass PromiseTableImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseQueryOrNullImpl<EntsDataModel, Table> {\n constructor(\n ctx: EntQueryCtx<EntsDataModel>,\n entDefinitions: EntsDataModel,\n table: Table,\n ) {\n super(ctx, entDefinitions, table, async () =>\n isSystemTable(table)\n ? (ctx.db.system.query(table as any) as any)\n : ctx.db.query(table),\n );\n }\n\n get(...args: any[]) {\n return this.getImpl(args);\n }\n\n getX(...args: any[]) {\n return this.getImpl(args, true);\n }\n\n getMany(...args: any[]) {\n return this.getManyImpl(args);\n }\n\n getManyX(...args: any[]) {\n return this.getManyImpl(args, true);\n }\n\n getImpl(args: any[], throwIfNull = false) {\n return new PromiseEntWriterImpl(\n this.ctx as any,\n this.entDefinitions,\n this.table,\n args.length === 1\n ? async () => {\n const id = args[0] as GenericId<Table>;\n if (this.normalizeId(id) === null) {\n throw new Error(`Invalid id \\`${id}\\` for table \"${this.table}\"`);\n }\n return {\n id,\n doc: async () => {\n const doc = await (isSystemTable(this.table)\n ? this.ctx.db.system.get(id as any)\n : this.ctx.db.get(id));\n if (throwIfNull && doc === null) {\n throw new Error(\n `Document not found with id \\`${id}\\` in table \"${this.table}\"`,\n );\n }\n return doc;\n },\n } as any; // any because PromiseEntWriterImpl expects non-nullable\n }\n : async () => {\n const [indexName, ...values] = args;\n const fieldNames = getIndexFields(\n this.entDefinitions,\n this.table,\n indexName,\n );\n const doc = await this.ctx.db\n .query(this.table)\n .withIndex(indexName, (q) =>\n values.reduce((q, value, i) => q.eq(fieldNames[i], value), q),\n )\n .unique();\n if (throwIfNull && doc === null) {\n throw new Error(\n `Table \"${this.table}\" does not contain document with field${values.reduce(\n (message, value, i) =>\n `${message} \"${fieldNames[i]}\" = \\`${value}\\``,\n \"\",\n )}`,\n );\n }\n return loadedRetriever(doc);\n },\n throwIfNull,\n );\n }\n\n getManyImpl(args: any[], throwIfNull = false) {\n return new PromiseEntsOrNullImpl(\n this.ctx as any,\n this.entDefinitions as any,\n this.table,\n args.length === 1\n ? async () => {\n const ids = args[0] as GenericId<Table>[];\n ids.forEach((id) => {\n if (this.normalizeId(id) === null) {\n throw new Error(\n `Invalid id \\`${id}\\` for table \"${this.table}\"`,\n );\n }\n });\n return await Promise.all(\n ids.map(async (id) => {\n const doc = await (isSystemTable(this.table)\n ? this.ctx.db.system.get(id as any)\n : this.ctx.db.get(id));\n if (doc === null) {\n throw new Error(\n `Document not found with id \\`${id}\\` in table \"${this.table}\"`,\n );\n }\n return doc;\n }),\n );\n }\n : async () => {\n const [indexName, values] = args;\n return (await Promise.all(\n (values as any[]).map(async (value) => {\n const doc = await this.ctx.db\n .query(this.table)\n .withIndex(indexName, (q) => q.eq(indexName, value))\n .unique();\n if (throwIfNull && doc === null) {\n throw new Error(\n `Table \"${this.table}\" does not contain document with field \"${indexName}\" = \\`${value}\\``,\n );\n }\n return doc;\n }),\n )) as any;\n },\n throwIfNull,\n );\n }\n\n normalizeId(id: string): GenericId<Table> | null {\n return isSystemTable(this.table)\n ? this.ctx.db.system.normalizeId(this.table as any, id)\n : this.ctx.db.normalizeId(this.table, id);\n }\n\n // normalizeId or throw\n normalizeIdX(id: string): GenericId<Table> {\n const normalized = this.normalizeId(id);\n if (normalized === null) {\n throw new Error(`Invalid id \\`${id}\\` for table \"${this.table}\"`);\n }\n return normalized;\n }\n\n withIndex(\n indexName: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n indexRange?: (\n q: IndexRangeBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedIndex<NamedTableInfo<EntsDataModel, Table>, typeof indexName>\n >,\n ) => IndexRange,\n ) {\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const query = await this.retrieve();\n return (\n query as QueryInitializer<NamedTableInfo<EntsDataModel, Table>>\n ).withIndex(indexName, indexRange);\n },\n );\n }\n\n search<\n IndexName extends SearchIndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n indexName: IndexName,\n searchFilter: (\n q: SearchFilterBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedSearchIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => SearchFilter,\n ) {\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const query = await this.retrieve();\n return (\n query as QueryInitializer<NamedTableInfo<EntsDataModel, Table>>\n ).withSearchIndex(indexName, searchFilter) as any;\n },\n );\n }\n}\n\n// This lazy promise materializes objects, so chaining to this type of\n// lazy promise performs one operation for each\n// retrieved document in JavaScript, basically as if using `Promise.all()`.\nexport interface PromiseEntsOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n > {\n // TODO: At this point there is nothing query specific here, and we can either:\n // 1. Return a generic lazy promise of the list.\n // 2. Not give any methods, because they might lead devs down the wrong path.\n // // This just returns the first retrieved document, it does not optimize\n // // the previous steps in the query.\n // first(): PromiseEntOrNull<EntsDataModel, Table>;\n // // This just returns the unique retrieved document, it does not optimize\n // // the previous steps in the query. Otherwise it behaves like db.query().unique().\n // unique(): PromiseEntOrNull<EntsDataModel, Table>;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArrayOrNull<TOutput>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>;\n}\n\nexport interface PromiseEntsWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n | EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null\n > {\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArrayOrNull<TOutput>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>;\n}\n\n// This lazy promise materializes objects, so chaining to this type of\n// lazy promise performs one operation for each\n// retrieved document in JavaScript, basically as if using\n// `Promise.all()`.\nexport interface PromiseEnts<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n > {\n // TODO: At this point there is nothing query specific here, and we can either:\n // 1. Return a generic lazy promise of the list.\n // 2. Not give any methods, because they might lead devs down the wrong path.\n // // This just returns the first retrieved document, it does not optimize\n // // the previous steps in the query.\n // first(): PromiseEntOrNull<EntsDataModel, Table>;\n // // This just returns the first retrieved document, or throws if there\n // // are no documents. It does not optimize the previous steps in the query.\n // firstX(): PromiseEnt<EntsDataModel, Table>;\n // // This just returns the unique retrieved document, it does not optimize\n // // the previous steps in the query. Otherwise it behaves like db.query().unique().\n // unique(): PromiseEntOrNull<EntsDataModel, Table>;\n // // This just returns the unique retrieved document, or throws if there\n // // are no documents. It does not optimize the previous steps in the query.\n // // Otherwise it behaves like db.query().unique().\n // uniqueX(): PromiseEnt<EntsDataModel, Table>;\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArray<TOutput>;\n\n docs(): Promise<DocumentByName<EntsDataModel, Table>[]>;\n}\n\nclass PromiseEntsOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null\n >\n implements PromiseEntsOrNull<EntsDataModel, Table>\n{\n constructor(\n private ctx: EntQueryCtx<EntsDataModel>,\n private entDefinitions: EntsDataModel,\n private table: Table,\n private retrieve: () => Promise<\n DocumentByName<EntsDataModel, Table>[] | null\n >,\n private throwIfNull: boolean,\n ) {\n super(() => {});\n }\n\n map<TOutput>(\n callbackFn: (\n value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>,\n index: number,\n array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[],\n ) => Promise<TOutput> | TOutput,\n ) {\n return new PromiseArrayImpl(async () => {\n const array = await this;\n if (array === null) {\n return null as TOutput[] | null;\n }\n return await Promise.all(array.map(callbackFn));\n });\n }\n\n first() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this.retrieve();\n if (docs === null) {\n return nullRetriever;\n }\n return loadedRetriever(docs[0] ?? null);\n },\n false,\n );\n }\n\n firstX() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this.retrieve();\n if (docs === null) {\n return nullRetriever;\n }\n const doc = docs[0] ?? undefined;\n if (doc === undefined) {\n throw new Error(\"Query returned no documents\");\n }\n return loadedRetriever(doc);\n },\n true,\n );\n }\n\n unique() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this.retrieve();\n if (docs === null) {\n return nullRetriever;\n }\n if (docs.length > 1) {\n throw new Error(\"unique() query returned more than one result\");\n }\n return loadedRetriever(docs[0] ?? null);\n },\n false,\n );\n }\n\n uniqueX() {\n return new PromiseEntOrNullImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const docs = await this.retrieve();\n if (docs === null) {\n return nullRetriever;\n }\n if (docs.length > 1) {\n throw new Error(\"unique() query returned more than one result\");\n }\n if (docs.length < 1) {\n throw new Error(\"unique() query returned no documents\");\n }\n return loadedRetriever(docs[0]);\n },\n true,\n );\n }\n\n async docs() {\n const docs = await this.retrieve();\n return filterByReadRule(\n this.ctx,\n this.entDefinitions,\n this.table,\n docs,\n this.throwIfNull,\n );\n }\n\n then<\n TResult1 =\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n TResult2 = never,\n >(\n onfulfilled?:\n | ((\n value:\n | Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n | null,\n ) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.docs()\n .then((docs) =>\n docs === null\n ? null\n : docs.map((doc) =>\n entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n ),\n )\n .then(onfulfilled, onrejected);\n }\n}\n\n// This lazy promise materializes objects, so chaining to this type of\n// lazy promise performs one operation for each\n// retrieved document in JavaScript, basically as if using\n// `Promise.all()`.\nexport interface PromiseEntsOrNulls<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n (Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel> | null)[]\n > {}\n\nexport interface PromiseEdgeEntsOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEntsOrNull<EntsDataModel, Table> {\n /**\n * Returns whether there is an ent with given ID on the other side\n * the edge. Returns null if chained to a null result.\n * @param id The ID of the ent on the other end of the edge\n */\n has(id: GenericId<Table>): Promise<boolean | null>;\n}\n\nexport interface PromiseEdgeEntsWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEntsWriterOrNull<EntsDataModel, Table> {\n /**\n * Returns whether there is an ent with given ID on the other side\n * the edge. Returns null if chained to a null result.\n * @param id The ID of the ent on the other end of the edge\n */\n has(id: GenericId<Table>): Promise<boolean | null>;\n}\n\nexport interface PromiseEdgeEnts<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEnts<EntsDataModel, Table> {\n /**\n * Returns whether there is an ent with given ID on the other side\n * the edge.\n * @param id The ID of the ent on the other end of the edge\n */\n has(id: GenericId<Table>): Promise<boolean>;\n}\n\nexport interface PromiseEdgeEntsWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEntsWriter<EntsDataModel, Table> {\n /**\n * Returns whether there is an ent with given ID on the other side\n * the edge.\n * @param id The ID of the ent on the other end of the edge\n */\n has(id: GenericId<Table>): Promise<boolean>;\n}\n\nclass PromiseEdgeOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends PromiseEntsOrNullImpl<EntsDataModel, Table>\n implements PromiseEdgeEntsOrNull<EntsDataModel, Table>\n{\n constructor(\n ctx: EntQueryCtx<EntsDataModel>,\n entDefinitions: EntsDataModel,\n table: Table,\n private field: string,\n private retrieveRange: (\n indexRange: (\n q: IndexRangeBuilder<DocumentByName<EntsDataModel, Table>, any>,\n ) => any,\n ) => Promise<DocumentByName<EntsDataModel, Table>[] | null>,\n ) {\n super(ctx, entDefinitions, table, () => retrieveRange((q) => q), false);\n }\n\n async has(id: GenericId<Table>) {\n const docs = await this.retrieveRange((q) => q.eq(this.field, id as any));\n return (docs?.length ?? 0) > 0;\n }\n}\n\nexport interface PromiseEntOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null> {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeOrNull<EntsDataModel, Table, Edge>;\n\n doc(): Promise<DocumentByName<EntsDataModel, Table> | null>;\n}\n\nexport interface PromiseEnt<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdge<EntsDataModel, Table, Edge>;\n\n edgeX<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeOrThrow<EntsDataModel, Table, Edge>;\n\n doc(): Promise<DocumentByName<EntsDataModel, Table>>;\n}\n\nclass PromiseEntOrNullImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null>\n implements PromiseEntOrNull<EntsDataModel, Table>\n{\n constructor(\n protected ctx: EntQueryCtx<EntsDataModel>,\n protected entDefinitions: EntsDataModel,\n protected table: Table,\n protected retrieve: DocRetriever<\n GenericId<Table> | null,\n DocumentByName<EntsDataModel, Table> | null\n >,\n protected throwIfNull: boolean,\n ) {\n super(() => {});\n }\n\n async doc() {\n const { id, doc: getDoc } = await this.retrieve();\n if (id === null) {\n return null;\n }\n const doc = await getDoc();\n if (doc === null) {\n return null;\n }\n const readPolicy = getReadRule(this.entDefinitions, this.table);\n if (readPolicy !== undefined) {\n const decision = await readPolicy(\n entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n );\n if (this.throwIfNull && !decision) {\n throw new Error(\n `Document cannot be read with id \\`${doc._id as string}\\` in table \"${\n this.table\n }\"`,\n );\n }\n return decision ? doc : null;\n }\n return doc;\n }\n\n then<\n TResult1 = Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null,\n TResult2 = never,\n >(\n onfulfilled?:\n | ((\n value: Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null,\n ) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.doc()\n .then((doc) =>\n doc === null\n ? null\n : entWrapper(doc, this.ctx, this.entDefinitions, this.table),\n )\n .then(onfulfilled, onrejected);\n }\n\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(edge: Edge) {\n return this.edgeImpl(edge);\n }\n\n edgeX<Edge extends keyof EntsDataModel[Table][\"edges\"]>(edge: Edge) {\n return this.edgeImpl(edge, true);\n }\n\n edgeImpl<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n throwIfNull = false,\n ) {\n const edgeDefinition = getEdgeDefinitions(this.entDefinitions, this.table)[\n edge\n ];\n\n if (edgeDefinition.cardinality === \"multiple\") {\n if (edgeDefinition.type === \"ref\") {\n return new PromiseEdgeOrNullImpl(\n this.ctx,\n this.entDefinitions,\n edgeDefinition.to,\n edgeDefinition.ref,\n async (indexRange) => {\n const { id } = await this.retrieve();\n if (id === null) {\n return null;\n }\n const edgeDocs = await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n indexRange(q.eq(edgeDefinition.field, id as any) as any),\n )\n .collect();\n return (\n await Promise.all(\n edgeDocs.map((edgeDoc) =>\n this.ctx.db.get(edgeDoc[edgeDefinition.ref] as any),\n ),\n )\n ).filter(<TValue>(doc: TValue | null, i: number): doc is TValue => {\n if (doc === null) {\n throw new Error(\n `Dangling reference for edge \"${edgeDefinition.name}\" in ` +\n `table \"${this.table}\" for document with ID \"${id}\": ` +\n `Could not find a document with ID \"${\n edgeDocs[i][edgeDefinition.field] as string\n }\"` +\n ` in table \"${edgeDefinition.to}\" (edge document ID is \"${\n edgeDocs[i]._id as string\n }\").`,\n );\n }\n return true;\n });\n },\n ) as any;\n }\n return new PromiseQueryOrNullImpl(\n this.ctx,\n this.entDefinitions,\n edgeDefinition.to,\n async () => {\n const { id } = await this.retrieve();\n if (id === null) {\n return null;\n }\n return this.ctx.db\n .query(edgeDefinition.to)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, id as any),\n );\n },\n ) as any;\n }\n\n return new PromiseEntWriterImpl(\n this.ctx as any,\n this.entDefinitions,\n edgeDefinition.to,\n async () => {\n const { id, doc: getDoc } = await this.retrieve();\n if (id === null) {\n return nullRetriever;\n }\n\n if (edgeDefinition.type === \"ref\") {\n const otherDoc = await this.ctx.db\n .query(edgeDefinition.to)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, id as any),\n )\n .unique();\n if (throwIfNull && otherDoc === null) {\n throw new Error(\n `Edge \"${\n edgeDefinition.name\n }\" does not exist for document with ID \"${id as string}\"`,\n );\n }\n return loadedRetriever(otherDoc);\n }\n const doc = (await getDoc())!;\n const otherId = doc[edgeDefinition.field] as any;\n return {\n id: otherId,\n doc: async () => {\n const otherDoc = await this.ctx.db.get(otherId);\n if (otherDoc === null) {\n throw new Error(\n `Dangling reference for edge \"${edgeDefinition.name}\" in ` +\n `table \"${this.table}\" for document with ID \"${id}\": ` +\n `Could not find a document with ID \"${otherId}\"` +\n ` in table \"${edgeDefinition.to}\".`,\n );\n }\n return otherDoc;\n },\n };\n },\n throwIfNull,\n ) as any;\n }\n}\n\nexport interface PromiseArrayOrNull<T> extends Promise<T[] | null> {\n filter<S extends T>(\n predicate: (value: T, index: number, array: T[] | null) => value is S,\n ): Promise<S[] | null>;\n\n filter(\n predicate: (value: T, index: number, array: T[] | null) => unknown,\n ): Promise<T[] | null>;\n}\n\nexport interface PromiseArray<T> extends Promise<T[]> {\n filter<S extends T>(\n predicate: (value: T, index: number, array: T[]) => value is S,\n ): Promise<S[]>;\n\n filter(\n predicate: (value: T, index: number, array: T[]) => unknown,\n ): Promise<T[]>;\n}\n\nclass PromiseArrayImpl<T>\n extends Promise<T[] | null>\n implements PromiseArrayOrNull<T>\n{\n constructor(protected retrieve: () => Promise<T[] | null>) {\n super(() => {});\n }\n\n async filter<S extends T>(\n predicate: (value: T, index: number, array: T[] | null) => value is S,\n ) {\n const array = await this.retrieve();\n if (array === null) {\n return null;\n }\n return array.filter(predicate);\n }\n\n then<TResult1 = T[] | null, TResult2 = never>(\n onfulfilled?:\n | ((value: T[] | null) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.retrieve().then(onfulfilled, onrejected);\n }\n}\n\nexport function entWrapper<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n>(\n fields: DocumentByName<EntsDataModel, Table>,\n ctx: EntQueryCtx<EntsDataModel>,\n entDefinitions: EntsDataModel,\n table: Table,\n): Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel> {\n const doc = { ...fields };\n const queryInterface = new PromiseEntWriterImpl(\n ctx as any,\n entDefinitions as any,\n table,\n async () => ({ id: doc._id as any, doc: async () => doc }),\n // this `true` doesn't matter, the queryInterface cannot be awaited\n true,\n );\n Object.defineProperty(doc, \"edge\", {\n value: (edge: any) => {\n return queryInterface.edge(edge);\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"edgeX\", {\n value: (edge: any) => {\n return queryInterface.edgeX(edge);\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"doc\", {\n value: () => {\n return doc;\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"patch\", {\n value: (value: any) => {\n return queryInterface.patch(value);\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"replace\", {\n value: (value: any) => {\n return queryInterface.replace(value);\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.defineProperty(doc, \"delete\", {\n value: () => {\n return queryInterface.delete();\n },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n Object.entries((entDefinitions as any)[table]?.defaults ?? []).map(\n ([field, value]) => {\n if (doc[field] === undefined) {\n (doc as any)[field] = value;\n }\n },\n );\n return doc as any;\n}\n\nexport function entsTableFactory<\n Ctx extends EntQueryCtx<any>,\n EntsDataModel extends GenericEntsDataModel,\n>(\n ctx: Ctx,\n entDefinitions: EntsDataModel,\n options?: {\n scheduledDelete: ScheduledDeleteFuncRef;\n },\n): Ctx extends EntMutationCtx<any>\n ? EntsTableWriter<EntsDataModel>\n : EntsTable<EntsDataModel> {\n const enrichedCtx = options !== undefined ? { ...ctx, ...options } : ctx;\n const table = (\n table: TableNamesInDataModel<EntsDataModel>,\n indexName?: string,\n indexRange?: any,\n ) => {\n // Consider being strict here if people struggle with setup:\n // if (typeof ctx.db?.query !== \"function\") {\n // throw new Error(\n // `Expected context with \\`db\\`, got \\`${JSON.stringify(ctx)}\\``\n // );\n // }\n if (typeof table !== \"string\") {\n throw new Error(`Expected table name, got \\`${table as any}\\``);\n }\n if (indexName !== undefined) {\n return new PromiseTableImpl(\n enrichedCtx as any,\n entDefinitions,\n table,\n ).withIndex(indexName, indexRange);\n }\n if ((ctx.db as any).insert !== undefined) {\n return new PromiseTableWriterImpl(\n enrichedCtx as any,\n entDefinitions,\n table,\n ) as any;\n }\n return new PromiseTableImpl(enrichedCtx as any, entDefinitions, table);\n };\n table.system = table;\n return table;\n}\n\ntype EntsTableReader<EntsDataModel extends GenericEntsDataModel> = {\n <\n Table extends TableNamesInDataModel<EntsDataModel>,\n IndexName extends IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n table: Table,\n indexName: IndexName,\n indexRange?: (\n q: IndexRangeBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => IndexRange,\n ): PromiseQuery<EntsDataModel, Table>;\n <Table extends TableNamesInDataModel<EntsDataModel>>(\n table: Table,\n ): PromiseTable<EntsDataModel, Table>;\n};\n\nexport type EntsTable<EntsDataModel extends GenericEntsDataModel> =\n EntsTableReader<EntsDataModel> & {\n system: EntsTableReader<EntsSystemDataModel>;\n };\n\ntype EntsSystemDataModel = {\n [key in keyof SystemDataModel]: SystemDataModel[key] & {\n edges: Record<string, never>;\n };\n};\n\nexport type EntsTableWriter<EntsDataModel extends GenericEntsDataModel> = {\n <\n Table extends TableNamesInDataModel<EntsDataModel>,\n IndexName extends IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n table: Table,\n indexName: IndexName,\n indexRange?: (\n q: IndexRangeBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => IndexRange,\n ): PromiseTableWriter<Table, EntsDataModel>;\n <Table extends TableNamesInDataModel<EntsDataModel>>(\n table: Table,\n ): PromiseTableWriter<Table, EntsDataModel>;\n\n system: EntsTableReader<EntsSystemDataModel>;\n};\n\ndeclare class EntInstance<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdge<EntsDataModel, Table, Edge>;\n edgeX<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeOrThrow<EntsDataModel, Table, Edge>;\n doc(): DocumentByName<EntsDataModel, Table>;\n}\n\nexport type Ent<\n Table extends TableNamesInDataModel<EntsDataModel>,\n Doc extends DocumentByName<EntsDataModel, Table>,\n EntsDataModel extends GenericEntsDataModel,\n> = Doc & EntInstance<EntsDataModel, Table>;\n\nexport type GenericEnt<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> = Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>;\n\ntype PromiseEdgeResult<\n EdgeConfig extends GenericEdgeConfig,\n MultipleRef,\n MultipleField,\n SingleRef,\n SingleField,\n> = EdgeConfig[\"cardinality\"] extends \"multiple\"\n ? EdgeConfig[\"type\"] extends \"ref\"\n ? MultipleRef\n : MultipleField\n : EdgeConfig[\"type\"] extends \"ref\"\n ? SingleRef\n : SingleField;\n\nexport type PromiseEdge<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEnts<EntsDataModel, ToTable>,\n PromiseQuery<EntsDataModel, ToTable>,\n PromiseEntOrNull<EntsDataModel, ToTable>,\n PromiseEnt<EntsDataModel, ToTable>\n>;\n\nexport type PromiseEdgeOrThrow<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEnts<EntsDataModel, ToTable>,\n PromiseQuery<EntsDataModel, ToTable>,\n PromiseEnt<EntsDataModel, ToTable>,\n PromiseEnt<EntsDataModel, ToTable>\n>;\n\ntype PromiseEdgeOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEntsOrNull<EntsDataModel, ToTable>,\n PromiseQueryOrNull<EntsDataModel, ToTable>,\n PromiseEntOrNull<EntsDataModel, ToTable>,\n PromiseEntOrNull<EntsDataModel, ToTable>\n>;\n\nexport type PromiseEdgeWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEntsWriter<EntsDataModel, ToTable>,\n PromiseQueryWriter<EntsDataModel, ToTable>,\n PromiseEntWriterOrNull<EntsDataModel, ToTable>,\n PromiseEntWriter<EntsDataModel, ToTable>\n>;\n\nexport type PromiseEdgeWriterOrThrow<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEntsWriter<EntsDataModel, ToTable>,\n PromiseQueryWriter<EntsDataModel, ToTable>,\n PromiseEntWriter<EntsDataModel, ToTable>,\n PromiseEntWriter<EntsDataModel, ToTable>\n>;\n\nexport type PromiseEdgeWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n Edge extends keyof EntsDataModel[Table][\"edges\"],\n Config extends GenericEdgeConfig = EntsDataModel[Table][\"edges\"][Edge],\n ToTable extends\n TableNamesInDataModel<EntsDataModel> = EntsDataModel[Table][\"edges\"][Edge][\"to\"],\n> = PromiseEdgeResult<\n Config,\n PromiseEdgeEntsWriterOrNull<EntsDataModel, ToTable>,\n PromiseQueryWriterOrNull<EntsDataModel, ToTable>,\n PromiseEntWriterOrNull<EntsDataModel, ToTable>,\n PromiseEntWriterOrNull<EntsDataModel, ToTable>\n>;\n\nexport interface PromiseOrderedQueryWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]\n >,\n PromiseOrderedQueryBase<EntsDataModel, Table> {\n paginate(\n paginationOpts: PaginationOptions,\n ): PromisePaginationResultWriter<EntsDataModel, Table>;\n\n map<TOutput>(\n callbackFn: (\n value: EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >,\n index: number,\n array: EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >[],\n ) => Promise<TOutput> | TOutput,\n ): PromiseArray<TOutput>;\n\n take(n: number): PromiseEntsWriter<EntsDataModel, Table>;\n\n first(): PromiseEntWriterOrNull<EntsDataModel, Table>;\n\n firstX(): PromiseEntWriter<EntsDataModel, Table>;\n\n unique(): PromiseEntWriterOrNull<EntsDataModel, Table>;\n\n uniqueX(): PromiseEntWriter<EntsDataModel, Table>;\n}\n\nexport interface PromiseQueryWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseOrderedQueryWriter<EntsDataModel, Table> {\n order(\n order: \"asc\" | \"desc\",\n indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>,\n ): PromiseOrderedQueryWriter<EntsDataModel, Table>;\n}\n\n// This lazy promise materializes objects, so chaining to this type of\n// lazy promise performs one operation for each\n// retrieved document in JavaScript, basically as if using\n// `Promise.all()`.\nexport interface PromiseEntsWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEnts<EntsDataModel, Table> {\n // This just returns the first retrieved document, or throws if there\n // are no documents. It does not optimize the previous steps in the query.\n firstX(): PromiseEntWriter<EntsDataModel, Table>;\n\n // This just returns the unique retrieved document, or throws if there\n // are no documents. It does not optimize the previous steps in the query.\n // Otherwise it behaves like db.query().unique().\n uniqueX(): PromiseEntWriter<EntsDataModel, Table>;\n}\n\nexport interface PromisePaginationResultWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n PaginationResult<\n EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n >\n > {\n docs(): Promise<PaginationResult<DocumentByName<EntsDataModel, Table>>>;\n\n map<TOutput>(\n callbackFn: (\n value: EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >,\n index: number,\n array: EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >[],\n ) => Promise<TOutput> | TOutput,\n ): Promise<PaginationResult<TOutput>>;\n}\n\nexport interface PromiseTableWriter<\n Table extends TableNamesInDataModel<EntsDataModel>,\n EntsDataModel extends GenericEntsDataModel,\n> extends PromiseQueryWriter<EntsDataModel, Table>,\n PromiseTableBase<EntsDataModel, Table> {\n get<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n ...values: IndexFieldTypesForEq<EntsDataModel, Table, Indexes[Index]>\n ): PromiseEntWriterOrNull<EntsDataModel, Table>;\n get(id: GenericId<Table>): PromiseEntWriterOrNull<EntsDataModel, Table>;\n /**\n * Fetch a unique document from the DB using given index, throw if it doesn't exist.\n */\n getX<\n Indexes extends EntsDataModel[Table][\"indexes\"],\n Index extends keyof Indexes,\n >(\n indexName: Index,\n ...values: IndexFieldTypesForEq<EntsDataModel, Table, Indexes[Index]>\n ): PromiseEntWriter<EntsDataModel, Table>;\n /**\n * Fetch a document from the DB for a given ID, throw if it doesn't exist.\n */\n getX(id: GenericId<Table>): PromiseEntWriter<EntsDataModel, Table>;\n /**\n * Query by running a full text search against a search index.\n *\n * Search queries must always search for some text within the index's\n * `searchField`. This query can optionally add equality filters for any\n * `filterFields` specified in the index.\n *\n * Documents will be returned in relevance order based on how well they\n * match the search text.\n *\n * To learn about full text search, see [Indexes](https://docs.convex.dev/text-search).\n *\n * @param indexName - The name of the search index to query.\n * @param searchFilter - A search filter expression constructed with the\n * supplied {@link SearchFilterBuilder}. This defines the full text search to run\n * along with equality filtering to run within the search index.\n * @returns - A query that searches for matching documents, returning them\n * in relevancy order.\n */\n search<\n IndexName extends SearchIndexNames<NamedTableInfo<EntsDataModel, Table>>,\n >(\n indexName: IndexName,\n searchFilter: (\n q: SearchFilterBuilder<\n DocumentByName<EntsDataModel, Table>,\n NamedSearchIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>\n >,\n ) => SearchFilter,\n ): PromiseOrderedQueryWriter<EntsDataModel, Table>;\n /**\n * Insert a new document into a table.\n *\n * @param table - The name of the table to insert a new document into.\n * @param value - The {@link Value} to insert into the given table.\n * @returns - {@link GenericId} of the new document.\n */\n // TODO: Chain methods to get the written document?\n insert(\n value: Expand<\n WithoutSystemFields<\n WithEdgeInserts<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n /**\n * Insert new documents into a table.\n *\n * @param table - The name of the table to insert a new document into.\n * @param value - The {@link Value} to insert into the given table.\n * @returns - {@link GenericId} of the new document.\n */\n // TODO: Chain methods to get the written documents?\n insertMany(\n values: Expand<\n WithoutSystemFields<\n WithEdgeInserts<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >\n >[],\n ): Promise<GenericId<Table>[]>;\n}\n\nclass PromiseTableWriterImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseTableImpl<EntsDataModel, Table> {\n private base: WriterImplBase<EntsDataModel, Table>;\n\n constructor(\n protected ctx: EntMutationCtx<EntsDataModel>,\n entDefinitions: EntsDataModel,\n table: Table,\n ) {\n super(ctx, entDefinitions, table);\n this.base = new WriterImplBase(ctx, entDefinitions, table);\n }\n\n insert(\n value: WithoutSystemFields<\n WithEdgeInserts<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ) {\n return new PromiseEntIdImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n await this.base.checkReadAndWriteRule(\"create\", undefined, value);\n await this.base.checkUniqueness(value);\n const fields = this.base.fieldsOnly(value as any);\n const docId = await this.ctx.db.insert(this.table, fields as any);\n const edges: EdgeChanges = {};\n Object.keys(value).forEach((key) => {\n const edgeDefinition = getEdgeDefinitions(\n this.entDefinitions,\n this.table,\n )[key];\n if (\n edgeDefinition === undefined ||\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"field\")\n ) {\n return;\n }\n\n edges[key] = {\n add:\n edgeDefinition.cardinality === \"single\"\n ? [value[key] as GenericId<any>]\n : (value[key] as GenericId<any>[]),\n };\n });\n await this.base.writeEdges(docId, edges);\n return docId;\n },\n );\n }\n\n // TODO: fluent API\n async insertMany(\n values: WithoutSystemFields<\n WithEdgeInserts<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >[],\n ) {\n return await Promise.all(values.map((value) => this.insert(value)));\n }\n}\n\nexport interface PromiseEntWriterOrNull<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<EntWriter<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n > | null> {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeWriterOrNull<EntsDataModel, Table, Edge>;\n\n doc(): Promise<DocumentByName<EntsDataModel, Table> | null>;\n}\n\nexport interface PromiseEntWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<\n EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>\n > {\n edge<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeWriter<EntsDataModel, Table, Edge>;\n\n edgeX<Edge extends keyof EntsDataModel[Table][\"edges\"]>(\n edge: Edge,\n ): PromiseEdgeWriterOrThrow<EntsDataModel, Table, Edge>;\n\n doc(): Promise<DocumentByName<EntsDataModel, Table>>;\n\n /**\n * Patch this existing document, shallow merging it with the given partial\n * document.\n *\n * New fields are added. Existing fields are overwritten. Fields set to\n * `undefined` are removed.\n *\n * @param value - The partial {@link GenericDocument} to merge into this document. If this new value\n * specifies system fields like `_id`, they must match the document's existing field values.\n */\n patch(\n value: Partial<\n Expand<\n WithEdgePatches<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n\n /**\n * Replace the value of an existing document, overwriting its old value.\n *\n * @param value - The new {@link GenericDocument} for the document. This value can omit the system fields,\n * and the database will preserve them in.\n */\n replace(\n value: Expand<\n WithOptionalSystemFields<\n WithEdges<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n\n /**\n * Delete this existing document.\n *\n * @param id - The {@link GenericId} of the document to remove.\n */\n delete(): Promise<GenericId<Table>>;\n}\n\nclass PromiseEntWriterImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends PromiseEntOrNullImpl<EntsDataModel, Table> {\n private base: WriterImplBase<EntsDataModel, Table>;\n\n constructor(\n protected ctx: EntMutationCtx<EntsDataModel>,\n protected entDefinitions: EntsDataModel,\n protected table: Table,\n protected retrieve: DocRetriever<\n GenericId<Table> | null,\n DocumentByName<EntsDataModel, Table> | null\n >,\n protected throwIfNull: boolean,\n ) {\n super(ctx, entDefinitions, table, retrieve, throwIfNull);\n this.base = new WriterImplBase(ctx, entDefinitions, table);\n }\n\n patch(\n value: Partial<\n WithEdgePatches<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ) {\n return new PromiseEntIdImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const { id: docId } = await this.retrieve();\n const id = docId!;\n await this.base.checkReadAndWriteRule(\"update\", id, value);\n await this.base.checkUniqueness(value, id);\n const fields = this.base.fieldsOnly(value);\n await this.ctx.db.patch(id, fields);\n\n const edges: EdgeChanges = {};\n await Promise.all(\n Object.keys(value).map(async (key) => {\n const edgeDefinition = getEdgeDefinitions(\n this.entDefinitions,\n this.table,\n )[key];\n if (\n edgeDefinition === undefined ||\n (edgeDefinition.cardinality === \"single\" &&\n edgeDefinition.type === \"field\")\n ) {\n // The built-in patch takes care of updating the field\n return;\n }\n if (edgeDefinition.cardinality === \"single\") {\n throw new Error(\n `Cannot set 1:1 edge \"${edgeDefinition.name}\" on ent in table ` +\n `\"${this.table}\", update the ent in \"${edgeDefinition.to}\" ` +\n `table instead.`,\n );\n // const existing = await this.ctx.db\n // .query(edgeDefinition.to)\n // .withIndex(edgeDefinition.ref, (q) =>\n // q.eq(edgeDefinition.ref, docId as any)\n // )\n // .unique();\n\n // edges[key] = {\n // add: value[key] as GenericId<any>,\n // remove: existing?._id as GenericId<any> | undefined,\n // };\n } else {\n if (edgeDefinition.type === \"field\") {\n throw new Error(\n `Cannot set 1:many edges \"${edgeDefinition.name}\" on ent in table ` +\n `\"${this.table}\", update the ents in \"${edgeDefinition.to}\" ` +\n `table instead.`,\n );\n } else {\n const { add, remove } = value[key]!;\n const removeEdges = (\n await Promise.all(\n (remove ?? []).map(async (edgeId) =>\n (\n await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n (q.eq(edgeDefinition.field, id as any) as any).eq(\n edgeDefinition.ref,\n edgeId,\n ),\n )\n .collect()\n ).concat(\n edgeDefinition.symmetric\n ? await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.ref, (q) =>\n (q.eq(edgeDefinition.ref, id as any) as any).eq(\n edgeDefinition.field,\n edgeId,\n ),\n )\n .collect()\n : [],\n ),\n ),\n )\n )\n .flat()\n .map((edgeDoc) => edgeDoc._id as GenericId<any>);\n edges[key] = {\n add,\n removeEdges,\n };\n }\n }\n }),\n );\n await this.base.writeEdges(id, edges);\n return id;\n },\n );\n }\n\n replace(\n value: WithOptionalSystemFields<\n WithEdges<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ) {\n return new PromiseEntIdImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const { id } = await this.retrieve();\n const docId = id!;\n await this.base.checkReadAndWriteRule(\"update\", docId, value);\n await this.base.checkUniqueness(value, docId);\n const fields = this.base.fieldsOnly(value as any);\n await this.ctx.db.replace(docId, fields as any);\n\n const edges: EdgeChanges = {};\n\n await Promise.all(\n Object.values(\n getEdgeDefinitions(this.entDefinitions, this.table),\n ).map(async (edgeDefinition) => {\n const key = edgeDefinition.name;\n const idOrIds = value[key];\n if (edgeDefinition.cardinality === \"single\") {\n if (edgeDefinition.type === \"ref\") {\n const oldDoc = (await this.ctx.db.get(docId))!;\n if (oldDoc[key] !== undefined && oldDoc[key] !== idOrIds) {\n // This would be only allowed if the edge is optional\n // on the field side, which is not supported\n throw new Error(\"Cannot set 1:1 edge from optional end.\");\n // edges[key] = {\n // add: idOrIds as GenericId<any>,\n // remove: oldDoc[key] as GenericId<any> | undefined,\n // };\n }\n }\n } else {\n if (edgeDefinition.type === \"field\") {\n if (idOrIds !== undefined) {\n throw new Error(\"Cannot set 1:many edge from many end.\");\n // const existing = (\n // await this.ctx.db\n // .query(edgeDefinition.to)\n // .withIndex(edgeDefinition.ref, (q) =>\n // q.eq(edgeDefinition.ref, docId as any)\n // )\n // .collect()\n // ).map((doc) => doc._id);\n // edges[key] = {\n // add: idOrIds as GenericId<any>[],\n // remove: { remove: true },\n // };\n }\n } else {\n const requested = new Set(idOrIds ?? []);\n const removeEdges = (\n await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.field, (q) =>\n q.eq(edgeDefinition.field, docId as any),\n )\n .collect()\n )\n .map((doc) => [doc._id, doc[edgeDefinition.ref]] as const)\n .concat(\n edgeDefinition.symmetric\n ? (\n await this.ctx.db\n .query(edgeDefinition.table)\n .withIndex(edgeDefinition.ref, (q) =>\n q.eq(edgeDefinition.ref, docId as any),\n )\n .collect()\n ).map(\n (doc) =>\n [doc._id, doc[edgeDefinition.field]] as const,\n )\n : [],\n )\n .filter(([_edgeId, otherId]) => {\n if (requested.has(otherId as any)) {\n requested.delete(otherId as any);\n return false;\n }\n return true;\n })\n .map(([edgeId]) => edgeId as GenericId<any>);\n edges[key] = {\n add: (idOrIds ?? []) as GenericId<any>[],\n removeEdges,\n };\n }\n }\n }),\n );\n await this.base.writeEdges(docId, edges);\n return docId;\n },\n );\n }\n\n async delete() {\n const { id: docId } = await this.retrieve();\n const id = docId!;\n return this.base.deleteId(id, \"default\");\n }\n}\n\ndeclare class EntWriterInstance<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends EntInstance<EntsDataModel, Table> {\n /**\n * Patch this existing document, shallow merging it with the given partial\n * document.\n *\n * New fields are added. Existing fields are overwritten. Fields set to\n * `undefined` are removed.\n *\n * @param value - The partial {@link GenericDocument} to merge into this document. If this new value\n * specifies system fields like `_id`, they must match the document's existing field values.\n */\n patch(\n value: Partial<\n WithEdgePatches<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n\n /**\n * Replace the value of this existing document, overwriting its old value.\n *\n * @param value - The new {@link GenericDocument} for the document. This value can omit the system fields,\n * and the database will preserve them in.\n */\n replace(\n value: WithOptionalSystemFields<\n WithEdges<\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel[Table][\"edges\"]\n >\n >,\n ): PromiseEntId<EntsDataModel, Table>;\n\n /**\n * Delete this existing document.\n *\n * @param id - The {@link GenericId} of the document to remove.\n */\n delete(): Promise<GenericId<Table>>;\n}\n\n// This type is strange: The ordering is strange,\n// and the `Doc` would not have to be generic:\n// This is all just so that the type shows useful\n// informatin when hovering values.\ntype EntWriter<\n Table extends TableNamesInDataModel<EntsDataModel>,\n Doc extends DocumentByName<EntsDataModel, Table>,\n EntsDataModel extends GenericEntsDataModel,\n> = Doc & EntWriterInstance<EntsDataModel, Table>;\n\nexport type GenericEntWriter<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> = EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>;\n\nexport interface PromiseEntId<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n> extends Promise<GenericId<Table>> {\n get(): PromiseEntWriter<EntsDataModel, Table>;\n}\n\nclass PromiseEntIdImpl<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n >\n extends Promise<GenericId<Table>>\n implements PromiseEntId<EntsDataModel, Table>\n{\n constructor(\n private ctx: EntMutationCtx<EntsDataModel>,\n private entDefinitions: EntsDataModel,\n private table: Table,\n private retrieve: () => Promise<GenericId<Table>>,\n ) {\n super(() => {});\n }\n\n get() {\n return new PromiseEntWriterImpl(\n this.ctx,\n this.entDefinitions,\n this.table,\n async () => {\n const id = await this.retrieve();\n return { id, doc: async () => this.ctx.db.get(id) };\n },\n true,\n ) as any;\n }\n\n then<TResult1 = GenericId<Table>, TResult2 = never>(\n onfulfilled?:\n | ((value: GenericId<Table>) => TResult1 | PromiseLike<TResult1>)\n | undefined\n | null,\n onrejected?:\n | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n | undefined\n | null,\n ): Promise<TResult1 | TResult2> {\n return this.retrieve().then(onfulfilled, onrejected);\n }\n}\n\nexport interface EntQueryCtx<DataModel extends GenericDataModel> {\n db: GenericDatabaseReader<DataModel>;\n}\n\nexport interface EntMutationCtx<DataModel extends GenericDataModel>\n extends EntQueryCtx<DataModel> {\n db: GenericDatabaseWriter<DataModel>;\n\n scheduler: Scheduler;\n}\n\nexport type DocRetriever<ID, Doc> = () => Promise<{\n id: ID;\n doc: () => Promise<Doc>;\n}>;\n\nconst nullRetriever = {\n id: null,\n doc: async () => null,\n};\n\ntype IndexFieldTypesForEq<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n T extends string[],\n> = Pop<{\n [K in keyof T]: FieldTypeFromFieldPath<\n DocumentByName<EntsDataModel, Table>,\n T[K]\n >;\n}>;\n\ntype Pop<T extends any[]> = T extends [...infer Rest, infer _Last]\n ? Rest\n : never;\n\n// function idRetriever<\n// DataModel extends GenericDataModel,\n// Table extends TableNamesInDataModel<DataModel>\n// >(ctx: EntQueryCtx<DataModel>, id: GenericId<Table>) {\n// return {\n// id,\n// doc: async () => ctx.db.get(id),\n// };\n// }\n\nfunction loadedRetriever<\n DataModel extends GenericDataModel,\n Table extends TableNamesInDataModel<DataModel>,\n>(doc: DocumentByName<DataModel, Table> | null) {\n return {\n id: (doc?._id ?? null) as GenericId<Table> | null,\n doc: async () => doc,\n };\n}\n\ntype Rules = Record<string, RuleConfig>;\n\ntype RuleConfig = {\n read?: (doc: GenericDocument) => Promise<boolean>;\n write?: (\n args:\n | {\n operation: \"create\";\n ent: undefined;\n value: WithoutSystemFields<GenericDocument>;\n }\n | {\n operation: \"update\";\n ent: Ent<any, GenericDocument, any>;\n value: Partial<WithoutSystemFields<GenericDocument>>;\n }\n | {\n operation: \"delete\";\n ent: Ent<any, GenericDocument, any>;\n value: undefined;\n },\n ) => Promise<boolean>;\n};\n\nexport function addEntRules<EntsDataModel extends GenericEntsDataModel>(\n entDefinitions: EntsDataModel,\n rules: {\n [Table in keyof EntsDataModel]?: Table extends TableNamesInDataModel<EntsDataModel>\n ? {\n read?: (\n ent: Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >,\n ) => Promise<boolean>;\n write?: (\n args:\n | {\n operation: \"create\";\n ent: undefined;\n value: WithoutSystemFields<\n DocumentByName<EntsDataModel, Table>\n >;\n }\n | {\n operation: \"update\";\n ent: Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >;\n value: Partial<\n WithoutSystemFields<DocumentByName<EntsDataModel, Table>>\n >;\n }\n | {\n operation: \"delete\";\n ent: Ent<\n Table,\n DocumentByName<EntsDataModel, Table>,\n EntsDataModel\n >;\n value: undefined;\n },\n ) => Promise<boolean>;\n }\n : never;\n },\n): EntsDataModel {\n return { ...entDefinitions, rules };\n}\n\nasync function filterByReadRule<Doc extends GenericDocument>(\n ctx: EntQueryCtx<any>,\n entDefinitions: GenericEntsDataModel,\n table: string,\n docs: Doc[] | null,\n throwIfNull: boolean,\n) {\n if (docs === null) {\n return null;\n }\n const readPolicy = getReadRule(entDefinitions, table);\n if (readPolicy === undefined) {\n return docs;\n }\n const decisions = await Promise.all(\n docs.map(async (doc) => {\n const decision = await readPolicy(\n entWrapper(doc, ctx, entDefinitions, table),\n );\n if (throwIfNull && !decision) {\n throw new Error(\n `Document cannot be read with id \\`${\n doc._id as string\n }\\` in table \"${table}\"`,\n );\n }\n return decision;\n }),\n );\n return docs.filter((_, i) => decisions[i]);\n}\n\nfunction getIndexFields(\n entDefinitions: GenericEntsDataModel,\n table: string,\n index: string,\n) {\n return (entDefinitions[table].indexes as unknown as Record<string, string[]>)[\n index\n ];\n}\n\nexport function getReadRule(\n entDefinitions: GenericEntsDataModel,\n table: string,\n) {\n return (entDefinitions.rules as Rules)?.[table]?.read;\n}\n\nexport function getWriteRule(\n entDefinitions: GenericEntsDataModel,\n table: string,\n) {\n return (entDefinitions.rules as Rules)?.[table]?.write;\n}\n\nexport function getEdgeDefinitions<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n>(entDefinitions: EntsDataModel, table: Table) {\n return entDefinitions[table].edges as Record<\n keyof EntsDataModel[Table][\"edges\"],\n EdgeConfig\n >;\n}\n\nexport function getDeletionConfig<\n EntsDataModel extends GenericEntsDataModel,\n Table extends TableNamesInDataModel<EntsDataModel>,\n>(entDefinitions: EntsDataModel, table: Table) {\n return (entDefinitions[table] as any).deletionConfig as\n | DeletionConfig\n | undefined;\n}\n\nfunction isSystemTable(table: string) {\n return table.startsWith(\"_\");\n}\n"],"mappings":"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","names":["import_server","internalMutation","table","edgeDefinition"]}