@davidtkramer/convex-relations 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -2
- package/dist/index.js +90 -32
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GenericDataModel, TableNamesInDataModel, DocumentByName,
|
|
1
|
+
import { GenericDataModel, TableNamesInDataModel, DocumentByName, IndexNames, NamedTableInfo, IndexRangeBuilder, NamedIndex, IndexRange, GenericTableInfo, FilterBuilder, ExpressionOrValue, GenericDatabaseReader, SchemaDefinition } from 'convex/server';
|
|
2
2
|
import { GenericId } from 'convex/values';
|
|
3
3
|
|
|
4
4
|
type Simplify<T> = {
|
|
@@ -139,6 +139,8 @@ type TableNamespace<DataModel extends GenericDataModel, Table extends AppTable<D
|
|
|
139
139
|
find<const Id extends GenericId<Table>>(id: Id): FindQueryBuilder<AppDoc<DataModel, Table>>;
|
|
140
140
|
findOrNull<const Id extends GenericId<Table>>(id: Id): FindOrNullQueryBuilder<AppDoc<DataModel, Table>>;
|
|
141
141
|
in<const Id extends GenericId<Table>>(ids: Id[]): TableBatchQueryFacade<DataModel, Table>;
|
|
142
|
+
withIndex<const IndexName extends UserIndex<DataModel, Table>>(index: IndexName, selector: IndexSelector<DataModel, Table, IndexName>): TableQueryFacade<DataModel, Table>;
|
|
143
|
+
withIndex<const IndexName extends UserIndex<DataModel, Table>>(index: IndexName): TableRangeQueryFacade<DataModel, Table>;
|
|
142
144
|
through: {
|
|
143
145
|
<const SourceTable extends AppTable<DataModel>, SourceItem, const TargetField extends ThroughSourceField<DataModel, Table, SourceItem>>(sourceQuery: ThroughCollectionSource<DataModel, SourceTable, SourceItem>, targetField: TargetField): ThroughQueryFacade<DataModel, Table, SourceItem>;
|
|
144
146
|
<SourceItem, const TargetField extends ThroughSourceField<DataModel, Table, SourceItem>>(sourceQuery: AnyManySourceNode<SourceItem>, targetField: TargetField): ManyThroughQueryBuilder<AppDoc<DataModel, Table>, SourceItem>;
|
|
@@ -165,17 +167,19 @@ type QuerySourcePlan = {
|
|
|
165
167
|
table: string;
|
|
166
168
|
index?: string;
|
|
167
169
|
selector?: unknown;
|
|
170
|
+
indexFields?: readonly string[];
|
|
168
171
|
} | {
|
|
169
172
|
kind: 'batch';
|
|
170
173
|
table: string;
|
|
171
174
|
index: string;
|
|
172
175
|
values: unknown[];
|
|
176
|
+
indexFields?: readonly string[];
|
|
173
177
|
};
|
|
174
178
|
type QueryPlan = {
|
|
175
179
|
source: QuerySourcePlan;
|
|
176
180
|
modifiers: QueryModifier[];
|
|
177
181
|
expanders: AnyWithBuilder<any, any>[];
|
|
178
182
|
};
|
|
179
|
-
declare function createQueryFacade<DataModel extends GenericDataModel>(db: GenericDatabaseReader<DataModel>): QueryFacade<DataModel>;
|
|
183
|
+
declare function createQueryFacade<DataModel extends GenericDataModel>(db: GenericDatabaseReader<DataModel>, schema: SchemaDefinition<any, boolean>): QueryFacade<DataModel>;
|
|
180
184
|
|
|
181
185
|
export { type AppDoc, type AppTable, type QueryFacade, type RootIndexValueArg, type StrictRootIndexValueArg, type UserIndex, createQueryFacade };
|
package/dist/index.js
CHANGED
|
@@ -67,16 +67,15 @@ function withExpander(plan, expander) {
|
|
|
67
67
|
expanders: [...plan.expanders, expander]
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
function normalizeIndexValues(index, value) {
|
|
70
|
+
function normalizeIndexValues(index, value, indexFields) {
|
|
71
71
|
if (Array.isArray(value)) {
|
|
72
|
-
|
|
73
|
-
return Object.fromEntries(fieldNames.map((field, index2) => [field, value[index2]]));
|
|
72
|
+
return Object.fromEntries(indexFields.map((field, index2) => [field, value[index2]]));
|
|
74
73
|
}
|
|
75
74
|
if (isPlainObject(value)) {
|
|
76
75
|
return value;
|
|
77
76
|
}
|
|
78
77
|
return {
|
|
79
|
-
[
|
|
78
|
+
[index === "by_id" ? "_id" : indexFields[0]]: value
|
|
80
79
|
};
|
|
81
80
|
}
|
|
82
81
|
function isPlainObject(value) {
|
|
@@ -89,19 +88,16 @@ function applyIndexValues(query, values) {
|
|
|
89
88
|
}
|
|
90
89
|
return current;
|
|
91
90
|
}
|
|
92
|
-
function
|
|
93
|
-
return inferFieldNamesFromIndex(index)[0];
|
|
94
|
-
}
|
|
95
|
-
function inferFieldNamesFromIndex(index) {
|
|
91
|
+
function requireIndexFields(index, indexFields) {
|
|
96
92
|
if (index === "by_id") {
|
|
97
93
|
return ["_id"];
|
|
98
94
|
}
|
|
99
|
-
if (
|
|
100
|
-
|
|
95
|
+
if (indexFields === void 0) {
|
|
96
|
+
throw new Error(`Missing schema metadata for index ${index}`);
|
|
101
97
|
}
|
|
102
|
-
|
|
98
|
+
return indexFields;
|
|
103
99
|
}
|
|
104
|
-
function createIndexedQuery(db, table, index, selector) {
|
|
100
|
+
function createIndexedQuery(db, table, index, selector, indexFields = []) {
|
|
105
101
|
const baseQuery = db.query(table);
|
|
106
102
|
if (index === void 0) {
|
|
107
103
|
return baseQuery;
|
|
@@ -115,9 +111,10 @@ function createIndexedQuery(db, table, index, selector) {
|
|
|
115
111
|
if (index === "by_id") {
|
|
116
112
|
return baseQuery.withIndex(index, (q) => q.eq("_id", selector));
|
|
117
113
|
}
|
|
114
|
+
const fields = requireIndexFields(index, indexFields);
|
|
118
115
|
return baseQuery.withIndex(
|
|
119
116
|
index,
|
|
120
|
-
(q) => applyIndexValues(q, normalizeIndexValues(index, selector))
|
|
117
|
+
(q) => applyIndexValues(q, normalizeIndexValues(index, selector, fields))
|
|
121
118
|
);
|
|
122
119
|
}
|
|
123
120
|
function sourceDescription(plan) {
|
|
@@ -154,7 +151,13 @@ async function* iterateDecoratedSourceItems(db, plan) {
|
|
|
154
151
|
const runtime = createPlanRuntime(db, plan);
|
|
155
152
|
const source = plan.source;
|
|
156
153
|
const query = buildQuery(
|
|
157
|
-
() => createIndexedQuery(
|
|
154
|
+
() => createIndexedQuery(
|
|
155
|
+
db,
|
|
156
|
+
source.table,
|
|
157
|
+
source.index,
|
|
158
|
+
source.selector,
|
|
159
|
+
source.indexFields
|
|
160
|
+
),
|
|
158
161
|
plan.modifiers
|
|
159
162
|
);
|
|
160
163
|
for await (const rawItem of query) {
|
|
@@ -191,18 +194,28 @@ function createPlanRuntime(db, plan) {
|
|
|
191
194
|
return {
|
|
192
195
|
many: async () => (await Promise.all(
|
|
193
196
|
source.values.map(
|
|
194
|
-
async (value) => await
|
|
197
|
+
async (value) => await queryManyByIndex(
|
|
198
|
+
db,
|
|
199
|
+
source.table,
|
|
200
|
+
source.index,
|
|
201
|
+
value,
|
|
202
|
+
source.indexFields
|
|
203
|
+
)
|
|
195
204
|
)
|
|
196
|
-
)).
|
|
197
|
-
(doc) => doc !== null
|
|
198
|
-
),
|
|
205
|
+
)).flat(),
|
|
199
206
|
mapOne: async (rawItem) => rawItem,
|
|
200
207
|
mapMany: async (rawItems) => rawItems,
|
|
201
208
|
missingMessages: {}
|
|
202
209
|
};
|
|
203
210
|
case "query": {
|
|
204
211
|
const runQuery = () => buildQuery(
|
|
205
|
-
() => createIndexedQuery(
|
|
212
|
+
() => createIndexedQuery(
|
|
213
|
+
db,
|
|
214
|
+
source.table,
|
|
215
|
+
source.index,
|
|
216
|
+
source.selector,
|
|
217
|
+
source.indexFields
|
|
218
|
+
),
|
|
206
219
|
plan.modifiers
|
|
207
220
|
);
|
|
208
221
|
return {
|
|
@@ -593,20 +606,22 @@ function createIdPlan(table, id) {
|
|
|
593
606
|
id
|
|
594
607
|
});
|
|
595
608
|
}
|
|
596
|
-
function createQueryPlan(table, index, selector) {
|
|
609
|
+
function createQueryPlan(table, index, selector, indexFields) {
|
|
597
610
|
return createPlan({
|
|
598
611
|
kind: "query",
|
|
599
612
|
table,
|
|
600
613
|
index,
|
|
601
|
-
selector
|
|
614
|
+
selector,
|
|
615
|
+
indexFields
|
|
602
616
|
});
|
|
603
617
|
}
|
|
604
|
-
function createBatchPlan(table, index, values) {
|
|
618
|
+
function createBatchPlan(table, index, values, indexFields) {
|
|
605
619
|
return createPlan({
|
|
606
620
|
kind: "batch",
|
|
607
621
|
table,
|
|
608
622
|
index,
|
|
609
|
-
values
|
|
623
|
+
values,
|
|
624
|
+
indexFields
|
|
610
625
|
});
|
|
611
626
|
}
|
|
612
627
|
function isCollectionSource(value) {
|
|
@@ -627,7 +642,7 @@ function normalizeIndexSelectorArgs(args) {
|
|
|
627
642
|
}
|
|
628
643
|
return args;
|
|
629
644
|
}
|
|
630
|
-
function createTableNamespace(db, table) {
|
|
645
|
+
function createTableNamespace(db, table, resolveIndexFields) {
|
|
631
646
|
const rootFacade = createCollectionFacade(db, createQueryPlan(table));
|
|
632
647
|
const target = {
|
|
633
648
|
...rootFacade,
|
|
@@ -640,6 +655,12 @@ function createTableNamespace(db, table) {
|
|
|
640
655
|
in(ids) {
|
|
641
656
|
return createBatchFacade(db, createBatchPlan(table, "by_id", ids));
|
|
642
657
|
},
|
|
658
|
+
withIndex(index, selector) {
|
|
659
|
+
return createCollectionFacade(
|
|
660
|
+
db,
|
|
661
|
+
createQueryPlan(table, index, selector, resolveIndexFields(table, index))
|
|
662
|
+
);
|
|
663
|
+
},
|
|
643
664
|
through(sourceQuery, targetField) {
|
|
644
665
|
if (isCollectionSource(sourceQuery)) {
|
|
645
666
|
return createThroughCollectionFacade(db, {
|
|
@@ -685,18 +706,49 @@ function createTableNamespace(db, table) {
|
|
|
685
706
|
createQueryPlan(
|
|
686
707
|
table,
|
|
687
708
|
prop,
|
|
688
|
-
normalizeIndexSelectorArgs(args)
|
|
709
|
+
normalizeIndexSelectorArgs(args),
|
|
710
|
+
resolveIndexFields(table, prop)
|
|
689
711
|
)
|
|
690
712
|
));
|
|
691
713
|
indexMethod.in = (values) => createBatchFacade(
|
|
692
714
|
db,
|
|
693
|
-
createBatchPlan(
|
|
715
|
+
createBatchPlan(
|
|
716
|
+
table,
|
|
717
|
+
prop,
|
|
718
|
+
values,
|
|
719
|
+
resolveIndexFields(table, prop)
|
|
720
|
+
)
|
|
694
721
|
);
|
|
695
722
|
return indexMethod;
|
|
696
723
|
}
|
|
697
724
|
});
|
|
698
725
|
}
|
|
699
|
-
function
|
|
726
|
+
function createIndexFieldResolver(schema) {
|
|
727
|
+
const cache = /* @__PURE__ */ new Map();
|
|
728
|
+
return (table, index) => {
|
|
729
|
+
if (index === "by_id") {
|
|
730
|
+
return ["_id"];
|
|
731
|
+
}
|
|
732
|
+
const cacheKey = `${table}:${index}`;
|
|
733
|
+
const cached = cache.get(cacheKey);
|
|
734
|
+
if (cached !== void 0) {
|
|
735
|
+
return cached;
|
|
736
|
+
}
|
|
737
|
+
const tableDefinition = schema.tables[table];
|
|
738
|
+
const indexes = tableDefinition?.[" indexes"]();
|
|
739
|
+
const match = indexes?.find(
|
|
740
|
+
(candidate) => candidate.indexDescriptor === index
|
|
741
|
+
);
|
|
742
|
+
if (match !== void 0 && Array.isArray(match.fields)) {
|
|
743
|
+
const fields = [...match.fields];
|
|
744
|
+
cache.set(cacheKey, fields);
|
|
745
|
+
return fields;
|
|
746
|
+
}
|
|
747
|
+
throw new Error(`Missing schema metadata for index ${table}.${index}`);
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function createQueryFacade(db, schema) {
|
|
751
|
+
const resolveIndexFields = createIndexFieldResolver(schema);
|
|
700
752
|
return new Proxy(
|
|
701
753
|
{},
|
|
702
754
|
{
|
|
@@ -704,19 +756,25 @@ function createQueryFacade(db) {
|
|
|
704
756
|
if (typeof prop !== "string" || RESERVED_PROMISE_KEYS.has(prop)) {
|
|
705
757
|
return void 0;
|
|
706
758
|
}
|
|
707
|
-
return createTableNamespace(
|
|
759
|
+
return createTableNamespace(
|
|
760
|
+
db,
|
|
761
|
+
prop,
|
|
762
|
+
resolveIndexFields
|
|
763
|
+
);
|
|
708
764
|
}
|
|
709
765
|
}
|
|
710
766
|
);
|
|
711
767
|
}
|
|
712
|
-
async function
|
|
768
|
+
async function queryManyByIndex(db, table, index, value, indexFields) {
|
|
713
769
|
if (index === "by_id") {
|
|
714
|
-
|
|
770
|
+
const doc = await db.query(table).withIndex("by_id", (q) => q.eq("_id", value)).unique();
|
|
771
|
+
return doc ? [doc] : [];
|
|
715
772
|
}
|
|
773
|
+
const fields = requireIndexFields(index, indexFields);
|
|
716
774
|
return await db.query(table).withIndex(
|
|
717
775
|
index,
|
|
718
|
-
(q) => applyIndexValues(q, normalizeIndexValues(index, value))
|
|
719
|
-
).
|
|
776
|
+
(q) => applyIndexValues(q, normalizeIndexValues(index, value, fields))
|
|
777
|
+
).collect();
|
|
720
778
|
}
|
|
721
779
|
export {
|
|
722
780
|
createQueryFacade
|