convex-ents
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -1,316 +0,5 @@ | ||
import { GenericEntsDataModel } from './schema.js'; | ||
import * as convex_values from 'convex/values'; | ||
import { GenericId } from 'convex/values'; | ||
import { TableNamesInDataModel, DocumentByName, FilterBuilder, NamedTableInfo, ExpressionOrValue, PaginationOptions, IndexNames, FieldTypeFromFieldPath, SearchIndexNames, SearchFilterBuilder, NamedSearchIndex, SearchFilter, PaginationResult, GenericDatabaseReader, GenericDatabaseWriter, IndexRangeBuilder, NamedIndex, IndexRange, WithoutSystemFields, WithOptionalSystemFields, GenericDocument } from 'convex/server'; | ||
import { WithEdges, WithEdgePatches } from './writer.js'; | ||
interface PromiseOrderedQueryOrNull<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null> { | ||
filter(predicate: (q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>) => ExpressionOrValue<boolean>): this; | ||
map<TOutput>(callbackFn: (value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>, index: number, array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]) => Promise<TOutput> | TOutput): Promise<TOutput[] | null>; | ||
paginate(paginationOpts: PaginationOptions): PromisePaginationResultOrNull<EntsDataModel, Table>; | ||
take(n: number): PromiseEntsOrNull<EntsDataModel, Table>; | ||
first(): PromiseEntOrNull<EntsDataModel, Table>; | ||
unique(): PromiseEntOrNull<EntsDataModel, Table>; | ||
docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>; | ||
} | ||
interface PromiseQueryOrNull<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends PromiseOrderedQueryOrNull<EntsDataModel, Table> { | ||
order(order: "asc" | "desc", indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>): PromiseOrderedQueryOrNull<EntsDataModel, Table>; | ||
} | ||
interface PromiseTableBase<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> { | ||
getMany<Indexes extends EntsDataModel[Table]["indexes"], Index extends keyof Indexes>(indexName: Index, values: FieldTypeFromFieldPath<DocumentByName<EntsDataModel, Table>, Indexes[Index][0]>[]): PromiseEntsOrNulls<EntsDataModel, Table>; | ||
getMany(ids: GenericId<Table>[]): PromiseEntsOrNulls<EntsDataModel, Table>; | ||
getManyX<Indexes extends EntsDataModel[Table]["indexes"], Index extends keyof Indexes>(indexName: Index, values: FieldTypeFromFieldPath<DocumentByName<EntsDataModel, Table>, Indexes[Index][0]>[]): PromiseEnts<EntsDataModel, Table>; | ||
getManyX(ids: GenericId<Table>[]): PromiseEnts<EntsDataModel, Table>; | ||
/** | ||
* Returns the string ID format for the ID in a given table, or null if the ID | ||
* is from a different table or is not a valid ID. | ||
* | ||
* This does not guarantee that the ID exists (i.e. `table("foo").get(id)` may return `null`). | ||
* | ||
* @param tableName - The name of the table. | ||
* @param id - The ID string. | ||
*/ | ||
normalizeId(id: string): GenericId<Table> | null; | ||
} | ||
interface PromiseTable<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends PromiseQuery<EntsDataModel, Table>, PromiseTableBase<EntsDataModel, Table> { | ||
get<Indexes extends EntsDataModel[Table]["indexes"], Index extends keyof Indexes>(indexName: Index, value0: FieldTypeFromFieldPath<DocumentByName<EntsDataModel, Table>, Indexes[Index][0]>): PromiseEntOrNull<EntsDataModel, Table>; | ||
get(id: GenericId<Table>): PromiseEntOrNull<EntsDataModel, Table>; | ||
/** | ||
* Fetch a document from the DB using given index, throw if it doesn't exist. | ||
*/ | ||
getX<Indexes extends EntsDataModel[Table]["indexes"], Index extends keyof Indexes>(indexName: Index, value0: FieldTypeFromFieldPath<DocumentByName<EntsDataModel, Table>, Indexes[Index][0]>): PromiseEnt<EntsDataModel, Table>; | ||
/** | ||
* Fetch a document from the DB for a given ID, throw if it doesn't exist. | ||
*/ | ||
getX(id: GenericId<Table>): PromiseEnt<EntsDataModel, Table>; | ||
/** | ||
* Query by running a full text search against a search index. | ||
* | ||
* Search queries must always search for some text within the index's | ||
* `searchField`. This query can optionally add equality filters for any | ||
* `filterFields` specified in the index. | ||
* | ||
* Documents will be returned in relevance order based on how well they | ||
* match the search text. | ||
* | ||
* To learn about full text search, see [Indexes](https://docs.convex.dev/text-search). | ||
* | ||
* @param indexName - The name of the search index to query. | ||
* @param searchFilter - A search filter expression constructed with the | ||
* supplied {@link SearchFilterBuilder}. This defines the full text search to run | ||
* along with equality filtering to run within the search index. | ||
* @returns - A query that searches for matching documents, returning them | ||
* in relevancy order. | ||
*/ | ||
search<IndexName extends SearchIndexNames<NamedTableInfo<EntsDataModel, Table>>>(indexName: IndexName, searchFilter: (q: SearchFilterBuilder<DocumentByName<EntsDataModel, Table>, NamedSearchIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>>) => SearchFilter): PromiseOrderedQuery<EntsDataModel, Table>; | ||
} | ||
interface PromiseOrderedQueryBase<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> { | ||
filter(predicate: (q: FilterBuilder<NamedTableInfo<EntsDataModel, Table>>) => ExpressionOrValue<boolean>): this; | ||
paginate(paginationOpts: PaginationOptions): PromisePaginationResult<EntsDataModel, Table>; | ||
first(): PromiseEntOrNull<EntsDataModel, Table>; | ||
unique(): PromiseEntOrNull<EntsDataModel, Table>; | ||
docs(): Promise<DocumentByName<EntsDataModel, Table>[]>; | ||
} | ||
interface PromiseOrderedQuery<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]>, PromiseOrderedQueryBase<EntsDataModel, Table> { | ||
map<TOutput>(callbackFn: (value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>, index: number, array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]) => Promise<TOutput> | TOutput): Promise<TOutput[]>; | ||
take(n: number): PromiseEnts<EntsDataModel, Table>; | ||
firstX(): PromiseEnt<EntsDataModel, Table>; | ||
uniqueX(): PromiseEnt<EntsDataModel, Table>; | ||
} | ||
interface PromiseQuery<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends PromiseOrderedQuery<EntsDataModel, Table> { | ||
order(order: "asc" | "desc", indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>): PromiseOrderedQuery<EntsDataModel, Table>; | ||
} | ||
interface PromisePaginationResultOrNull<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<PaginationResult<Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>> | null> { | ||
docs(): Promise<PaginationResult<DocumentByName<EntsDataModel, Table>> | null>; | ||
map<TOutput>(callbackFn: (value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>, index: number, array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]) => Promise<TOutput> | TOutput): Promise<PaginationResult<TOutput> | null>; | ||
} | ||
interface PromisePaginationResult<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<PaginationResult<Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>>> { | ||
docs(): Promise<PaginationResult<DocumentByName<EntsDataModel, Table>>>; | ||
map<TOutput>(callbackFn: (value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>, index: number, array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]) => Promise<TOutput> | TOutput): Promise<PaginationResult<TOutput>>; | ||
} | ||
interface PromiseEntsOrNull<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[] | null> { | ||
map<TOutput>(callbackFn: (value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>, index: number, array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]) => Promise<TOutput> | TOutput): Promise<TOutput[] | null>; | ||
docs(): Promise<DocumentByName<EntsDataModel, Table>[] | null>; | ||
} | ||
interface PromiseEnts<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]> { | ||
map<TOutput>(callbackFn: (value: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>, index: number, array: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]) => Promise<TOutput> | TOutput): Promise<TOutput[]>; | ||
docs(): Promise<DocumentByName<EntsDataModel, Table>[]>; | ||
} | ||
interface PromiseEntsOrNulls<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<(Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel> | null)[]> { | ||
} | ||
interface PromiseEdgeEntsOrNull<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends PromiseEntsOrNull<EntsDataModel, Table> { | ||
/** | ||
* Returns whether there is an ent with given ID on the other side | ||
* the edge. Returns null if chained to a null result. | ||
* @param id The ID of the ent on the other end of the edge | ||
*/ | ||
has(id: GenericId<Table>): Promise<boolean | null>; | ||
} | ||
interface PromiseEdgeEnts<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends PromiseEnts<EntsDataModel, Table> { | ||
/** | ||
* Returns whether there is an ent with given ID on the other side | ||
* the edge. | ||
* @param id The ID of the ent on the other end of the edge | ||
*/ | ||
has(id: GenericId<Table>): Promise<boolean>; | ||
} | ||
interface PromiseEntOrNull<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel> | null> { | ||
edge<Edge extends keyof EntsDataModel[Table]["edges"]>(edge: Edge): PromiseEdgeOrNull<EntsDataModel, Table, Edge>; | ||
doc(): Promise<DocumentByName<EntsDataModel, Table> | null>; | ||
} | ||
interface PromiseEnt<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>> { | ||
edge<Edge extends keyof EntsDataModel[Table]["edges"]>(edge: Edge): PromiseEdge<EntsDataModel, Table, Edge>; | ||
edgeX<Edge extends keyof EntsDataModel[Table]["edges"]>(edge: Edge): PromiseEdgeOrThrow<EntsDataModel, Table, Edge>; | ||
doc(): Promise<DocumentByName<EntsDataModel, Table>>; | ||
} | ||
declare function entWrapper<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>>(fields: DocumentByName<EntsDataModel, Table>, db: GenericDatabaseReader<EntsDataModel>, entDefinitions: EntsDataModel, table: Table): Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>; | ||
declare function entsTableFactory<Database extends GenericDatabaseReader<any>, EntsDataModel extends GenericEntsDataModel>(db: Database, entDefinitions: EntsDataModel): Database extends GenericDatabaseWriter<any> ? EntsTableWriter<EntsDataModel> : EntsTable<EntsDataModel>; | ||
type EntsTable<EntsDataModel extends GenericEntsDataModel> = { | ||
<Table extends TableNamesInDataModel<EntsDataModel>, IndexName extends IndexNames<NamedTableInfo<EntsDataModel, Table>>>(table: Table, indexName: IndexName, indexRange?: (q: IndexRangeBuilder<DocumentByName<EntsDataModel, Table>, NamedIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>>) => IndexRange): PromiseQuery<EntsDataModel, Table>; | ||
<Table extends TableNamesInDataModel<EntsDataModel>>(table: Table): PromiseTable<EntsDataModel, Table>; | ||
}; | ||
type EntsTableWriter<EntsDataModel extends GenericEntsDataModel> = { | ||
<Table extends TableNamesInDataModel<EntsDataModel>, IndexName extends IndexNames<NamedTableInfo<EntsDataModel, Table>>>(table: Table, indexName: IndexName, indexRange?: (q: IndexRangeBuilder<DocumentByName<EntsDataModel, Table>, NamedIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>>) => IndexRange): PromiseTable<EntsDataModel, Table>; | ||
<Table extends TableNamesInDataModel<EntsDataModel>>(table: Table): PromiseTableWriter<Table, EntsDataModel>; | ||
}; | ||
declare class EntInstance<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> { | ||
edge<Edge extends keyof EntsDataModel[Table]["edges"]>(edge: Edge): PromiseEdge<EntsDataModel, Table, Edge>; | ||
edgeX<Edge extends keyof EntsDataModel[Table]["edges"]>(edge: Edge): PromiseEdgeOrThrow<EntsDataModel, Table, Edge>; | ||
} | ||
type Ent<Table extends TableNamesInDataModel<EntsDataModel>, Doc extends DocumentByName<EntsDataModel, Table>, EntsDataModel extends GenericEntsDataModel> = Doc & EntInstance<EntsDataModel, Table>; | ||
type GenericEnt<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> = Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>; | ||
type PromiseEdge<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>, Edge extends keyof EntsDataModel[Table]["edges"]> = EntsDataModel[Table]["edges"][Edge]["cardinality"] extends "multiple" ? EntsDataModel[Table]["edges"][Edge]["type"] extends "ref" ? PromiseEdgeEnts<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]> : PromiseQuery<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]> : EntsDataModel[Table]["edges"][Edge]["type"] extends "ref" ? PromiseEntOrNull<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]> : PromiseEnt<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]>; | ||
type PromiseEdgeOrThrow<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>, Edge extends keyof EntsDataModel[Table]["edges"]> = EntsDataModel[Table]["edges"][Edge]["cardinality"] extends "multiple" ? EntsDataModel[Table]["edges"][Edge]["type"] extends "ref" ? PromiseEdgeEnts<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]> : PromiseQuery<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]> : EntsDataModel[Table]["edges"][Edge]["type"] extends "ref" ? PromiseEnt<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]> : PromiseEnt<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]>; | ||
type PromiseEdgeOrNull<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>, Edge extends keyof EntsDataModel[Table]["edges"]> = EntsDataModel[Table]["edges"][Edge]["cardinality"] extends "multiple" ? EntsDataModel[Table]["edges"][Edge]["type"] extends "ref" ? PromiseEdgeEntsOrNull<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]> : PromiseQueryOrNull<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]> : PromiseEntOrNull<EntsDataModel, EntsDataModel[Table]["edges"][Edge]["to"]>; | ||
interface PromiseOrderedQueryWriter<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]>, PromiseOrderedQueryBase<EntsDataModel, Table> { | ||
paginate(paginationOpts: PaginationOptions): PromisePaginationResultWriter<EntsDataModel, Table>; | ||
map<TOutput>(callbackFn: (value: EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>, index: number, array: EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]) => Promise<TOutput> | TOutput): Promise<TOutput[]>; | ||
take(n: number): PromiseEntsWriter<EntsDataModel, Table>; | ||
firstX(): PromiseEntWriter<EntsDataModel, Table>; | ||
uniqueX(): PromiseEntWriter<EntsDataModel, Table>; | ||
} | ||
interface PromiseQueryWriter<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends PromiseOrderedQueryWriter<EntsDataModel, Table> { | ||
order(order: "asc" | "desc", indexName?: IndexNames<NamedTableInfo<EntsDataModel, Table>>): PromiseOrderedQueryWriter<EntsDataModel, Table>; | ||
} | ||
interface PromiseEntsWriter<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends PromiseEnts<EntsDataModel, Table> { | ||
firstX(): PromiseEntWriter<EntsDataModel, Table>; | ||
uniqueX(): PromiseEntWriter<EntsDataModel, Table>; | ||
} | ||
interface PromisePaginationResultWriter<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<PaginationResult<EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>>> { | ||
docs(): Promise<PaginationResult<DocumentByName<EntsDataModel, Table>>>; | ||
map<TOutput>(callbackFn: (value: EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>, index: number, array: EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>[]) => Promise<TOutput> | TOutput): Promise<PaginationResult<TOutput>>; | ||
} | ||
interface PromiseTableWriter<Table extends TableNamesInDataModel<EntsDataModel>, EntsDataModel extends GenericEntsDataModel> extends PromiseQueryWriter<EntsDataModel, Table>, PromiseTableBase<EntsDataModel, Table> { | ||
get<Indexes extends EntsDataModel[Table]["indexes"], Index extends keyof Indexes>(indexName: Index, value0: FieldTypeFromFieldPath<DocumentByName<EntsDataModel, Table>, Indexes[Index][0]>): PromiseEntWriterOrNull<EntsDataModel, Table>; | ||
get(id: GenericId<Table>): PromiseEntWriterOrNull<EntsDataModel, Table>; | ||
/** | ||
* Fetch a document from the DB using given index, throw if it doesn't exist. | ||
*/ | ||
getX<Indexes extends EntsDataModel[Table]["indexes"], Index extends keyof Indexes>(indexName: Index, value0: FieldTypeFromFieldPath<DocumentByName<EntsDataModel, Table>, Indexes[Index][0]>): PromiseEnt<EntsDataModel, Table>; | ||
/** | ||
* Fetch a document from the DB for a given ID, throw if it doesn't exist. | ||
*/ | ||
getX(id: GenericId<Table>): PromiseEntWriter<EntsDataModel, Table>; | ||
/** | ||
* Query by running a full text search against a search index. | ||
* | ||
* Search queries must always search for some text within the index's | ||
* `searchField`. This query can optionally add equality filters for any | ||
* `filterFields` specified in the index. | ||
* | ||
* Documents will be returned in relevance order based on how well they | ||
* match the search text. | ||
* | ||
* To learn about full text search, see [Indexes](https://docs.convex.dev/text-search). | ||
* | ||
* @param indexName - The name of the search index to query. | ||
* @param searchFilter - A search filter expression constructed with the | ||
* supplied {@link SearchFilterBuilder}. This defines the full text search to run | ||
* along with equality filtering to run within the search index. | ||
* @returns - A query that searches for matching documents, returning them | ||
* in relevancy order. | ||
*/ | ||
search<IndexName extends SearchIndexNames<NamedTableInfo<EntsDataModel, Table>>>(indexName: IndexName, searchFilter: (q: SearchFilterBuilder<DocumentByName<EntsDataModel, Table>, NamedSearchIndex<NamedTableInfo<EntsDataModel, Table>, IndexName>>) => SearchFilter): PromiseOrderedQueryWriter<EntsDataModel, Table>; | ||
/** | ||
* Insert a new document into a table. | ||
* | ||
* @param table - The name of the table to insert a new document into. | ||
* @param value - The {@link Value} to insert into the given table. | ||
* @returns - {@link GenericId} of the new document. | ||
*/ | ||
insert(value: WithoutSystemFields<WithEdges<DocumentByName<EntsDataModel, Table>, EntsDataModel[Table]["edges"]>>): PromiseEntId<EntsDataModel, Table>; | ||
/** | ||
* Insert new documents into a table. | ||
* | ||
* @param table - The name of the table to insert a new document into. | ||
* @param value - The {@link Value} to insert into the given table. | ||
* @returns - {@link GenericId} of the new document. | ||
*/ | ||
insertMany(values: WithoutSystemFields<WithEdges<DocumentByName<EntsDataModel, Table>, EntsDataModel[Table]["edges"]>>[]): Promise<GenericId<Table>[]>; | ||
} | ||
interface PromiseEntWriterOrNull<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel> | null> { | ||
edge<Edge extends keyof EntsDataModel[Table]["edges"]>(edge: Edge): PromiseEdgeOrNull<EntsDataModel, Table, Edge>; | ||
doc(): Promise<DocumentByName<EntsDataModel, Table> | null>; | ||
} | ||
interface PromiseEntWriter<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>> { | ||
edge<Edge extends keyof EntsDataModel[Table]["edges"]>(edge: Edge): PromiseEdge<EntsDataModel, Table, Edge>; | ||
edgeX<Edge extends keyof EntsDataModel[Table]["edges"]>(edge: Edge): PromiseEdgeOrThrow<EntsDataModel, Table, Edge>; | ||
doc(): Promise<DocumentByName<EntsDataModel, Table>>; | ||
/** | ||
* Patch this existing document, shallow merging it with the given partial | ||
* document. | ||
* | ||
* New fields are added. Existing fields are overwritten. Fields set to | ||
* `undefined` are removed. | ||
* | ||
* @param value - The partial {@link GenericDocument} to merge into this document. If this new value | ||
* specifies system fields like `_id`, they must match the document's existing field values. | ||
*/ | ||
patch(value: Partial<WithEdgePatches<DocumentByName<EntsDataModel, Table>, EntsDataModel[Table]["edges"]>>): Promise<PromiseEntId<EntsDataModel, Table>>; | ||
/** | ||
* Replace the value of an existing document, overwriting its old value. | ||
* | ||
* @param value - The new {@link GenericDocument} for the document. This value can omit the system fields, | ||
* and the database will preserve them in. | ||
*/ | ||
replace(value: WithOptionalSystemFields<WithEdges<DocumentByName<EntsDataModel, Table>, EntsDataModel[Table]["edges"]>>): Promise<PromiseEntId<EntsDataModel, Table>>; | ||
/** | ||
* Delete this existing document. | ||
* | ||
* @param id - The {@link GenericId} of the document to remove. | ||
*/ | ||
delete(): Promise<GenericId<Table>>; | ||
} | ||
declare class EntWriterInstance<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends EntInstance<EntsDataModel, Table> { | ||
/** | ||
* Patch this existing document, shallow merging it with the given partial | ||
* document. | ||
* | ||
* New fields are added. Existing fields are overwritten. Fields set to | ||
* `undefined` are removed. | ||
* | ||
* @param value - The partial {@link GenericDocument} to merge into this document. If this new value | ||
* specifies system fields like `_id`, they must match the document's existing field values. | ||
*/ | ||
patch(value: Partial<WithEdgePatches<DocumentByName<EntsDataModel, Table>, EntsDataModel[Table]["edges"]>>): PromiseEntId<EntsDataModel, Table>; | ||
/** | ||
* Replace the value of this existing document, overwriting its old value. | ||
* | ||
* @param value - The new {@link GenericDocument} for the document. This value can omit the system fields, | ||
* and the database will preserve them in. | ||
*/ | ||
replace(value: WithOptionalSystemFields<WithEdges<DocumentByName<EntsDataModel, Table>, EntsDataModel[Table]["edges"]>>): PromiseEntId<EntsDataModel, Table>; | ||
/** | ||
* Delete this existing document. | ||
* | ||
* @param id - The {@link GenericId} of the document to remove. | ||
*/ | ||
delete(): Promise<GenericId<Table>>; | ||
} | ||
type EntWriter<Table extends TableNamesInDataModel<EntsDataModel>, Doc extends DocumentByName<EntsDataModel, Table>, EntsDataModel extends GenericEntsDataModel> = Doc & EntWriterInstance<EntsDataModel, Table>; | ||
type GenericEntWriter<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> = EntWriter<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>; | ||
interface PromiseEntId<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> extends Promise<GenericId<Table>> { | ||
get(): PromiseEntWriter<EntsDataModel, Table>; | ||
} | ||
type DocRetriever<ID, Doc> = () => Promise<{ | ||
id: ID; | ||
doc: () => Promise<Doc>; | ||
}>; | ||
declare function addEntRules<EntsDataModel extends GenericEntsDataModel>(entDefinitions: EntsDataModel, rules: { | ||
[Table in keyof EntsDataModel]?: Table extends TableNamesInDataModel<EntsDataModel> ? { | ||
read?: (ent: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>) => Promise<boolean>; | ||
write?: (args: { | ||
operation: "create"; | ||
ent: undefined; | ||
value: WithoutSystemFields<DocumentByName<EntsDataModel, Table>>; | ||
} | { | ||
operation: "update"; | ||
ent: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>; | ||
value: Partial<WithoutSystemFields<DocumentByName<EntsDataModel, Table>>>; | ||
} | { | ||
operation: "delete"; | ||
ent: Ent<Table, DocumentByName<EntsDataModel, Table>, EntsDataModel>; | ||
value: undefined; | ||
}) => Promise<boolean>; | ||
} : never; | ||
}): EntsDataModel; | ||
declare function getReadRule(entDefinitions: GenericEntsDataModel, table: string): ((doc: GenericDocument) => Promise<boolean>) | undefined; | ||
declare function getWriteRule(entDefinitions: GenericEntsDataModel, table: string): ((args: { | ||
operation: "create"; | ||
ent: undefined; | ||
value: { | ||
[x: string]: convex_values.Value; | ||
}; | ||
} | { | ||
operation: "update"; | ||
ent: Ent<any, GenericDocument, any>; | ||
value: Partial<{ | ||
[x: string]: convex_values.Value; | ||
}>; | ||
} | { | ||
operation: "delete"; | ||
ent: Ent<any, GenericDocument, any>; | ||
value: undefined; | ||
}) => Promise<boolean>) | undefined; | ||
export { type DocRetriever, type Ent, type GenericEnt, type GenericEntWriter, type PromiseEdge, type PromiseEdgeEnts, type PromiseEdgeEntsOrNull, type PromiseEdgeOrThrow, type PromiseEnt, type PromiseEntId, type PromiseEntOrNull, type PromiseEntWriter, type PromiseEntWriterOrNull, type PromiseEnts, type PromiseEntsOrNull, type PromiseEntsOrNulls, type PromiseEntsWriter, type PromiseOrderedQuery, type PromiseOrderedQueryBase, type PromiseOrderedQueryOrNull, type PromiseOrderedQueryWriter, type PromisePaginationResult, type PromisePaginationResultOrNull, type PromisePaginationResultWriter, type PromiseQuery, type PromiseQueryOrNull, type PromiseQueryWriter, type PromiseTable, type PromiseTableBase, type PromiseTableWriter, addEntRules, entWrapper, entsTableFactory, getReadRule, getWriteRule }; | ||
import 'convex/values'; | ||
import 'convex/server'; | ||
import './schema.js'; | ||
export { M as DocRetriever, w as Ent, L as EntMutationCtx, K as EntQueryCtx, G as GenericEnt, I as GenericEntWriter, t as PromiseArray, s as PromiseArrayOrNull, x as PromiseEdge, p as PromiseEdgeEnts, o as PromiseEdgeEntsOrNull, y as PromiseEdgeOrThrow, r as PromiseEnt, J as PromiseEntId, q as PromiseEntOrNull, H as PromiseEntWriter, F as PromiseEntWriterOrNull, m as PromiseEnts, l as PromiseEntsOrNull, n as PromiseEntsOrNulls, B as PromiseEntsWriter, h as PromiseOrderedQuery, g as PromiseOrderedQueryBase, P as PromiseOrderedQueryOrNull, z as PromiseOrderedQueryWriter, k as PromisePaginationResult, j as PromisePaginationResultOrNull, C as PromisePaginationResultWriter, i as PromiseQuery, d as PromiseQueryOrNull, A as PromiseQueryWriter, f as PromiseTable, e as PromiseTableBase, D as PromiseTableWriter, N as addEntRules, u as entWrapper, v as entsTableFactory, S as getDeletionConfig, R as getEdgeDefinitions, O as getReadRule, Q as getWriteRule } from './index--8O_7LM9.js'; | ||
import './deletion.js'; |
@@ -26,2 +26,4 @@ "use strict"; | ||
entsTableFactory: () => entsTableFactory, | ||
getDeletionConfig: () => getDeletionConfig, | ||
getEdgeDefinitions: () => getEdgeDefinitions, | ||
getReadRule: () => getReadRule, | ||
@@ -33,95 +35,104 @@ getWriteRule: () => getWriteRule | ||
// src/writer.ts | ||
var import_server = require("convex/server"); | ||
var WriterImplBase = class _WriterImplBase { | ||
constructor(db, entDefinitions, table) { | ||
this.db = db; | ||
constructor(ctx, entDefinitions, table) { | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
this.table = table; | ||
} | ||
async deleteId(id) { | ||
async deleteId(id, behavior) { | ||
await this.checkReadAndWriteRule("delete", id, void 0); | ||
let memoized = void 0; | ||
const oldDoc = async () => { | ||
if (memoized !== void 0) { | ||
return memoized; | ||
} | ||
return memoized = await this.db.get(id); | ||
}; | ||
const deletionConfig = getDeletionConfig(this.entDefinitions, this.table); | ||
const isDeletingSoftly = behavior !== "hard" && deletionConfig !== void 0 && (deletionConfig.type === "soft" || deletionConfig.type === "scheduled"); | ||
if (behavior === "soft" && !isDeletingSoftly) { | ||
throw new Error( | ||
`Cannot soft delete document with ID "${id}" in table "${this.table}" because it does not have an "allowSoft", "soft" or "scheduled" deletion behavior configured.` | ||
); | ||
} | ||
const edges = {}; | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
).map(async (edgeDefinition) => { | ||
const key = edgeDefinition.name; | ||
if (edgeDefinition.cardinality === "single") { | ||
if (edgeDefinition.type === "ref") { | ||
edges[key] = { | ||
remove: (await oldDoc())[key] | ||
}; | ||
} | ||
} else { | ||
if (edgeDefinition.type === "field") { | ||
const existing = (await this.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id) | ||
).collect()).map((doc) => doc._id); | ||
edges[key] = { remove: existing }; | ||
} else { | ||
const existing = (await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, id) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.db.query(edgeDefinition.table).withIndex( | ||
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map( | ||
async (edgeDefinition) => { | ||
const key = edgeDefinition.name; | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") { | ||
if (!isDeletingSoftly || edgeDefinition.deletion === "soft") { | ||
const remove = (await this.ctx.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id) | ||
).collect() : [] | ||
).map((doc) => doc._id); | ||
edges[key] = { removeEdges: existing }; | ||
).collect()).map((doc) => doc._id); | ||
edges[key] = { remove }; | ||
} | ||
} else if (edgeDefinition.cardinality === "multiple") { | ||
if (!isDeletingSoftly) { | ||
const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, id) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id) | ||
).collect() : [] | ||
).map((doc) => doc._id); | ||
edges[key] = { removeEdges }; | ||
} | ||
} | ||
} | ||
}) | ||
) | ||
); | ||
await this.db.delete(id); | ||
await this.writeEdges(id, edges); | ||
const deletionTime = +/* @__PURE__ */ new Date(); | ||
if (isDeletingSoftly) { | ||
await this.ctx.db.patch(id, { deletionTime }); | ||
} else { | ||
try { | ||
await this.ctx.db.delete(id); | ||
} catch (e) { | ||
} | ||
} | ||
await this.writeEdges(id, edges, isDeletingSoftly); | ||
if (deletionConfig !== void 0 && deletionConfig.type === "scheduled") { | ||
const fnRef = this.ctx.scheduledDelete ?? (0, import_server.makeFunctionReference)( | ||
"functions:scheduledDelete" | ||
); | ||
await this.ctx.scheduler.runAfter(deletionConfig.delayMs ?? 0, fnRef, { | ||
origin: { | ||
id, | ||
table: this.table, | ||
deletionTime | ||
}, | ||
inProgress: false, | ||
stack: [] | ||
}); | ||
} | ||
return id; | ||
} | ||
async deletedIdIn(id, table) { | ||
await new _WriterImplBase(this.db, this.entDefinitions, table).deleteId(id); | ||
async deletedIdIn(id, table, cascadingSoft) { | ||
await new _WriterImplBase(this.ctx, this.entDefinitions, table).deleteId( | ||
id, | ||
cascadingSoft ? "soft" : "hard" | ||
); | ||
} | ||
async writeEdges(docId, changes) { | ||
async writeEdges(docId, changes, deleteSoftly) { | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
).map(async (edgeDefinition) => { | ||
const idOrIds = changes[edgeDefinition.name]; | ||
if (idOrIds === void 0) { | ||
return; | ||
} | ||
if (edgeDefinition.cardinality === "single") { | ||
if (edgeDefinition.type === "ref") { | ||
if (idOrIds.remove !== void 0) { | ||
await this.deletedIdIn( | ||
idOrIds.remove, | ||
edgeDefinition.to | ||
); | ||
} | ||
if (idOrIds.add !== void 0) { | ||
await this.db.patch( | ||
idOrIds.add, | ||
{ [edgeDefinition.ref]: docId } | ||
); | ||
} | ||
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map( | ||
async (edgeDefinition) => { | ||
const idOrIds = changes[edgeDefinition.name]; | ||
if (idOrIds === void 0) { | ||
return; | ||
} | ||
} else { | ||
if (edgeDefinition.type === "field") { | ||
if (idOrIds.remove !== void 0) { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") { | ||
if (idOrIds.remove !== void 0 && idOrIds.remove.length > 0) { | ||
await Promise.all( | ||
idOrIds.remove.map( | ||
(id) => this.deletedIdIn(id, edgeDefinition.to) | ||
(id) => this.deletedIdIn( | ||
id, | ||
edgeDefinition.to, | ||
(deleteSoftly ?? false) && edgeDefinition.deletion === "soft" | ||
) | ||
) | ||
); | ||
} | ||
if (idOrIds.add !== void 0) { | ||
if (idOrIds.add !== void 0 && idOrIds.add.length > 0) { | ||
await Promise.all( | ||
idOrIds.add.map( | ||
async (id) => this.db.patch(id, { | ||
async (id) => this.ctx.db.patch(id, { | ||
[edgeDefinition.ref]: docId | ||
@@ -132,30 +143,8 @@ }) | ||
} | ||
} else { | ||
let removeEdges = []; | ||
if (idOrIds.remove !== void 0) { | ||
removeEdges = (await Promise.all( | ||
idOrIds.remove.map( | ||
async (id) => (await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, docId).eq( | ||
edgeDefinition.ref, | ||
id | ||
) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, docId).eq(edgeDefinition.field, id) | ||
).collect() : [] | ||
) | ||
) | ||
)).map((doc) => doc._id); | ||
} | ||
if (idOrIds.removeEdges !== void 0) { | ||
removeEdges = idOrIds.removeEdges; | ||
} | ||
if (removeEdges.length > 0) { | ||
} else if (edgeDefinition.cardinality === "multiple") { | ||
if ((idOrIds.removeEdges ?? []).length > 0) { | ||
await Promise.all( | ||
removeEdges.map(async (id) => { | ||
idOrIds.removeEdges.map(async (id) => { | ||
try { | ||
await this.db.delete(id); | ||
await this.ctx.db.delete(id); | ||
} catch (e) { | ||
@@ -169,3 +158,3 @@ } | ||
idOrIds.add.map(async (id) => { | ||
await this.db.insert(edgeDefinition.table, { | ||
await this.ctx.db.insert(edgeDefinition.table, { | ||
[edgeDefinition.field]: docId, | ||
@@ -175,3 +164,3 @@ [edgeDefinition.ref]: id | ||
if (edgeDefinition.symmetric) { | ||
await this.db.insert(edgeDefinition.table, { | ||
await this.ctx.db.insert(edgeDefinition.table, { | ||
[edgeDefinition.field]: id, | ||
@@ -186,3 +175,3 @@ [edgeDefinition.ref]: docId | ||
} | ||
}) | ||
) | ||
); | ||
@@ -198,3 +187,3 @@ } | ||
const fieldValue = value[key]; | ||
const existing = await this.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
if (existing !== null && (id === void 0 || existing._id !== id)) { | ||
@@ -209,18 +198,18 @@ throw new Error( | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
).map(async (edgeDefinition) => { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "field" && edgeDefinition.unique) { | ||
const key = edgeDefinition.field; | ||
if (value[key] === void 0) { | ||
return; | ||
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map( | ||
async (edgeDefinition) => { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "field" && edgeDefinition.unique) { | ||
const key = edgeDefinition.field; | ||
if (value[key] === void 0) { | ||
return; | ||
} | ||
const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
if (existing !== null && (id === void 0 || existing._id !== id)) { | ||
throw new Error( | ||
`In table "${this.table}" cannot create a duplicate 1:1 edge "${edgeDefinition.name}" to ID "${value[key]}", existing document with ID "${existing._id}" already has it.` | ||
); | ||
} | ||
} | ||
const existing = await this.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
if (existing !== null && (id === void 0 || existing._id !== id)) { | ||
throw new Error( | ||
`In table "${this.table}" cannot create a duplicate 1:1 edge "${edgeDefinition.name}" to ID "${value[key]}", existing document with ID "${existing._id}" already has it.` | ||
); | ||
} | ||
} | ||
}) | ||
) | ||
); | ||
@@ -231,3 +220,6 @@ } | ||
Object.keys(value).forEach((key) => { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[key]; | ||
const edgeDefinition = getEdgeDefinitions( | ||
this.entDefinitions, | ||
this.table | ||
)[key]; | ||
if (edgeDefinition === void 0) { | ||
@@ -243,3 +235,3 @@ fields[key] = value[key]; | ||
if (readPolicy !== void 0) { | ||
const doc = await this.db.get(id); | ||
const doc = await this.ctx.db.get(id); | ||
if (doc === null) { | ||
@@ -263,4 +255,4 @@ throw new Error( | ||
const ent = id === void 0 ? void 0 : entWrapper( | ||
await this.db.get(id), | ||
this.db, | ||
await this.ctx.db.get(id), | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -297,6 +289,6 @@ this.table | ||
var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise { | ||
constructor(db, entDefinitions, table, retrieve) { | ||
constructor(ctx, entDefinitions, table, retrieve) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -308,3 +300,3 @@ this.table = table; | ||
return new _PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -321,12 +313,14 @@ this.table, | ||
} | ||
async map(callbackFn) { | ||
const array = await this; | ||
if (array === null) { | ||
return []; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
map(callbackFn) { | ||
return new PromiseArrayImpl(async () => { | ||
const array = await this; | ||
if (array === null) { | ||
return null; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
}); | ||
} | ||
order(order, indexName) { | ||
return new _PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -348,3 +342,3 @@ this.table, | ||
return new PromisePaginationResultOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -358,3 +352,3 @@ this.table, | ||
return new PromiseEntsOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -370,3 +364,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -387,3 +381,3 @@ this.table, | ||
return new PromiseEntWriterImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -407,3 +401,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -430,3 +424,3 @@ this.table, | ||
return new PromiseEntWriterImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -458,3 +452,3 @@ this.table, | ||
return filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -469,3 +463,3 @@ this.table, | ||
(documents) => documents === null ? null : documents.map( | ||
(doc) => entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
) | ||
@@ -499,3 +493,3 @@ ).then(onfulfilled, onrejected); | ||
...(await filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -513,6 +507,6 @@ this.table, | ||
var PromisePaginationResultOrNullImpl = class extends Promise { | ||
constructor(db, entDefinitions, table, retrieve, paginationOpts) { | ||
constructor(ctx, entDefinitions, table, retrieve, paginationOpts) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -542,3 +536,3 @@ this.table = table; | ||
page: await filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -556,3 +550,3 @@ this.table, | ||
page: result.page.map( | ||
(doc) => entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
) | ||
@@ -564,4 +558,4 @@ } | ||
var PromiseTableImpl = class extends PromiseQueryOrNullImpl { | ||
constructor(db, entDefinitions, table) { | ||
super(db, entDefinitions, table, async () => db.query(table)); | ||
constructor(ctx, entDefinitions, table) { | ||
super(ctx, entDefinitions, table, async () => ctx.db.query(table)); | ||
} | ||
@@ -582,3 +576,3 @@ get(...args) { | ||
return new PromiseEntWriterImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -588,3 +582,3 @@ this.table, | ||
const id = args[0]; | ||
if (this.db.normalizeId(this.table, id) === null) { | ||
if (this.ctx.db.normalizeId(this.table, id) === null) { | ||
throw new Error(`Invalid id \`${id}\` for table "${this.table}"`); | ||
@@ -595,3 +589,3 @@ } | ||
doc: async () => { | ||
const doc = await this.db.get(id); | ||
const doc = await this.ctx.db.get(id); | ||
if (throwIfNull && doc === null) { | ||
@@ -607,3 +601,3 @@ throw new Error( | ||
const [indexName, value] = args; | ||
const doc = await this.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique(); | ||
const doc = await this.ctx.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique(); | ||
if (throwIfNull && doc === null) { | ||
@@ -621,3 +615,3 @@ throw new Error( | ||
return new PromiseEntsOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -628,3 +622,3 @@ this.table, | ||
ids.forEach((id) => { | ||
if (this.db.normalizeId(this.table, id) === null) { | ||
if (this.ctx.db.normalizeId(this.table, id) === null) { | ||
throw new Error( | ||
@@ -637,3 +631,3 @@ `Invalid id \`${id}\` for table "${this.table}"` | ||
ids.map(async (id) => { | ||
const doc = await this.db.get(id); | ||
const doc = await this.ctx.db.get(id); | ||
if (doc === null) { | ||
@@ -651,3 +645,3 @@ throw new Error( | ||
values.map(async (value) => { | ||
const doc = await this.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique(); | ||
const doc = await this.ctx.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique(); | ||
if (throwIfNull && doc === null) { | ||
@@ -666,3 +660,3 @@ throw new Error( | ||
normalizeId(id) { | ||
return this.db.normalizeId(this.table, id); | ||
return this.ctx.db.normalizeId(this.table, id); | ||
} | ||
@@ -679,3 +673,3 @@ // normalizeId or throw | ||
return new PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -691,3 +685,3 @@ this.table, | ||
return new PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -703,6 +697,6 @@ this.table, | ||
var PromiseEntsOrNullImpl = class extends Promise { | ||
constructor(db, entDefinitions, table, retrieve, throwIfNull) { | ||
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -713,12 +707,14 @@ this.table = table; | ||
} | ||
async map(callbackFn) { | ||
const array = await this; | ||
if (array === null) { | ||
return []; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
map(callbackFn) { | ||
return new PromiseArrayImpl(async () => { | ||
const array = await this; | ||
if (array === null) { | ||
return null; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
}); | ||
} | ||
first() { | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -738,3 +734,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -758,3 +754,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -777,3 +773,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -800,3 +796,3 @@ this.table, | ||
return filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -811,3 +807,3 @@ this.table, | ||
(docs) => docs === null ? null : docs.map( | ||
(doc) => entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
) | ||
@@ -818,4 +814,4 @@ ).then(onfulfilled, onrejected); | ||
var PromiseEdgeOrNullImpl = class extends PromiseEntsOrNullImpl { | ||
constructor(db, entDefinitions, table, field, retrieveRange) { | ||
super(db, entDefinitions, table, () => retrieveRange((q) => q), false); | ||
constructor(ctx, entDefinitions, table, field, retrieveRange) { | ||
super(ctx, entDefinitions, table, () => retrieveRange((q) => q), false); | ||
this.field = field; | ||
@@ -830,6 +826,6 @@ this.retrieveRange = retrieveRange; | ||
var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise { | ||
constructor(db, entDefinitions, table, retrieve, throwIfNull) { | ||
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -852,3 +848,3 @@ this.table = table; | ||
const decision = await readPolicy( | ||
entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
); | ||
@@ -866,3 +862,3 @@ if (this.throwIfNull && !decision) { | ||
return this.doc().then( | ||
(doc) => doc === null ? null : entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => doc === null ? null : entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
).then(onfulfilled, onrejected); | ||
@@ -877,7 +873,7 @@ } | ||
edgeImpl(edge, throwIfNull = false) { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[edge]; | ||
const edgeDefinition = getEdgeDefinitions(this.entDefinitions, this.table)[edge]; | ||
if (edgeDefinition.cardinality === "multiple") { | ||
if (edgeDefinition.type === "ref") { | ||
return new PromiseEdgeOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -891,3 +887,3 @@ edgeDefinition.to, | ||
} | ||
const edgeDocs = await this.db.query(edgeDefinition.table).withIndex( | ||
const edgeDocs = await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
@@ -898,3 +894,3 @@ (q) => indexRange(q.eq(edgeDefinition.field, id)) | ||
edgeDocs.map( | ||
(edgeDoc) => this.db.get(edgeDoc[edgeDefinition.ref]) | ||
(edgeDoc) => this.ctx.db.get(edgeDoc[edgeDefinition.ref]) | ||
) | ||
@@ -913,3 +909,3 @@ )).filter((doc, i) => { | ||
return new PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -922,3 +918,3 @@ edgeDefinition.to, | ||
} | ||
return this.db.query(edgeDefinition.to).withIndex( | ||
return this.ctx.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
@@ -931,3 +927,3 @@ (q) => q.eq(edgeDefinition.ref, id) | ||
return new _PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -941,3 +937,3 @@ edgeDefinition.to, | ||
if (edgeDefinition.type === "ref") { | ||
const otherDoc = await this.db.query(edgeDefinition.to).withIndex( | ||
const otherDoc = await this.ctx.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
@@ -958,3 +954,3 @@ (q) => q.eq(edgeDefinition.ref, id) | ||
doc: async () => { | ||
const otherDoc = await this.db.get(otherId); | ||
const otherDoc = await this.ctx.db.get(otherId); | ||
if (otherDoc === null) { | ||
@@ -973,6 +969,23 @@ throw new Error( | ||
}; | ||
function entWrapper(fields, db, entDefinitions, table) { | ||
var PromiseArrayImpl = class extends Promise { | ||
constructor(retrieve) { | ||
super(() => { | ||
}); | ||
this.retrieve = retrieve; | ||
} | ||
async filter(predicate) { | ||
const array = await this.retrieve(); | ||
if (array === null) { | ||
return null; | ||
} | ||
return array.filter(predicate); | ||
} | ||
then(onfulfilled, onrejected) { | ||
return this.retrieve().then(onfulfilled, onrejected); | ||
} | ||
}; | ||
function entWrapper(fields, ctx, entDefinitions, table) { | ||
const doc = { ...fields }; | ||
const queryInterface = new PromiseEntWriterImpl( | ||
db, | ||
ctx, | ||
entDefinitions, | ||
@@ -1000,2 +1013,10 @@ table, | ||
}); | ||
Object.defineProperty(doc, "doc", { | ||
value: () => { | ||
return doc; | ||
}, | ||
enumerable: false, | ||
writable: false, | ||
configurable: false | ||
}); | ||
Object.defineProperty(doc, "patch", { | ||
@@ -1034,3 +1055,4 @@ value: (value) => { | ||
} | ||
function entsTableFactory(db, entDefinitions) { | ||
function entsTableFactory(ctx, entDefinitions, options) { | ||
const enrichedCtx = options !== void 0 ? { ...ctx, ...options } : ctx; | ||
return (table, indexName, indexRange) => { | ||
@@ -1041,10 +1063,11 @@ if (typeof table !== "string") { | ||
if (indexName !== void 0) { | ||
return new PromiseTableImpl(db, entDefinitions, table).withIndex( | ||
indexName, | ||
indexRange | ||
); | ||
return new PromiseTableImpl( | ||
enrichedCtx, | ||
entDefinitions, | ||
table | ||
).withIndex(indexName, indexRange); | ||
} | ||
if (db.insert !== void 0) { | ||
if (ctx.db.insert !== void 0) { | ||
return new PromiseTableWriterImpl( | ||
db, | ||
enrichedCtx, | ||
entDefinitions, | ||
@@ -1054,10 +1077,10 @@ table | ||
} | ||
return new PromiseTableImpl(db, entDefinitions, table); | ||
return new PromiseTableImpl(enrichedCtx, entDefinitions, table); | ||
}; | ||
} | ||
var PromiseTableWriterImpl = class extends PromiseTableImpl { | ||
constructor(db, entDefinitions, table) { | ||
super(db, entDefinitions, table); | ||
this.db = db; | ||
this.base = new WriterImplBase(db, entDefinitions, table); | ||
constructor(ctx, entDefinitions, table) { | ||
super(ctx, entDefinitions, table); | ||
this.ctx = ctx; | ||
this.base = new WriterImplBase(ctx, entDefinitions, table); | ||
} | ||
@@ -1067,3 +1090,3 @@ base; | ||
return new PromiseEntIdImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1075,13 +1098,15 @@ this.table, | ||
const fields = this.base.fieldsOnly(value); | ||
const docId = await this.db.insert(this.table, fields); | ||
const docId = await this.ctx.db.insert(this.table, fields); | ||
const edges = {}; | ||
Object.keys(value).forEach((key) => { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[key]; | ||
const edgeDefinition = getEdgeDefinitions( | ||
this.entDefinitions, | ||
this.table | ||
)[key]; | ||
if (edgeDefinition === void 0 || edgeDefinition.cardinality === "single" && edgeDefinition.type === "field") { | ||
return; | ||
} | ||
if (edgeDefinition.cardinality === "single") { | ||
throw new Error("Cannot set 1:1 edge from optional end."); | ||
} | ||
edges[key] = { add: value[key] }; | ||
edges[key] = { | ||
add: edgeDefinition.cardinality === "single" ? [value[key]] : value[key] | ||
}; | ||
}); | ||
@@ -1099,5 +1124,5 @@ await this.base.writeEdges(docId, edges); | ||
var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl { | ||
constructor(db, entDefinitions, table, retrieve, throwIfNull) { | ||
super(db, entDefinitions, table, retrieve, throwIfNull); | ||
this.db = db; | ||
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) { | ||
super(ctx, entDefinitions, table, retrieve, throwIfNull); | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -1107,3 +1132,3 @@ this.table = table; | ||
this.throwIfNull = throwIfNull; | ||
this.base = new WriterImplBase(db, entDefinitions, table); | ||
this.base = new WriterImplBase(ctx, entDefinitions, table); | ||
} | ||
@@ -1113,3 +1138,3 @@ base; | ||
return new PromiseEntIdImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1123,7 +1148,10 @@ this.table, | ||
const fields = this.base.fieldsOnly(value); | ||
await this.db.patch(id, fields); | ||
await this.ctx.db.patch(id, fields); | ||
const edges = {}; | ||
await Promise.all( | ||
Object.keys(value).map(async (key) => { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[key]; | ||
const edgeDefinition = getEdgeDefinitions( | ||
this.entDefinitions, | ||
this.table | ||
)[key]; | ||
if (edgeDefinition === void 0 || edgeDefinition.cardinality === "single" && edgeDefinition.type === "field") { | ||
@@ -1133,5 +1161,36 @@ return; | ||
if (edgeDefinition.cardinality === "single") { | ||
throw new Error("Cannot set 1:1 edge from optional end."); | ||
throw new Error( | ||
`Cannot set 1:1 edge "${edgeDefinition.name}" on ent in table "${this.table}", update the ent in "${edgeDefinition.to}" table instead.` | ||
); | ||
} else { | ||
edges[key] = value[key]; | ||
if (edgeDefinition.type === "field") { | ||
throw new Error( | ||
`Cannot set 1:many edges "${edgeDefinition.name}" on ent in table "${this.table}", update the ents in "${edgeDefinition.to}" table instead.` | ||
); | ||
} else { | ||
const { add, remove } = value[key]; | ||
const removeEdges = (await Promise.all( | ||
(remove ?? []).map( | ||
async (edgeId) => (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, id).eq( | ||
edgeDefinition.ref, | ||
edgeId | ||
) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id).eq( | ||
edgeDefinition.field, | ||
edgeId | ||
) | ||
).collect() : [] | ||
) | ||
) | ||
)).map((edgeDoc) => edgeDoc._id); | ||
edges[key] = { | ||
add, | ||
removeEdges | ||
}; | ||
} | ||
} | ||
@@ -1147,3 +1206,3 @@ }) | ||
return new PromiseEntIdImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1157,7 +1216,7 @@ this.table, | ||
const fields = this.base.fieldsOnly(value); | ||
await this.db.replace(docId, fields); | ||
await this.ctx.db.replace(docId, fields); | ||
const edges = {}; | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
getEdgeDefinitions(this.entDefinitions, this.table) | ||
).map(async (edgeDefinition) => { | ||
@@ -1168,3 +1227,3 @@ const key = edgeDefinition.name; | ||
if (edgeDefinition.type === "ref") { | ||
const oldDoc = await this.db.get(docId); | ||
const oldDoc = await this.ctx.db.get(docId); | ||
if (oldDoc[key] !== void 0 && oldDoc[key] !== idOrIds) { | ||
@@ -1176,17 +1235,12 @@ throw new Error("Cannot set 1:1 edge from optional end."); | ||
if (edgeDefinition.type === "field") { | ||
const existing = (await this.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, docId) | ||
).collect()).map((doc) => doc._id); | ||
edges[key] = { | ||
add: idOrIds, | ||
remove: existing | ||
}; | ||
if (idOrIds !== void 0) { | ||
throw new Error("Cannot set 1:many edge from many end."); | ||
} | ||
} else { | ||
const requested = new Set(idOrIds ?? []); | ||
const remove = (await this.db.query(edgeDefinition.table).withIndex( | ||
const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, docId) | ||
).collect()).map((doc) => [doc._id, doc[edgeDefinition.ref]]).concat( | ||
edgeDefinition.symmetric ? (await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.symmetric ? (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
@@ -1205,4 +1259,4 @@ (q) => q.eq(edgeDefinition.ref, docId) | ||
edges[key] = { | ||
add: Array.from(requested), | ||
removeEdges: remove | ||
add: idOrIds ?? [], | ||
removeEdges | ||
}; | ||
@@ -1221,10 +1275,10 @@ } | ||
const id = docId; | ||
return this.base.deleteId(id); | ||
return this.base.deleteId(id, "default"); | ||
} | ||
}; | ||
var PromiseEntIdImpl = class extends Promise { | ||
constructor(db, entDefinitions, table, retrieve) { | ||
constructor(ctx, entDefinitions, table, retrieve) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -1236,3 +1290,3 @@ this.table = table; | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1242,3 +1296,3 @@ this.table, | ||
const id = await this.retrieve(); | ||
return { id, doc: async () => this.db.get(id) }; | ||
return { id, doc: async () => this.ctx.db.get(id) }; | ||
}, | ||
@@ -1265,3 +1319,3 @@ true | ||
} | ||
async function filterByReadRule(db, entDefinitions, table, docs, throwIfNull) { | ||
async function filterByReadRule(ctx, entDefinitions, table, docs, throwIfNull) { | ||
if (docs === null) { | ||
@@ -1277,3 +1331,3 @@ return null; | ||
const decision = await readPolicy( | ||
entWrapper(doc, db, entDefinitions, table) | ||
entWrapper(doc, ctx, entDefinitions, table) | ||
); | ||
@@ -1296,2 +1350,8 @@ if (throwIfNull && !decision) { | ||
} | ||
function getEdgeDefinitions(entDefinitions, table) { | ||
return entDefinitions[table].edges; | ||
} | ||
function getDeletionConfig(entDefinitions, table) { | ||
return entDefinitions[table].deletionConfig; | ||
} | ||
// Annotate the CommonJS export names for ESM import in node: | ||
@@ -1302,2 +1362,4 @@ 0 && (module.exports = { | ||
entsTableFactory, | ||
getDeletionConfig, | ||
getEdgeDefinitions, | ||
getReadRule, | ||
@@ -1304,0 +1366,0 @@ getWriteRule |
export { EntDefinition, defineEnt, defineEntSchema, getEntDefinitions } from './schema.js'; | ||
export { GenericEnt, GenericEntWriter, PromiseEdgeEnts, PromiseEdgeEntsOrNull, PromiseEnt, PromiseEntId, PromiseEntOrNull, PromiseEntWriter, PromiseEntWriterOrNull, PromiseEnts, PromiseEntsOrNull, PromiseEntsOrNulls, PromiseEntsWriter, PromiseOrderedQuery, PromiseOrderedQueryBase, PromiseOrderedQueryOrNull, PromiseOrderedQueryWriter, PromiseQuery, PromiseQueryOrNull, PromiseQueryWriter, PromiseTable, PromiseTableBase, PromiseTableWriter, addEntRules, entsTableFactory } from './functions.js'; | ||
export { G as GenericEnt, I as GenericEntWriter, p as PromiseEdgeEnts, o as PromiseEdgeEntsOrNull, r as PromiseEnt, J as PromiseEntId, q as PromiseEntOrNull, H as PromiseEntWriter, F as PromiseEntWriterOrNull, m as PromiseEnts, l as PromiseEntsOrNull, n as PromiseEntsOrNulls, B as PromiseEntsWriter, h as PromiseOrderedQuery, g as PromiseOrderedQueryBase, P as PromiseOrderedQueryOrNull, z as PromiseOrderedQueryWriter, i as PromiseQuery, d as PromiseQueryOrNull, A as PromiseQueryWriter, f as PromiseTable, e as PromiseTableBase, D as PromiseTableWriter, N as addEntRules, v as entsTableFactory } from './index--8O_7LM9.js'; | ||
export { scheduledDeleteFactory } from './deletion.js'; | ||
import 'convex/server'; | ||
import 'convex/values'; | ||
import './writer.js'; |
@@ -27,3 +27,4 @@ "use strict"; | ||
entsTableFactory: () => entsTableFactory, | ||
getEntDefinitions: () => getEntDefinitions | ||
getEntDefinitions: () => getEntDefinitions, | ||
scheduledDeleteFactory: () => scheduledDeleteFactory | ||
}); | ||
@@ -39,3 +40,3 @@ module.exports = __toCommonJS(src_exports); | ||
const table = schema[tableName]; | ||
for (const edge of edgeConfigsFromEntDefinition(table)) { | ||
for (const edge of edgeConfigsBeforeDefineSchema(table)) { | ||
if ( | ||
@@ -57,3 +58,3 @@ // Skip inverse edges, we process their forward edges | ||
const isSelfDirected = edge.to === tableName; | ||
const inverseEdgeCandidates = edgeConfigsFromEntDefinition( | ||
const inverseEdgeCandidates = edgeConfigsBeforeDefineSchema( | ||
otherTable | ||
@@ -93,2 +94,9 @@ ).filter(canBeInverseEdge(tableName, edge, isSelfDirected)); | ||
} | ||
if (edge.cardinality === "single" && edge.type === "ref" || edge.cardinality === "multiple" && edge.type === "field") { | ||
if (edge.deletion !== void 0 && deletionConfigFromEntDefinition(otherTable) === void 0) { | ||
throw new Error( | ||
`Cannot specify soft deletion behavior for edge "${edge.name}" in table "${tableName}" because the target table "${otherTableName}" does not have a "soft" or "scheduled" deletion behavior configured.` | ||
); | ||
} | ||
} | ||
if (edge.cardinality === "multiple") { | ||
@@ -177,3 +185,3 @@ if (!isSelfDirected && inverseEdge === void 0) { | ||
} | ||
function edgeConfigsFromEntDefinition(table) { | ||
function edgeConfigsBeforeDefineSchema(table) { | ||
return Object.values( | ||
@@ -183,2 +191,5 @@ table.edgeConfigs | ||
} | ||
function deletionConfigFromEntDefinition(table) { | ||
return table.deletionConfig; | ||
} | ||
function defineEnt(documentSchema) { | ||
@@ -201,2 +212,3 @@ return new EntDefinitionImpl(documentSchema); | ||
defaults = {}; | ||
deletionConfig; | ||
constructor(documentSchema) { | ||
@@ -307,3 +319,4 @@ this.documentSchema = documentSchema; | ||
const inverseName = options?.inverse; | ||
this.edgeConfigs[name] = ref !== void 0 ? { name, to, cardinality, type: "field", ref } : { name, to, cardinality, type: "ref", table, field, inverseField }; | ||
const deletion = options?.deletion; | ||
this.edgeConfigs[name] = ref !== void 0 ? { name, to, cardinality, type: "field", ref, deletion } : { name, to, cardinality, type: "ref", table, field, inverseField }; | ||
if (inverseName !== void 0) { | ||
@@ -321,2 +334,18 @@ this.edgeConfigs[inverseName] = { | ||
} | ||
deletion(type, options) { | ||
if (this.documentSchema.deletionTime !== void 0) { | ||
throw new Error( | ||
`Cannot enable "${type}" deletion because "deletionTime" field was already defined.` | ||
); | ||
} | ||
if (this.deletionConfig !== void 0) { | ||
throw new Error(`Deletion behavior can only be specified once.`); | ||
} | ||
this.documentSchema = { | ||
...this.documentSchema, | ||
deletionTime: import_values.v.optional(import_values.v.number()) | ||
}; | ||
this.deletionConfig = { type, ...options }; | ||
return this; | ||
} | ||
}; | ||
@@ -331,3 +360,4 @@ function getEntDefinitions(schema) { | ||
edges: tables[tableName].edgeConfigs, | ||
fields: tables[tableName].fieldConfigs | ||
fields: tables[tableName].fieldConfigs, | ||
deletionConfig: tables[tableName].deletionConfig | ||
} | ||
@@ -340,95 +370,104 @@ }), | ||
// src/writer.ts | ||
var import_server2 = require("convex/server"); | ||
var WriterImplBase = class _WriterImplBase { | ||
constructor(db, entDefinitions, table) { | ||
this.db = db; | ||
constructor(ctx, entDefinitions, table) { | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
this.table = table; | ||
} | ||
async deleteId(id) { | ||
async deleteId(id, behavior) { | ||
await this.checkReadAndWriteRule("delete", id, void 0); | ||
let memoized = void 0; | ||
const oldDoc = async () => { | ||
if (memoized !== void 0) { | ||
return memoized; | ||
} | ||
return memoized = await this.db.get(id); | ||
}; | ||
const deletionConfig = getDeletionConfig(this.entDefinitions, this.table); | ||
const isDeletingSoftly = behavior !== "hard" && deletionConfig !== void 0 && (deletionConfig.type === "soft" || deletionConfig.type === "scheduled"); | ||
if (behavior === "soft" && !isDeletingSoftly) { | ||
throw new Error( | ||
`Cannot soft delete document with ID "${id}" in table "${this.table}" because it does not have an "allowSoft", "soft" or "scheduled" deletion behavior configured.` | ||
); | ||
} | ||
const edges = {}; | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
).map(async (edgeDefinition) => { | ||
const key = edgeDefinition.name; | ||
if (edgeDefinition.cardinality === "single") { | ||
if (edgeDefinition.type === "ref") { | ||
edges[key] = { | ||
remove: (await oldDoc())[key] | ||
}; | ||
} | ||
} else { | ||
if (edgeDefinition.type === "field") { | ||
const existing = (await this.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id) | ||
).collect()).map((doc) => doc._id); | ||
edges[key] = { remove: existing }; | ||
} else { | ||
const existing = (await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, id) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.db.query(edgeDefinition.table).withIndex( | ||
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map( | ||
async (edgeDefinition) => { | ||
const key = edgeDefinition.name; | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") { | ||
if (!isDeletingSoftly || edgeDefinition.deletion === "soft") { | ||
const remove = (await this.ctx.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id) | ||
).collect() : [] | ||
).map((doc) => doc._id); | ||
edges[key] = { removeEdges: existing }; | ||
).collect()).map((doc) => doc._id); | ||
edges[key] = { remove }; | ||
} | ||
} else if (edgeDefinition.cardinality === "multiple") { | ||
if (!isDeletingSoftly) { | ||
const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, id) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id) | ||
).collect() : [] | ||
).map((doc) => doc._id); | ||
edges[key] = { removeEdges }; | ||
} | ||
} | ||
} | ||
}) | ||
) | ||
); | ||
await this.db.delete(id); | ||
await this.writeEdges(id, edges); | ||
const deletionTime = +/* @__PURE__ */ new Date(); | ||
if (isDeletingSoftly) { | ||
await this.ctx.db.patch(id, { deletionTime }); | ||
} else { | ||
try { | ||
await this.ctx.db.delete(id); | ||
} catch (e) { | ||
} | ||
} | ||
await this.writeEdges(id, edges, isDeletingSoftly); | ||
if (deletionConfig !== void 0 && deletionConfig.type === "scheduled") { | ||
const fnRef = this.ctx.scheduledDelete ?? (0, import_server2.makeFunctionReference)( | ||
"functions:scheduledDelete" | ||
); | ||
await this.ctx.scheduler.runAfter(deletionConfig.delayMs ?? 0, fnRef, { | ||
origin: { | ||
id, | ||
table: this.table, | ||
deletionTime | ||
}, | ||
inProgress: false, | ||
stack: [] | ||
}); | ||
} | ||
return id; | ||
} | ||
async deletedIdIn(id, table) { | ||
await new _WriterImplBase(this.db, this.entDefinitions, table).deleteId(id); | ||
async deletedIdIn(id, table, cascadingSoft) { | ||
await new _WriterImplBase(this.ctx, this.entDefinitions, table).deleteId( | ||
id, | ||
cascadingSoft ? "soft" : "hard" | ||
); | ||
} | ||
async writeEdges(docId, changes) { | ||
async writeEdges(docId, changes, deleteSoftly) { | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
).map(async (edgeDefinition) => { | ||
const idOrIds = changes[edgeDefinition.name]; | ||
if (idOrIds === void 0) { | ||
return; | ||
} | ||
if (edgeDefinition.cardinality === "single") { | ||
if (edgeDefinition.type === "ref") { | ||
if (idOrIds.remove !== void 0) { | ||
await this.deletedIdIn( | ||
idOrIds.remove, | ||
edgeDefinition.to | ||
); | ||
} | ||
if (idOrIds.add !== void 0) { | ||
await this.db.patch( | ||
idOrIds.add, | ||
{ [edgeDefinition.ref]: docId } | ||
); | ||
} | ||
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map( | ||
async (edgeDefinition) => { | ||
const idOrIds = changes[edgeDefinition.name]; | ||
if (idOrIds === void 0) { | ||
return; | ||
} | ||
} else { | ||
if (edgeDefinition.type === "field") { | ||
if (idOrIds.remove !== void 0) { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") { | ||
if (idOrIds.remove !== void 0 && idOrIds.remove.length > 0) { | ||
await Promise.all( | ||
idOrIds.remove.map( | ||
(id) => this.deletedIdIn(id, edgeDefinition.to) | ||
(id) => this.deletedIdIn( | ||
id, | ||
edgeDefinition.to, | ||
(deleteSoftly ?? false) && edgeDefinition.deletion === "soft" | ||
) | ||
) | ||
); | ||
} | ||
if (idOrIds.add !== void 0) { | ||
if (idOrIds.add !== void 0 && idOrIds.add.length > 0) { | ||
await Promise.all( | ||
idOrIds.add.map( | ||
async (id) => this.db.patch(id, { | ||
async (id) => this.ctx.db.patch(id, { | ||
[edgeDefinition.ref]: docId | ||
@@ -439,30 +478,8 @@ }) | ||
} | ||
} else { | ||
let removeEdges = []; | ||
if (idOrIds.remove !== void 0) { | ||
removeEdges = (await Promise.all( | ||
idOrIds.remove.map( | ||
async (id) => (await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, docId).eq( | ||
edgeDefinition.ref, | ||
id | ||
) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, docId).eq(edgeDefinition.field, id) | ||
).collect() : [] | ||
) | ||
) | ||
)).map((doc) => doc._id); | ||
} | ||
if (idOrIds.removeEdges !== void 0) { | ||
removeEdges = idOrIds.removeEdges; | ||
} | ||
if (removeEdges.length > 0) { | ||
} else if (edgeDefinition.cardinality === "multiple") { | ||
if ((idOrIds.removeEdges ?? []).length > 0) { | ||
await Promise.all( | ||
removeEdges.map(async (id) => { | ||
idOrIds.removeEdges.map(async (id) => { | ||
try { | ||
await this.db.delete(id); | ||
await this.ctx.db.delete(id); | ||
} catch (e) { | ||
@@ -476,3 +493,3 @@ } | ||
idOrIds.add.map(async (id) => { | ||
await this.db.insert(edgeDefinition.table, { | ||
await this.ctx.db.insert(edgeDefinition.table, { | ||
[edgeDefinition.field]: docId, | ||
@@ -482,3 +499,3 @@ [edgeDefinition.ref]: id | ||
if (edgeDefinition.symmetric) { | ||
await this.db.insert(edgeDefinition.table, { | ||
await this.ctx.db.insert(edgeDefinition.table, { | ||
[edgeDefinition.field]: id, | ||
@@ -493,3 +510,3 @@ [edgeDefinition.ref]: docId | ||
} | ||
}) | ||
) | ||
); | ||
@@ -505,3 +522,3 @@ } | ||
const fieldValue = value[key]; | ||
const existing = await this.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
if (existing !== null && (id === void 0 || existing._id !== id)) { | ||
@@ -516,18 +533,18 @@ throw new Error( | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
).map(async (edgeDefinition) => { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "field" && edgeDefinition.unique) { | ||
const key = edgeDefinition.field; | ||
if (value[key] === void 0) { | ||
return; | ||
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map( | ||
async (edgeDefinition) => { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "field" && edgeDefinition.unique) { | ||
const key = edgeDefinition.field; | ||
if (value[key] === void 0) { | ||
return; | ||
} | ||
const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
if (existing !== null && (id === void 0 || existing._id !== id)) { | ||
throw new Error( | ||
`In table "${this.table}" cannot create a duplicate 1:1 edge "${edgeDefinition.name}" to ID "${value[key]}", existing document with ID "${existing._id}" already has it.` | ||
); | ||
} | ||
} | ||
const existing = await this.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
if (existing !== null && (id === void 0 || existing._id !== id)) { | ||
throw new Error( | ||
`In table "${this.table}" cannot create a duplicate 1:1 edge "${edgeDefinition.name}" to ID "${value[key]}", existing document with ID "${existing._id}" already has it.` | ||
); | ||
} | ||
} | ||
}) | ||
) | ||
); | ||
@@ -538,3 +555,6 @@ } | ||
Object.keys(value).forEach((key) => { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[key]; | ||
const edgeDefinition = getEdgeDefinitions( | ||
this.entDefinitions, | ||
this.table | ||
)[key]; | ||
if (edgeDefinition === void 0) { | ||
@@ -550,3 +570,3 @@ fields[key] = value[key]; | ||
if (readPolicy !== void 0) { | ||
const doc = await this.db.get(id); | ||
const doc = await this.ctx.db.get(id); | ||
if (doc === null) { | ||
@@ -570,4 +590,4 @@ throw new Error( | ||
const ent = id === void 0 ? void 0 : entWrapper( | ||
await this.db.get(id), | ||
this.db, | ||
await this.ctx.db.get(id), | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -604,6 +624,6 @@ this.table | ||
var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise { | ||
constructor(db, entDefinitions, table, retrieve) { | ||
constructor(ctx, entDefinitions, table, retrieve) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -615,3 +635,3 @@ this.table = table; | ||
return new _PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -628,12 +648,14 @@ this.table, | ||
} | ||
async map(callbackFn) { | ||
const array = await this; | ||
if (array === null) { | ||
return []; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
map(callbackFn) { | ||
return new PromiseArrayImpl(async () => { | ||
const array = await this; | ||
if (array === null) { | ||
return null; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
}); | ||
} | ||
order(order, indexName) { | ||
return new _PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -655,3 +677,3 @@ this.table, | ||
return new PromisePaginationResultOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -665,3 +687,3 @@ this.table, | ||
return new PromiseEntsOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -677,3 +699,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -694,3 +716,3 @@ this.table, | ||
return new PromiseEntWriterImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -714,3 +736,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -737,3 +759,3 @@ this.table, | ||
return new PromiseEntWriterImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -765,3 +787,3 @@ this.table, | ||
return filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -776,3 +798,3 @@ this.table, | ||
(documents) => documents === null ? null : documents.map( | ||
(doc) => entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
) | ||
@@ -806,3 +828,3 @@ ).then(onfulfilled, onrejected); | ||
...(await filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -820,6 +842,6 @@ this.table, | ||
var PromisePaginationResultOrNullImpl = class extends Promise { | ||
constructor(db, entDefinitions, table, retrieve, paginationOpts) { | ||
constructor(ctx, entDefinitions, table, retrieve, paginationOpts) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -849,3 +871,3 @@ this.table = table; | ||
page: await filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -863,3 +885,3 @@ this.table, | ||
page: result.page.map( | ||
(doc) => entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
) | ||
@@ -871,4 +893,4 @@ } | ||
var PromiseTableImpl = class extends PromiseQueryOrNullImpl { | ||
constructor(db, entDefinitions, table) { | ||
super(db, entDefinitions, table, async () => db.query(table)); | ||
constructor(ctx, entDefinitions, table) { | ||
super(ctx, entDefinitions, table, async () => ctx.db.query(table)); | ||
} | ||
@@ -889,3 +911,3 @@ get(...args) { | ||
return new PromiseEntWriterImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -895,3 +917,3 @@ this.table, | ||
const id = args[0]; | ||
if (this.db.normalizeId(this.table, id) === null) { | ||
if (this.ctx.db.normalizeId(this.table, id) === null) { | ||
throw new Error(`Invalid id \`${id}\` for table "${this.table}"`); | ||
@@ -902,3 +924,3 @@ } | ||
doc: async () => { | ||
const doc = await this.db.get(id); | ||
const doc = await this.ctx.db.get(id); | ||
if (throwIfNull && doc === null) { | ||
@@ -914,3 +936,3 @@ throw new Error( | ||
const [indexName, value] = args; | ||
const doc = await this.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique(); | ||
const doc = await this.ctx.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique(); | ||
if (throwIfNull && doc === null) { | ||
@@ -928,3 +950,3 @@ throw new Error( | ||
return new PromiseEntsOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -935,3 +957,3 @@ this.table, | ||
ids.forEach((id) => { | ||
if (this.db.normalizeId(this.table, id) === null) { | ||
if (this.ctx.db.normalizeId(this.table, id) === null) { | ||
throw new Error( | ||
@@ -944,3 +966,3 @@ `Invalid id \`${id}\` for table "${this.table}"` | ||
ids.map(async (id) => { | ||
const doc = await this.db.get(id); | ||
const doc = await this.ctx.db.get(id); | ||
if (doc === null) { | ||
@@ -958,3 +980,3 @@ throw new Error( | ||
values.map(async (value) => { | ||
const doc = await this.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique(); | ||
const doc = await this.ctx.db.query(this.table).withIndex(indexName, (q) => q.eq(indexName, value)).unique(); | ||
if (throwIfNull && doc === null) { | ||
@@ -973,3 +995,3 @@ throw new Error( | ||
normalizeId(id) { | ||
return this.db.normalizeId(this.table, id); | ||
return this.ctx.db.normalizeId(this.table, id); | ||
} | ||
@@ -986,3 +1008,3 @@ // normalizeId or throw | ||
return new PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -998,3 +1020,3 @@ this.table, | ||
return new PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1010,6 +1032,6 @@ this.table, | ||
var PromiseEntsOrNullImpl = class extends Promise { | ||
constructor(db, entDefinitions, table, retrieve, throwIfNull) { | ||
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -1020,12 +1042,14 @@ this.table = table; | ||
} | ||
async map(callbackFn) { | ||
const array = await this; | ||
if (array === null) { | ||
return []; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
map(callbackFn) { | ||
return new PromiseArrayImpl(async () => { | ||
const array = await this; | ||
if (array === null) { | ||
return null; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
}); | ||
} | ||
first() { | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1045,3 +1069,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1065,3 +1089,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1084,3 +1108,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1107,3 +1131,3 @@ this.table, | ||
return filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1118,3 +1142,3 @@ this.table, | ||
(docs) => docs === null ? null : docs.map( | ||
(doc) => entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
) | ||
@@ -1125,4 +1149,4 @@ ).then(onfulfilled, onrejected); | ||
var PromiseEdgeOrNullImpl = class extends PromiseEntsOrNullImpl { | ||
constructor(db, entDefinitions, table, field, retrieveRange) { | ||
super(db, entDefinitions, table, () => retrieveRange((q) => q), false); | ||
constructor(ctx, entDefinitions, table, field, retrieveRange) { | ||
super(ctx, entDefinitions, table, () => retrieveRange((q) => q), false); | ||
this.field = field; | ||
@@ -1137,6 +1161,6 @@ this.retrieveRange = retrieveRange; | ||
var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise { | ||
constructor(db, entDefinitions, table, retrieve, throwIfNull) { | ||
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -1159,3 +1183,3 @@ this.table = table; | ||
const decision = await readPolicy( | ||
entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
); | ||
@@ -1173,3 +1197,3 @@ if (this.throwIfNull && !decision) { | ||
return this.doc().then( | ||
(doc) => doc === null ? null : entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => doc === null ? null : entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
).then(onfulfilled, onrejected); | ||
@@ -1184,7 +1208,7 @@ } | ||
edgeImpl(edge, throwIfNull = false) { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[edge]; | ||
const edgeDefinition = getEdgeDefinitions(this.entDefinitions, this.table)[edge]; | ||
if (edgeDefinition.cardinality === "multiple") { | ||
if (edgeDefinition.type === "ref") { | ||
return new PromiseEdgeOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1198,3 +1222,3 @@ edgeDefinition.to, | ||
} | ||
const edgeDocs = await this.db.query(edgeDefinition.table).withIndex( | ||
const edgeDocs = await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
@@ -1205,3 +1229,3 @@ (q) => indexRange(q.eq(edgeDefinition.field, id)) | ||
edgeDocs.map( | ||
(edgeDoc) => this.db.get(edgeDoc[edgeDefinition.ref]) | ||
(edgeDoc) => this.ctx.db.get(edgeDoc[edgeDefinition.ref]) | ||
) | ||
@@ -1220,3 +1244,3 @@ )).filter((doc, i) => { | ||
return new PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1229,3 +1253,3 @@ edgeDefinition.to, | ||
} | ||
return this.db.query(edgeDefinition.to).withIndex( | ||
return this.ctx.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
@@ -1238,3 +1262,3 @@ (q) => q.eq(edgeDefinition.ref, id) | ||
return new _PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1248,3 +1272,3 @@ edgeDefinition.to, | ||
if (edgeDefinition.type === "ref") { | ||
const otherDoc = await this.db.query(edgeDefinition.to).withIndex( | ||
const otherDoc = await this.ctx.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
@@ -1265,3 +1289,3 @@ (q) => q.eq(edgeDefinition.ref, id) | ||
doc: async () => { | ||
const otherDoc = await this.db.get(otherId); | ||
const otherDoc = await this.ctx.db.get(otherId); | ||
if (otherDoc === null) { | ||
@@ -1280,6 +1304,23 @@ throw new Error( | ||
}; | ||
function entWrapper(fields, db, entDefinitions, table) { | ||
var PromiseArrayImpl = class extends Promise { | ||
constructor(retrieve) { | ||
super(() => { | ||
}); | ||
this.retrieve = retrieve; | ||
} | ||
async filter(predicate) { | ||
const array = await this.retrieve(); | ||
if (array === null) { | ||
return null; | ||
} | ||
return array.filter(predicate); | ||
} | ||
then(onfulfilled, onrejected) { | ||
return this.retrieve().then(onfulfilled, onrejected); | ||
} | ||
}; | ||
function entWrapper(fields, ctx, entDefinitions, table) { | ||
const doc = { ...fields }; | ||
const queryInterface = new PromiseEntWriterImpl( | ||
db, | ||
ctx, | ||
entDefinitions, | ||
@@ -1307,2 +1348,10 @@ table, | ||
}); | ||
Object.defineProperty(doc, "doc", { | ||
value: () => { | ||
return doc; | ||
}, | ||
enumerable: false, | ||
writable: false, | ||
configurable: false | ||
}); | ||
Object.defineProperty(doc, "patch", { | ||
@@ -1341,3 +1390,4 @@ value: (value) => { | ||
} | ||
function entsTableFactory(db, entDefinitions) { | ||
function entsTableFactory(ctx, entDefinitions, options) { | ||
const enrichedCtx = options !== void 0 ? { ...ctx, ...options } : ctx; | ||
return (table, indexName, indexRange) => { | ||
@@ -1348,10 +1398,11 @@ if (typeof table !== "string") { | ||
if (indexName !== void 0) { | ||
return new PromiseTableImpl(db, entDefinitions, table).withIndex( | ||
indexName, | ||
indexRange | ||
); | ||
return new PromiseTableImpl( | ||
enrichedCtx, | ||
entDefinitions, | ||
table | ||
).withIndex(indexName, indexRange); | ||
} | ||
if (db.insert !== void 0) { | ||
if (ctx.db.insert !== void 0) { | ||
return new PromiseTableWriterImpl( | ||
db, | ||
enrichedCtx, | ||
entDefinitions, | ||
@@ -1361,10 +1412,10 @@ table | ||
} | ||
return new PromiseTableImpl(db, entDefinitions, table); | ||
return new PromiseTableImpl(enrichedCtx, entDefinitions, table); | ||
}; | ||
} | ||
var PromiseTableWriterImpl = class extends PromiseTableImpl { | ||
constructor(db, entDefinitions, table) { | ||
super(db, entDefinitions, table); | ||
this.db = db; | ||
this.base = new WriterImplBase(db, entDefinitions, table); | ||
constructor(ctx, entDefinitions, table) { | ||
super(ctx, entDefinitions, table); | ||
this.ctx = ctx; | ||
this.base = new WriterImplBase(ctx, entDefinitions, table); | ||
} | ||
@@ -1374,3 +1425,3 @@ base; | ||
return new PromiseEntIdImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1382,13 +1433,15 @@ this.table, | ||
const fields = this.base.fieldsOnly(value); | ||
const docId = await this.db.insert(this.table, fields); | ||
const docId = await this.ctx.db.insert(this.table, fields); | ||
const edges = {}; | ||
Object.keys(value).forEach((key) => { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[key]; | ||
const edgeDefinition = getEdgeDefinitions( | ||
this.entDefinitions, | ||
this.table | ||
)[key]; | ||
if (edgeDefinition === void 0 || edgeDefinition.cardinality === "single" && edgeDefinition.type === "field") { | ||
return; | ||
} | ||
if (edgeDefinition.cardinality === "single") { | ||
throw new Error("Cannot set 1:1 edge from optional end."); | ||
} | ||
edges[key] = { add: value[key] }; | ||
edges[key] = { | ||
add: edgeDefinition.cardinality === "single" ? [value[key]] : value[key] | ||
}; | ||
}); | ||
@@ -1406,5 +1459,5 @@ await this.base.writeEdges(docId, edges); | ||
var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl { | ||
constructor(db, entDefinitions, table, retrieve, throwIfNull) { | ||
super(db, entDefinitions, table, retrieve, throwIfNull); | ||
this.db = db; | ||
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) { | ||
super(ctx, entDefinitions, table, retrieve, throwIfNull); | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -1414,3 +1467,3 @@ this.table = table; | ||
this.throwIfNull = throwIfNull; | ||
this.base = new WriterImplBase(db, entDefinitions, table); | ||
this.base = new WriterImplBase(ctx, entDefinitions, table); | ||
} | ||
@@ -1420,3 +1473,3 @@ base; | ||
return new PromiseEntIdImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1430,7 +1483,10 @@ this.table, | ||
const fields = this.base.fieldsOnly(value); | ||
await this.db.patch(id, fields); | ||
await this.ctx.db.patch(id, fields); | ||
const edges = {}; | ||
await Promise.all( | ||
Object.keys(value).map(async (key) => { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[key]; | ||
const edgeDefinition = getEdgeDefinitions( | ||
this.entDefinitions, | ||
this.table | ||
)[key]; | ||
if (edgeDefinition === void 0 || edgeDefinition.cardinality === "single" && edgeDefinition.type === "field") { | ||
@@ -1440,5 +1496,36 @@ return; | ||
if (edgeDefinition.cardinality === "single") { | ||
throw new Error("Cannot set 1:1 edge from optional end."); | ||
throw new Error( | ||
`Cannot set 1:1 edge "${edgeDefinition.name}" on ent in table "${this.table}", update the ent in "${edgeDefinition.to}" table instead.` | ||
); | ||
} else { | ||
edges[key] = value[key]; | ||
if (edgeDefinition.type === "field") { | ||
throw new Error( | ||
`Cannot set 1:many edges "${edgeDefinition.name}" on ent in table "${this.table}", update the ents in "${edgeDefinition.to}" table instead.` | ||
); | ||
} else { | ||
const { add, remove } = value[key]; | ||
const removeEdges = (await Promise.all( | ||
(remove ?? []).map( | ||
async (edgeId) => (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, id).eq( | ||
edgeDefinition.ref, | ||
edgeId | ||
) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id).eq( | ||
edgeDefinition.field, | ||
edgeId | ||
) | ||
).collect() : [] | ||
) | ||
) | ||
)).map((edgeDoc) => edgeDoc._id); | ||
edges[key] = { | ||
add, | ||
removeEdges | ||
}; | ||
} | ||
} | ||
@@ -1454,3 +1541,3 @@ }) | ||
return new PromiseEntIdImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1464,7 +1551,7 @@ this.table, | ||
const fields = this.base.fieldsOnly(value); | ||
await this.db.replace(docId, fields); | ||
await this.ctx.db.replace(docId, fields); | ||
const edges = {}; | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
getEdgeDefinitions(this.entDefinitions, this.table) | ||
).map(async (edgeDefinition) => { | ||
@@ -1475,3 +1562,3 @@ const key = edgeDefinition.name; | ||
if (edgeDefinition.type === "ref") { | ||
const oldDoc = await this.db.get(docId); | ||
const oldDoc = await this.ctx.db.get(docId); | ||
if (oldDoc[key] !== void 0 && oldDoc[key] !== idOrIds) { | ||
@@ -1483,17 +1570,12 @@ throw new Error("Cannot set 1:1 edge from optional end."); | ||
if (edgeDefinition.type === "field") { | ||
const existing = (await this.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, docId) | ||
).collect()).map((doc) => doc._id); | ||
edges[key] = { | ||
add: idOrIds, | ||
remove: existing | ||
}; | ||
if (idOrIds !== void 0) { | ||
throw new Error("Cannot set 1:many edge from many end."); | ||
} | ||
} else { | ||
const requested = new Set(idOrIds ?? []); | ||
const remove = (await this.db.query(edgeDefinition.table).withIndex( | ||
const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, docId) | ||
).collect()).map((doc) => [doc._id, doc[edgeDefinition.ref]]).concat( | ||
edgeDefinition.symmetric ? (await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.symmetric ? (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
@@ -1512,4 +1594,4 @@ (q) => q.eq(edgeDefinition.ref, docId) | ||
edges[key] = { | ||
add: Array.from(requested), | ||
removeEdges: remove | ||
add: idOrIds ?? [], | ||
removeEdges | ||
}; | ||
@@ -1528,10 +1610,10 @@ } | ||
const id = docId; | ||
return this.base.deleteId(id); | ||
return this.base.deleteId(id, "default"); | ||
} | ||
}; | ||
var PromiseEntIdImpl = class extends Promise { | ||
constructor(db, entDefinitions, table, retrieve) { | ||
constructor(ctx, entDefinitions, table, retrieve) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -1543,3 +1625,3 @@ this.table = table; | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1549,3 +1631,3 @@ this.table, | ||
const id = await this.retrieve(); | ||
return { id, doc: async () => this.db.get(id) }; | ||
return { id, doc: async () => this.ctx.db.get(id) }; | ||
}, | ||
@@ -1572,3 +1654,3 @@ true | ||
} | ||
async function filterByReadRule(db, entDefinitions, table, docs, throwIfNull) { | ||
async function filterByReadRule(ctx, entDefinitions, table, docs, throwIfNull) { | ||
if (docs === null) { | ||
@@ -1584,3 +1666,3 @@ return null; | ||
const decision = await readPolicy( | ||
entWrapper(doc, db, entDefinitions, table) | ||
entWrapper(doc, ctx, entDefinitions, table) | ||
); | ||
@@ -1603,2 +1685,190 @@ if (throwIfNull && !decision) { | ||
} | ||
function getEdgeDefinitions(entDefinitions, table) { | ||
return entDefinitions[table].edges; | ||
} | ||
function getDeletionConfig(entDefinitions, table) { | ||
return entDefinitions[table].deletionConfig; | ||
} | ||
// src/deletion.ts | ||
var import_server3 = require("convex/server"); | ||
var import_values2 = require("convex/values"); | ||
var vApproach = import_values2.v.union( | ||
import_values2.v.literal("schedule"), | ||
import_values2.v.literal("deleteOne"), | ||
import_values2.v.literal("paginate") | ||
); | ||
function scheduledDeleteFactory(entDefinitions, options) { | ||
const selfRef = options?.scheduledDelete ?? (0, import_server3.makeFunctionReference)( | ||
"functions:scheduledDelete" | ||
); | ||
return (0, import_server3.internalMutationGeneric)({ | ||
args: { | ||
origin: import_values2.v.object({ | ||
id: import_values2.v.string(), | ||
table: import_values2.v.string(), | ||
deletionTime: import_values2.v.number() | ||
}), | ||
stack: import_values2.v.array( | ||
import_values2.v.union( | ||
import_values2.v.object({ | ||
id: import_values2.v.string(), | ||
table: import_values2.v.string(), | ||
edges: import_values2.v.array( | ||
import_values2.v.object({ | ||
approach: vApproach, | ||
table: import_values2.v.string(), | ||
indexName: import_values2.v.string() | ||
}) | ||
) | ||
}), | ||
import_values2.v.object({ | ||
approach: vApproach, | ||
cursor: import_values2.v.union(import_values2.v.string(), import_values2.v.null()), | ||
table: import_values2.v.string(), | ||
indexName: import_values2.v.string(), | ||
fieldValue: import_values2.v.any() | ||
}) | ||
) | ||
), | ||
inProgress: import_values2.v.boolean() | ||
}, | ||
handler: async (ctx, { origin, stack, inProgress }) => { | ||
const originId = ctx.db.normalizeId(origin.table, origin.id); | ||
if (originId === null) { | ||
throw new Error(`Invalid ID "${origin.id}" for table ${origin.table}`); | ||
} | ||
const doc = await ctx.db.get(originId); | ||
if (doc.deletionTime !== origin.deletionTime) { | ||
if (inProgress) { | ||
console.error( | ||
`[Ents] Already in-progress scheduled deletion for "${origin.id}" was cancelled!` | ||
); | ||
} else { | ||
console.log( | ||
`[Ents] Scheduled deletion for "${origin.id}" was cancelled` | ||
); | ||
} | ||
return; | ||
} | ||
await progressScheduledDeletion( | ||
ctx, | ||
entDefinitions, | ||
selfRef, | ||
origin, | ||
inProgress ? stack : [ | ||
{ | ||
id: originId, | ||
table: origin.table, | ||
edges: getEdgeArgs(entDefinitions, origin.table) | ||
} | ||
] | ||
); | ||
} | ||
}); | ||
} | ||
function getEdgeArgs(entDefinitions, table) { | ||
const edges = getEdgeDefinitions(entDefinitions, table); | ||
return Object.values(edges).flatMap((edgeDefinition) => { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") { | ||
const table2 = edgeDefinition.to; | ||
const targetDeletionConfig = getDeletionConfig(entDefinitions, table2); | ||
const targetEdges = getEdgeDefinitions(entDefinitions, table2); | ||
const hasCascadingEdges = Object.values(targetEdges).some( | ||
(edgeDefinition2) => edgeDefinition2.cardinality === "single" && edgeDefinition2.type === "ref" || edgeDefinition2.cardinality === "multiple" | ||
); | ||
const approach = targetDeletionConfig !== void 0 && hasCascadingEdges ? "schedule" : hasCascadingEdges ? "deleteOne" : "paginate"; | ||
const indexName = edgeDefinition.ref; | ||
return [{ table: table2, indexName, approach }]; | ||
} else if (edgeDefinition.cardinality === "multiple") { | ||
const table2 = edgeDefinition.table; | ||
return [ | ||
{ | ||
table: table2, | ||
indexName: edgeDefinition.field, | ||
approach: "paginate" | ||
}, | ||
...edgeDefinition.symmetric ? [ | ||
{ | ||
table: table2, | ||
indexName: edgeDefinition.ref, | ||
approach: "paginate" | ||
} | ||
] : [] | ||
]; | ||
} else { | ||
return []; | ||
} | ||
}); | ||
} | ||
async function progressScheduledDeletion(ctx, entDefinitions, selfRef, origin, stack) { | ||
const last = stack[stack.length - 1]; | ||
if ("id" in last) { | ||
const edgeArgs = last.edges[0]; | ||
if (edgeArgs === void 0) { | ||
await ctx.db.delete(last.id); | ||
if (stack.length > 1) { | ||
await ctx.scheduler.runAfter(0, selfRef, { | ||
origin, | ||
stack: stack.slice(0, -1), | ||
inProgress: true | ||
}); | ||
} | ||
} else { | ||
const updated = { ...last, edges: last.edges.slice(1) }; | ||
await paginate( | ||
ctx, | ||
entDefinitions, | ||
selfRef, | ||
origin, | ||
stack.slice(0, -1).concat(updated), | ||
{ cursor: null, fieldValue: last.id, ...edgeArgs } | ||
); | ||
} | ||
} else { | ||
await paginate(ctx, entDefinitions, selfRef, origin, stack, last); | ||
} | ||
} | ||
async function paginate(ctx, entDefinitions, selfRef, origin, stack, { table, approach, indexName, fieldValue, cursor }) { | ||
const { page, continueCursor, isDone } = await ctx.db.query(table).withIndex(indexName, (q) => q.eq(indexName, fieldValue)).paginate({ | ||
cursor, | ||
...approach === "paginate" ? { numItems: 8192 / 4, maximumBytesRead: 2 ** 18 } : { numItems: 1 } | ||
}); | ||
const updated = { | ||
approach, | ||
table, | ||
cursor: continueCursor, | ||
indexName, | ||
fieldValue | ||
}; | ||
const relevantStack = cursor === null ? stack : stack.slice(0, -1); | ||
if (approach === "schedule") { | ||
await ctx.scheduler.runAfter(0, selfRef, { | ||
origin, | ||
stack: isDone ? relevantStack : relevantStack.concat([ | ||
updated, | ||
{ | ||
id: page[0]._id, | ||
table, | ||
edges: getEdgeArgs(entDefinitions, table) | ||
} | ||
]), | ||
inProgress: true | ||
}); | ||
} else { | ||
if (approach === "deleteOne") { | ||
await new WriterImplBase(ctx, entDefinitions, origin.table).deleteId( | ||
page[0].id, | ||
"hard" | ||
); | ||
} else { | ||
await Promise.all(page.map((doc) => ctx.db.delete(doc._id))); | ||
} | ||
await ctx.scheduler.runAfter(0, selfRef, { | ||
origin, | ||
stack: isDone ? relevantStack : relevantStack.concat([updated]), | ||
inProgress: true | ||
}); | ||
} | ||
} | ||
// Annotate the CommonJS export names for ESM import in node: | ||
@@ -1610,4 +1880,5 @@ 0 && (module.exports = { | ||
entsTableFactory, | ||
getEntDefinitions | ||
getEntDefinitions, | ||
scheduledDeleteFactory | ||
}); | ||
//# sourceMappingURL=index.js.map |
@@ -101,2 +101,3 @@ import { DefineSchemaOptions, SchemaDefinition, GenericDocument, GenericTableIndexes, GenericTableSearchIndexes, GenericTableVectorIndexes, TableDefinition, SearchIndexConfig, VectorIndexConfig, GenericDataModel, DataModelFromSchemaDefinition } from 'convex/server'; | ||
ref?: string; | ||
deletion?: "soft"; | ||
}): EntDefinition<Document, FieldPaths, Indexes, SearchIndexes, VectorIndexes, Edges & { | ||
@@ -114,2 +115,3 @@ [key in EdgeName]: { | ||
ref?: string; | ||
deletion?: "soft"; | ||
}): EntDefinition<Document, FieldPaths, Indexes, SearchIndexes, VectorIndexes, Edges & { | ||
@@ -131,2 +133,3 @@ [key in EdgeName]: { | ||
ref: true | string; | ||
deletion?: "soft"; | ||
}): EntDefinition<Document, FieldPaths, Indexes, SearchIndexes, VectorIndexes, Edges & { | ||
@@ -152,2 +155,3 @@ [key in EdgesName]: { | ||
ref: true | string; | ||
deletion?: "soft"; | ||
}): EntDefinition<Document, FieldPaths, Indexes, SearchIndexes, VectorIndexes, Edges & { | ||
@@ -238,2 +242,29 @@ [key in EdgesName]: { | ||
}>; | ||
/** | ||
* Add the "soft" deletion behavior to this ent. | ||
* | ||
* When the ent is "soft" deleted, its `deletionTime` field is set to the | ||
* current time and it is not actually deleted. | ||
* | ||
* @param type `"soft"` | ||
*/ | ||
deletion(type: "soft"): EntDefinition<Document & { | ||
deletionTime?: number; | ||
}, FieldPaths | "deletionTime", Indexes, SearchIndexes, VectorIndexes, Edges>; | ||
/** | ||
* Add the "scheduled" deletion behavior to this ent. | ||
* | ||
* The ent is first "soft" deleted and its hard deletion is scheduled | ||
* to run in a separate mutation. | ||
* | ||
* @param type `"scheduled"` | ||
* @param options.delayMs If the `delayMs` option is specified, | ||
* the hard deletion is scheduled to happen after the specified | ||
* time duration. | ||
*/ | ||
deletion(type: "scheduled", options?: { | ||
delayMs: number; | ||
}): EntDefinition<Document & { | ||
deletionTime?: number; | ||
}, FieldPaths | "deletionTime", Indexes, SearchIndexes, VectorIndexes, Edges>; | ||
} | ||
@@ -258,2 +289,3 @@ type NoInfer<T> = [T][T extends any ? 0 : never]; | ||
ref: string; | ||
deletion?: "soft"; | ||
})) | ({ | ||
@@ -264,2 +296,3 @@ cardinality: "multiple"; | ||
ref: string; | ||
deletion?: "soft"; | ||
} | { | ||
@@ -293,2 +326,8 @@ type: "ref"; | ||
}; | ||
type DeletionConfig = { | ||
type: "soft"; | ||
} | { | ||
type: "scheduled"; | ||
delayMs?: number; | ||
}; | ||
type EntDataModelFromSchema<SchemaDef extends SchemaDefinition<any, boolean>> = DataModelFromSchemaDefinition<SchemaDef> & { | ||
@@ -301,2 +340,2 @@ [TableName in keyof SchemaDef["tables"] & string]: SchemaDef["tables"][TableName] extends EntDefinition<any, any, any, any, any, infer Edges> ? { | ||
export { type EdgeConfig, type EntDataModelFromSchema, type EntDefinition, type Expand, type FieldConfig, type GenericEdgeConfig, type GenericEntModel, type GenericEntsDataModel, type SystemFields, defineEnt, defineEntSchema, getEntDefinitions }; | ||
export { type DeletionConfig, type EdgeConfig, type EntDataModelFromSchema, type EntDefinition, type Expand, type FieldConfig, type GenericEdgeConfig, type GenericEntModel, type GenericEntsDataModel, type SystemFields, defineEnt, defineEntSchema, getEntDefinitions }; |
@@ -34,3 +34,3 @@ "use strict"; | ||
const table = schema[tableName]; | ||
for (const edge of edgeConfigsFromEntDefinition(table)) { | ||
for (const edge of edgeConfigsBeforeDefineSchema(table)) { | ||
if ( | ||
@@ -52,3 +52,3 @@ // Skip inverse edges, we process their forward edges | ||
const isSelfDirected = edge.to === tableName; | ||
const inverseEdgeCandidates = edgeConfigsFromEntDefinition( | ||
const inverseEdgeCandidates = edgeConfigsBeforeDefineSchema( | ||
otherTable | ||
@@ -88,2 +88,9 @@ ).filter(canBeInverseEdge(tableName, edge, isSelfDirected)); | ||
} | ||
if (edge.cardinality === "single" && edge.type === "ref" || edge.cardinality === "multiple" && edge.type === "field") { | ||
if (edge.deletion !== void 0 && deletionConfigFromEntDefinition(otherTable) === void 0) { | ||
throw new Error( | ||
`Cannot specify soft deletion behavior for edge "${edge.name}" in table "${tableName}" because the target table "${otherTableName}" does not have a "soft" or "scheduled" deletion behavior configured.` | ||
); | ||
} | ||
} | ||
if (edge.cardinality === "multiple") { | ||
@@ -172,3 +179,3 @@ if (!isSelfDirected && inverseEdge === void 0) { | ||
} | ||
function edgeConfigsFromEntDefinition(table) { | ||
function edgeConfigsBeforeDefineSchema(table) { | ||
return Object.values( | ||
@@ -178,2 +185,5 @@ table.edgeConfigs | ||
} | ||
function deletionConfigFromEntDefinition(table) { | ||
return table.deletionConfig; | ||
} | ||
function defineEnt(documentSchema) { | ||
@@ -196,2 +206,3 @@ return new EntDefinitionImpl(documentSchema); | ||
defaults = {}; | ||
deletionConfig; | ||
constructor(documentSchema) { | ||
@@ -302,3 +313,4 @@ this.documentSchema = documentSchema; | ||
const inverseName = options?.inverse; | ||
this.edgeConfigs[name] = ref !== void 0 ? { name, to, cardinality, type: "field", ref } : { name, to, cardinality, type: "ref", table, field, inverseField }; | ||
const deletion = options?.deletion; | ||
this.edgeConfigs[name] = ref !== void 0 ? { name, to, cardinality, type: "field", ref, deletion } : { name, to, cardinality, type: "ref", table, field, inverseField }; | ||
if (inverseName !== void 0) { | ||
@@ -316,2 +328,18 @@ this.edgeConfigs[inverseName] = { | ||
} | ||
deletion(type, options) { | ||
if (this.documentSchema.deletionTime !== void 0) { | ||
throw new Error( | ||
`Cannot enable "${type}" deletion because "deletionTime" field was already defined.` | ||
); | ||
} | ||
if (this.deletionConfig !== void 0) { | ||
throw new Error(`Deletion behavior can only be specified once.`); | ||
} | ||
this.documentSchema = { | ||
...this.documentSchema, | ||
deletionTime: import_values.v.optional(import_values.v.number()) | ||
}; | ||
this.deletionConfig = { type, ...options }; | ||
return this; | ||
} | ||
}; | ||
@@ -326,3 +354,4 @@ function getEntDefinitions(schema) { | ||
edges: tables[tableName].edgeConfigs, | ||
fields: tables[tableName].fieldConfigs | ||
fields: tables[tableName].fieldConfigs, | ||
deletionConfig: tables[tableName].deletionConfig | ||
} | ||
@@ -329,0 +358,0 @@ }), |
@@ -1,32 +0,5 @@ | ||
import { TableNamesInDataModel, GenericDatabaseWriter, GenericDocument, DocumentByName } from 'convex/server'; | ||
import { GenericId } from 'convex/values'; | ||
import { GenericEntsDataModel, GenericEdgeConfig } from './schema.js'; | ||
declare class WriterImplBase<EntsDataModel extends GenericEntsDataModel, Table extends TableNamesInDataModel<EntsDataModel>> { | ||
protected db: GenericDatabaseWriter<EntsDataModel>; | ||
protected entDefinitions: EntsDataModel; | ||
protected table: Table; | ||
constructor(db: GenericDatabaseWriter<EntsDataModel>, entDefinitions: EntsDataModel, table: Table); | ||
deleteId(id: GenericId<any>): Promise<GenericId<any>>; | ||
deletedIdIn(id: GenericId<any>, table: string): Promise<void>; | ||
writeEdges(docId: GenericId<any>, changes: EdgeChanges): Promise<void>; | ||
checkUniqueness(value: Partial<GenericDocument>, id?: GenericId<any>): Promise<void>; | ||
fieldsOnly(value: Partial<WithEdgePatches<DocumentByName<EntsDataModel, Table>, EntsDataModel[Table]["edges"]>>): GenericDocument; | ||
checkReadAndWriteRule(operation: "create" | "update" | "delete", id: GenericId<Table> | undefined, value: Partial<GenericDocument> | undefined): Promise<void>; | ||
} | ||
type WithEdges<Document extends GenericDocument, Edges extends Record<string, GenericEdgeConfig>> = Document & { | ||
[key in keyof Edges as Edges[key]["cardinality"] extends "single" ? never : key]?: GenericId<Edges[key]["to"]>[]; | ||
}; | ||
type WithEdgePatches<Document extends GenericDocument, Edges extends Record<string, GenericEdgeConfig>> = Document & { | ||
[key in keyof Edges as Edges[key]["cardinality"] extends "single" ? never : key]?: { | ||
add?: GenericId<Edges[key]["to"]>[]; | ||
remove?: GenericId<Edges[key]["to"]>[]; | ||
}; | ||
}; | ||
type EdgeChanges = Record<string, { | ||
add?: GenericId<any>[] | GenericId<any>; | ||
remove?: GenericId<any>[] | GenericId<any>; | ||
removeEdges?: GenericId<any>[]; | ||
}>; | ||
export { type EdgeChanges, type WithEdgePatches, type WithEdges, WriterImplBase }; | ||
import 'convex/server'; | ||
import 'convex/values'; | ||
export { E as EdgeChanges, a as WithEdgeInserts, c as WithEdgePatches, b as WithEdges, W as WriterImplBase } from './index--8O_7LM9.js'; | ||
import './schema.js'; | ||
import './deletion.js'; |
@@ -26,9 +26,10 @@ "use strict"; | ||
module.exports = __toCommonJS(writer_exports); | ||
var import_server = require("convex/server"); | ||
// src/functions.ts | ||
var PromiseQueryOrNullImpl = class _PromiseQueryOrNullImpl extends Promise { | ||
constructor(db, entDefinitions, table, retrieve) { | ||
constructor(ctx, entDefinitions, table, retrieve) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -40,3 +41,3 @@ this.table = table; | ||
return new _PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -53,12 +54,14 @@ this.table, | ||
} | ||
async map(callbackFn) { | ||
const array = await this; | ||
if (array === null) { | ||
return []; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
map(callbackFn) { | ||
return new PromiseArrayImpl(async () => { | ||
const array = await this; | ||
if (array === null) { | ||
return null; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
}); | ||
} | ||
order(order, indexName) { | ||
return new _PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -80,3 +83,3 @@ this.table, | ||
return new PromisePaginationResultOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -90,3 +93,3 @@ this.table, | ||
return new PromiseEntsOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -102,3 +105,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -119,3 +122,3 @@ this.table, | ||
return new PromiseEntWriterImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -139,3 +142,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -162,3 +165,3 @@ this.table, | ||
return new PromiseEntWriterImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -190,3 +193,3 @@ this.table, | ||
return filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -201,3 +204,3 @@ this.table, | ||
(documents) => documents === null ? null : documents.map( | ||
(doc) => entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
) | ||
@@ -231,3 +234,3 @@ ).then(onfulfilled, onrejected); | ||
...(await filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -245,6 +248,6 @@ this.table, | ||
var PromisePaginationResultOrNullImpl = class extends Promise { | ||
constructor(db, entDefinitions, table, retrieve, paginationOpts) { | ||
constructor(ctx, entDefinitions, table, retrieve, paginationOpts) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -274,3 +277,3 @@ this.table = table; | ||
page: await filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -288,3 +291,3 @@ this.table, | ||
page: result.page.map( | ||
(doc) => entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
) | ||
@@ -296,6 +299,6 @@ } | ||
var PromiseEntsOrNullImpl = class extends Promise { | ||
constructor(db, entDefinitions, table, retrieve, throwIfNull) { | ||
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -306,12 +309,14 @@ this.table = table; | ||
} | ||
async map(callbackFn) { | ||
const array = await this; | ||
if (array === null) { | ||
return []; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
map(callbackFn) { | ||
return new PromiseArrayImpl(async () => { | ||
const array = await this; | ||
if (array === null) { | ||
return null; | ||
} | ||
return await Promise.all(array.map(callbackFn)); | ||
}); | ||
} | ||
first() { | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -331,3 +336,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -351,3 +356,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -370,3 +375,3 @@ this.table, | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -393,3 +398,3 @@ this.table, | ||
return filterByReadRule( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -404,3 +409,3 @@ this.table, | ||
(docs) => docs === null ? null : docs.map( | ||
(doc) => entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
) | ||
@@ -411,4 +416,4 @@ ).then(onfulfilled, onrejected); | ||
var PromiseEdgeOrNullImpl = class extends PromiseEntsOrNullImpl { | ||
constructor(db, entDefinitions, table, field, retrieveRange) { | ||
super(db, entDefinitions, table, () => retrieveRange((q) => q), false); | ||
constructor(ctx, entDefinitions, table, field, retrieveRange) { | ||
super(ctx, entDefinitions, table, () => retrieveRange((q) => q), false); | ||
this.field = field; | ||
@@ -423,6 +428,6 @@ this.retrieveRange = retrieveRange; | ||
var PromiseEntOrNullImpl = class _PromiseEntOrNullImpl extends Promise { | ||
constructor(db, entDefinitions, table, retrieve, throwIfNull) { | ||
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -445,3 +450,3 @@ this.table = table; | ||
const decision = await readPolicy( | ||
entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
); | ||
@@ -459,3 +464,3 @@ if (this.throwIfNull && !decision) { | ||
return this.doc().then( | ||
(doc) => doc === null ? null : entWrapper(doc, this.db, this.entDefinitions, this.table) | ||
(doc) => doc === null ? null : entWrapper(doc, this.ctx, this.entDefinitions, this.table) | ||
).then(onfulfilled, onrejected); | ||
@@ -470,7 +475,7 @@ } | ||
edgeImpl(edge, throwIfNull = false) { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[edge]; | ||
const edgeDefinition = getEdgeDefinitions(this.entDefinitions, this.table)[edge]; | ||
if (edgeDefinition.cardinality === "multiple") { | ||
if (edgeDefinition.type === "ref") { | ||
return new PromiseEdgeOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -484,3 +489,3 @@ edgeDefinition.to, | ||
} | ||
const edgeDocs = await this.db.query(edgeDefinition.table).withIndex( | ||
const edgeDocs = await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
@@ -491,3 +496,3 @@ (q) => indexRange(q.eq(edgeDefinition.field, id)) | ||
edgeDocs.map( | ||
(edgeDoc) => this.db.get(edgeDoc[edgeDefinition.ref]) | ||
(edgeDoc) => this.ctx.db.get(edgeDoc[edgeDefinition.ref]) | ||
) | ||
@@ -506,3 +511,3 @@ )).filter((doc, i) => { | ||
return new PromiseQueryOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -515,3 +520,3 @@ edgeDefinition.to, | ||
} | ||
return this.db.query(edgeDefinition.to).withIndex( | ||
return this.ctx.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
@@ -524,3 +529,3 @@ (q) => q.eq(edgeDefinition.ref, id) | ||
return new _PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -534,3 +539,3 @@ edgeDefinition.to, | ||
if (edgeDefinition.type === "ref") { | ||
const otherDoc = await this.db.query(edgeDefinition.to).withIndex( | ||
const otherDoc = await this.ctx.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
@@ -551,3 +556,3 @@ (q) => q.eq(edgeDefinition.ref, id) | ||
doc: async () => { | ||
const otherDoc = await this.db.get(otherId); | ||
const otherDoc = await this.ctx.db.get(otherId); | ||
if (otherDoc === null) { | ||
@@ -566,6 +571,23 @@ throw new Error( | ||
}; | ||
function entWrapper(fields, db, entDefinitions, table) { | ||
var PromiseArrayImpl = class extends Promise { | ||
constructor(retrieve) { | ||
super(() => { | ||
}); | ||
this.retrieve = retrieve; | ||
} | ||
async filter(predicate) { | ||
const array = await this.retrieve(); | ||
if (array === null) { | ||
return null; | ||
} | ||
return array.filter(predicate); | ||
} | ||
then(onfulfilled, onrejected) { | ||
return this.retrieve().then(onfulfilled, onrejected); | ||
} | ||
}; | ||
function entWrapper(fields, ctx, entDefinitions, table) { | ||
const doc = { ...fields }; | ||
const queryInterface = new PromiseEntWriterImpl( | ||
db, | ||
ctx, | ||
entDefinitions, | ||
@@ -593,2 +615,10 @@ table, | ||
}); | ||
Object.defineProperty(doc, "doc", { | ||
value: () => { | ||
return doc; | ||
}, | ||
enumerable: false, | ||
writable: false, | ||
configurable: false | ||
}); | ||
Object.defineProperty(doc, "patch", { | ||
@@ -628,5 +658,5 @@ value: (value) => { | ||
var PromiseEntWriterImpl = class extends PromiseEntOrNullImpl { | ||
constructor(db, entDefinitions, table, retrieve, throwIfNull) { | ||
super(db, entDefinitions, table, retrieve, throwIfNull); | ||
this.db = db; | ||
constructor(ctx, entDefinitions, table, retrieve, throwIfNull) { | ||
super(ctx, entDefinitions, table, retrieve, throwIfNull); | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -636,3 +666,3 @@ this.table = table; | ||
this.throwIfNull = throwIfNull; | ||
this.base = new WriterImplBase(db, entDefinitions, table); | ||
this.base = new WriterImplBase(ctx, entDefinitions, table); | ||
} | ||
@@ -642,3 +672,3 @@ base; | ||
return new PromiseEntIdImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -652,7 +682,10 @@ this.table, | ||
const fields = this.base.fieldsOnly(value); | ||
await this.db.patch(id, fields); | ||
await this.ctx.db.patch(id, fields); | ||
const edges = {}; | ||
await Promise.all( | ||
Object.keys(value).map(async (key) => { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[key]; | ||
const edgeDefinition = getEdgeDefinitions( | ||
this.entDefinitions, | ||
this.table | ||
)[key]; | ||
if (edgeDefinition === void 0 || edgeDefinition.cardinality === "single" && edgeDefinition.type === "field") { | ||
@@ -662,5 +695,36 @@ return; | ||
if (edgeDefinition.cardinality === "single") { | ||
throw new Error("Cannot set 1:1 edge from optional end."); | ||
throw new Error( | ||
`Cannot set 1:1 edge "${edgeDefinition.name}" on ent in table "${this.table}", update the ent in "${edgeDefinition.to}" table instead.` | ||
); | ||
} else { | ||
edges[key] = value[key]; | ||
if (edgeDefinition.type === "field") { | ||
throw new Error( | ||
`Cannot set 1:many edges "${edgeDefinition.name}" on ent in table "${this.table}", update the ents in "${edgeDefinition.to}" table instead.` | ||
); | ||
} else { | ||
const { add, remove } = value[key]; | ||
const removeEdges = (await Promise.all( | ||
(remove ?? []).map( | ||
async (edgeId) => (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, id).eq( | ||
edgeDefinition.ref, | ||
edgeId | ||
) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id).eq( | ||
edgeDefinition.field, | ||
edgeId | ||
) | ||
).collect() : [] | ||
) | ||
) | ||
)).map((edgeDoc) => edgeDoc._id); | ||
edges[key] = { | ||
add, | ||
removeEdges | ||
}; | ||
} | ||
} | ||
@@ -676,3 +740,3 @@ }) | ||
return new PromiseEntIdImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -686,7 +750,7 @@ this.table, | ||
const fields = this.base.fieldsOnly(value); | ||
await this.db.replace(docId, fields); | ||
await this.ctx.db.replace(docId, fields); | ||
const edges = {}; | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
getEdgeDefinitions(this.entDefinitions, this.table) | ||
).map(async (edgeDefinition) => { | ||
@@ -697,3 +761,3 @@ const key = edgeDefinition.name; | ||
if (edgeDefinition.type === "ref") { | ||
const oldDoc = await this.db.get(docId); | ||
const oldDoc = await this.ctx.db.get(docId); | ||
if (oldDoc[key] !== void 0 && oldDoc[key] !== idOrIds) { | ||
@@ -705,17 +769,12 @@ throw new Error("Cannot set 1:1 edge from optional end."); | ||
if (edgeDefinition.type === "field") { | ||
const existing = (await this.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, docId) | ||
).collect()).map((doc) => doc._id); | ||
edges[key] = { | ||
add: idOrIds, | ||
remove: existing | ||
}; | ||
if (idOrIds !== void 0) { | ||
throw new Error("Cannot set 1:many edge from many end."); | ||
} | ||
} else { | ||
const requested = new Set(idOrIds ?? []); | ||
const remove = (await this.db.query(edgeDefinition.table).withIndex( | ||
const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, docId) | ||
).collect()).map((doc) => [doc._id, doc[edgeDefinition.ref]]).concat( | ||
edgeDefinition.symmetric ? (await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.symmetric ? (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
@@ -734,4 +793,4 @@ (q) => q.eq(edgeDefinition.ref, docId) | ||
edges[key] = { | ||
add: Array.from(requested), | ||
removeEdges: remove | ||
add: idOrIds ?? [], | ||
removeEdges | ||
}; | ||
@@ -750,10 +809,10 @@ } | ||
const id = docId; | ||
return this.base.deleteId(id); | ||
return this.base.deleteId(id, "default"); | ||
} | ||
}; | ||
var PromiseEntIdImpl = class extends Promise { | ||
constructor(db, entDefinitions, table, retrieve) { | ||
constructor(ctx, entDefinitions, table, retrieve) { | ||
super(() => { | ||
}); | ||
this.db = db; | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
@@ -765,3 +824,3 @@ this.table = table; | ||
return new PromiseEntOrNullImpl( | ||
this.db, | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -771,3 +830,3 @@ this.table, | ||
const id = await this.retrieve(); | ||
return { id, doc: async () => this.db.get(id) }; | ||
return { id, doc: async () => this.ctx.db.get(id) }; | ||
}, | ||
@@ -791,3 +850,3 @@ true | ||
} | ||
async function filterByReadRule(db, entDefinitions, table, docs, throwIfNull) { | ||
async function filterByReadRule(ctx, entDefinitions, table, docs, throwIfNull) { | ||
if (docs === null) { | ||
@@ -803,3 +862,3 @@ return null; | ||
const decision = await readPolicy( | ||
entWrapper(doc, db, entDefinitions, table) | ||
entWrapper(doc, ctx, entDefinitions, table) | ||
); | ||
@@ -822,97 +881,111 @@ if (throwIfNull && !decision) { | ||
} | ||
function getEdgeDefinitions(entDefinitions, table) { | ||
return entDefinitions[table].edges; | ||
} | ||
function getDeletionConfig(entDefinitions, table) { | ||
return entDefinitions[table].deletionConfig; | ||
} | ||
// src/writer.ts | ||
var WriterImplBase = class _WriterImplBase { | ||
constructor(db, entDefinitions, table) { | ||
this.db = db; | ||
constructor(ctx, entDefinitions, table) { | ||
this.ctx = ctx; | ||
this.entDefinitions = entDefinitions; | ||
this.table = table; | ||
} | ||
async deleteId(id) { | ||
async deleteId(id, behavior) { | ||
await this.checkReadAndWriteRule("delete", id, void 0); | ||
let memoized = void 0; | ||
const oldDoc = async () => { | ||
if (memoized !== void 0) { | ||
return memoized; | ||
} | ||
return memoized = await this.db.get(id); | ||
}; | ||
const deletionConfig = getDeletionConfig(this.entDefinitions, this.table); | ||
const isDeletingSoftly = behavior !== "hard" && deletionConfig !== void 0 && (deletionConfig.type === "soft" || deletionConfig.type === "scheduled"); | ||
if (behavior === "soft" && !isDeletingSoftly) { | ||
throw new Error( | ||
`Cannot soft delete document with ID "${id}" in table "${this.table}" because it does not have an "allowSoft", "soft" or "scheduled" deletion behavior configured.` | ||
); | ||
} | ||
const edges = {}; | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
).map(async (edgeDefinition) => { | ||
const key = edgeDefinition.name; | ||
if (edgeDefinition.cardinality === "single") { | ||
if (edgeDefinition.type === "ref") { | ||
edges[key] = { | ||
remove: (await oldDoc())[key] | ||
}; | ||
} | ||
} else { | ||
if (edgeDefinition.type === "field") { | ||
const existing = (await this.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id) | ||
).collect()).map((doc) => doc._id); | ||
edges[key] = { remove: existing }; | ||
} else { | ||
const existing = (await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, id) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.db.query(edgeDefinition.table).withIndex( | ||
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map( | ||
async (edgeDefinition) => { | ||
const key = edgeDefinition.name; | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") { | ||
if (!isDeletingSoftly || edgeDefinition.deletion === "soft") { | ||
const remove = (await this.ctx.db.query(edgeDefinition.to).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id) | ||
).collect() : [] | ||
).map((doc) => doc._id); | ||
edges[key] = { removeEdges: existing }; | ||
).collect()).map((doc) => doc._id); | ||
edges[key] = { remove }; | ||
} | ||
} else if (edgeDefinition.cardinality === "multiple") { | ||
if (!isDeletingSoftly) { | ||
const removeEdges = (await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, id) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.ctx.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, id) | ||
).collect() : [] | ||
).map((doc) => doc._id); | ||
edges[key] = { removeEdges }; | ||
} | ||
} | ||
} | ||
}) | ||
) | ||
); | ||
await this.db.delete(id); | ||
await this.writeEdges(id, edges); | ||
const deletionTime = +/* @__PURE__ */ new Date(); | ||
if (isDeletingSoftly) { | ||
await this.ctx.db.patch(id, { deletionTime }); | ||
} else { | ||
try { | ||
await this.ctx.db.delete(id); | ||
} catch (e) { | ||
} | ||
} | ||
await this.writeEdges(id, edges, isDeletingSoftly); | ||
if (deletionConfig !== void 0 && deletionConfig.type === "scheduled") { | ||
const fnRef = this.ctx.scheduledDelete ?? (0, import_server.makeFunctionReference)( | ||
"functions:scheduledDelete" | ||
); | ||
await this.ctx.scheduler.runAfter(deletionConfig.delayMs ?? 0, fnRef, { | ||
origin: { | ||
id, | ||
table: this.table, | ||
deletionTime | ||
}, | ||
inProgress: false, | ||
stack: [] | ||
}); | ||
} | ||
return id; | ||
} | ||
async deletedIdIn(id, table) { | ||
await new _WriterImplBase(this.db, this.entDefinitions, table).deleteId(id); | ||
async deletedIdIn(id, table, cascadingSoft) { | ||
await new _WriterImplBase(this.ctx, this.entDefinitions, table).deleteId( | ||
id, | ||
cascadingSoft ? "soft" : "hard" | ||
); | ||
} | ||
async writeEdges(docId, changes) { | ||
async writeEdges(docId, changes, deleteSoftly) { | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
).map(async (edgeDefinition) => { | ||
const idOrIds = changes[edgeDefinition.name]; | ||
if (idOrIds === void 0) { | ||
return; | ||
} | ||
if (edgeDefinition.cardinality === "single") { | ||
if (edgeDefinition.type === "ref") { | ||
if (idOrIds.remove !== void 0) { | ||
await this.deletedIdIn( | ||
idOrIds.remove, | ||
edgeDefinition.to | ||
); | ||
} | ||
if (idOrIds.add !== void 0) { | ||
await this.db.patch( | ||
idOrIds.add, | ||
{ [edgeDefinition.ref]: docId } | ||
); | ||
} | ||
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map( | ||
async (edgeDefinition) => { | ||
const idOrIds = changes[edgeDefinition.name]; | ||
if (idOrIds === void 0) { | ||
return; | ||
} | ||
} else { | ||
if (edgeDefinition.type === "field") { | ||
if (idOrIds.remove !== void 0) { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "ref" || edgeDefinition.cardinality === "multiple" && edgeDefinition.type === "field") { | ||
if (idOrIds.remove !== void 0 && idOrIds.remove.length > 0) { | ||
await Promise.all( | ||
idOrIds.remove.map( | ||
(id) => this.deletedIdIn(id, edgeDefinition.to) | ||
(id) => this.deletedIdIn( | ||
id, | ||
edgeDefinition.to, | ||
(deleteSoftly ?? false) && edgeDefinition.deletion === "soft" | ||
) | ||
) | ||
); | ||
} | ||
if (idOrIds.add !== void 0) { | ||
if (idOrIds.add !== void 0 && idOrIds.add.length > 0) { | ||
await Promise.all( | ||
idOrIds.add.map( | ||
async (id) => this.db.patch(id, { | ||
async (id) => this.ctx.db.patch(id, { | ||
[edgeDefinition.ref]: docId | ||
@@ -923,30 +996,8 @@ }) | ||
} | ||
} else { | ||
let removeEdges = []; | ||
if (idOrIds.remove !== void 0) { | ||
removeEdges = (await Promise.all( | ||
idOrIds.remove.map( | ||
async (id) => (await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.field, | ||
(q) => q.eq(edgeDefinition.field, docId).eq( | ||
edgeDefinition.ref, | ||
id | ||
) | ||
).collect()).concat( | ||
edgeDefinition.symmetric ? await this.db.query(edgeDefinition.table).withIndex( | ||
edgeDefinition.ref, | ||
(q) => q.eq(edgeDefinition.ref, docId).eq(edgeDefinition.field, id) | ||
).collect() : [] | ||
) | ||
) | ||
)).map((doc) => doc._id); | ||
} | ||
if (idOrIds.removeEdges !== void 0) { | ||
removeEdges = idOrIds.removeEdges; | ||
} | ||
if (removeEdges.length > 0) { | ||
} else if (edgeDefinition.cardinality === "multiple") { | ||
if ((idOrIds.removeEdges ?? []).length > 0) { | ||
await Promise.all( | ||
removeEdges.map(async (id) => { | ||
idOrIds.removeEdges.map(async (id) => { | ||
try { | ||
await this.db.delete(id); | ||
await this.ctx.db.delete(id); | ||
} catch (e) { | ||
@@ -960,3 +1011,3 @@ } | ||
idOrIds.add.map(async (id) => { | ||
await this.db.insert(edgeDefinition.table, { | ||
await this.ctx.db.insert(edgeDefinition.table, { | ||
[edgeDefinition.field]: docId, | ||
@@ -966,3 +1017,3 @@ [edgeDefinition.ref]: id | ||
if (edgeDefinition.symmetric) { | ||
await this.db.insert(edgeDefinition.table, { | ||
await this.ctx.db.insert(edgeDefinition.table, { | ||
[edgeDefinition.field]: id, | ||
@@ -977,3 +1028,3 @@ [edgeDefinition.ref]: docId | ||
} | ||
}) | ||
) | ||
); | ||
@@ -989,3 +1040,3 @@ } | ||
const fieldValue = value[key]; | ||
const existing = await this.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
if (existing !== null && (id === void 0 || existing._id !== id)) { | ||
@@ -1000,18 +1051,18 @@ throw new Error( | ||
await Promise.all( | ||
Object.values( | ||
this.entDefinitions[this.table].edges | ||
).map(async (edgeDefinition) => { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "field" && edgeDefinition.unique) { | ||
const key = edgeDefinition.field; | ||
if (value[key] === void 0) { | ||
return; | ||
Object.values(getEdgeDefinitions(this.entDefinitions, this.table)).map( | ||
async (edgeDefinition) => { | ||
if (edgeDefinition.cardinality === "single" && edgeDefinition.type === "field" && edgeDefinition.unique) { | ||
const key = edgeDefinition.field; | ||
if (value[key] === void 0) { | ||
return; | ||
} | ||
const existing = await this.ctx.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
if (existing !== null && (id === void 0 || existing._id !== id)) { | ||
throw new Error( | ||
`In table "${this.table}" cannot create a duplicate 1:1 edge "${edgeDefinition.name}" to ID "${value[key]}", existing document with ID "${existing._id}" already has it.` | ||
); | ||
} | ||
} | ||
const existing = await this.db.query(this.table).withIndex(key, (q) => q.eq(key, value[key])).unique(); | ||
if (existing !== null && (id === void 0 || existing._id !== id)) { | ||
throw new Error( | ||
`In table "${this.table}" cannot create a duplicate 1:1 edge "${edgeDefinition.name}" to ID "${value[key]}", existing document with ID "${existing._id}" already has it.` | ||
); | ||
} | ||
} | ||
}) | ||
) | ||
); | ||
@@ -1022,3 +1073,6 @@ } | ||
Object.keys(value).forEach((key) => { | ||
const edgeDefinition = this.entDefinitions[this.table].edges[key]; | ||
const edgeDefinition = getEdgeDefinitions( | ||
this.entDefinitions, | ||
this.table | ||
)[key]; | ||
if (edgeDefinition === void 0) { | ||
@@ -1034,3 +1088,3 @@ fields[key] = value[key]; | ||
if (readPolicy !== void 0) { | ||
const doc = await this.db.get(id); | ||
const doc = await this.ctx.db.get(id); | ||
if (doc === null) { | ||
@@ -1054,4 +1108,4 @@ throw new Error( | ||
const ent = id === void 0 ? void 0 : entWrapper( | ||
await this.db.get(id), | ||
this.db, | ||
await this.ctx.db.get(id), | ||
this.ctx, | ||
this.entDefinitions, | ||
@@ -1058,0 +1112,0 @@ this.table |
{ | ||
"name": "convex-ents", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Relations, default values, unique fields, RLS for Convex", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
817096
18
6538