@convex-dev/better-auth 0.7.0-alpha.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/client/adapter.d.ts +10 -1
- package/dist/commonjs/client/adapter.d.ts.map +1 -1
- package/dist/commonjs/client/adapter.js +190 -192
- package/dist/commonjs/client/adapter.js.map +1 -1
- package/dist/commonjs/client/index.d.ts +283 -179
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js +46 -58
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/commonjs/component/adapterTest.d.ts +19 -0
- package/dist/commonjs/component/adapterTest.d.ts.map +1 -0
- package/dist/commonjs/component/adapterTest.js +82 -0
- package/dist/commonjs/component/adapterTest.js.map +1 -0
- package/dist/commonjs/component/lib.d.ts +308 -536
- package/dist/commonjs/component/lib.d.ts.map +1 -1
- package/dist/commonjs/component/lib.js +469 -292
- package/dist/commonjs/component/lib.js.map +1 -1
- package/dist/commonjs/component/schema.d.ts +465 -26
- package/dist/commonjs/component/schema.d.ts.map +1 -1
- package/dist/commonjs/component/schema.js +334 -18
- package/dist/commonjs/component/schema.js.map +1 -1
- package/dist/commonjs/component/util.d.ts +944 -68
- package/dist/commonjs/component/util.d.ts.map +1 -1
- package/dist/commonjs/nextjs/index.d.ts.map +1 -1
- package/dist/commonjs/nextjs/index.js +3 -9
- package/dist/commonjs/nextjs/index.js.map +1 -1
- package/dist/commonjs/plugins/convex/index.d.ts +14 -11
- package/dist/commonjs/plugins/convex/index.d.ts.map +1 -1
- package/dist/commonjs/plugins/convex/index.js +3 -2
- package/dist/commonjs/plugins/convex/index.js.map +1 -1
- package/dist/commonjs/plugins/cross-domain/client.d.ts +1 -1
- package/dist/commonjs/plugins/cross-domain/index.d.ts +5 -3
- package/dist/commonjs/plugins/cross-domain/index.d.ts.map +1 -1
- package/dist/commonjs/plugins/cross-domain/index.js +19 -5
- package/dist/commonjs/plugins/cross-domain/index.js.map +1 -1
- package/dist/commonjs/react/client.d.ts +1 -1
- package/dist/commonjs/react/client.d.ts.map +1 -1
- package/dist/commonjs/react/client.js +3 -9
- package/dist/commonjs/react/client.js.map +1 -1
- package/dist/commonjs/react-start/index.d.ts +4 -4
- package/dist/commonjs/react-start/index.d.ts.map +1 -1
- package/dist/commonjs/react-start/index.js +3 -0
- package/dist/commonjs/react-start/index.js.map +1 -1
- package/dist/commonjs/utils/index.d.ts +2 -0
- package/dist/commonjs/utils/index.d.ts.map +1 -0
- package/dist/commonjs/utils/index.js +8 -0
- package/dist/commonjs/utils/index.js.map +1 -0
- package/dist/esm/client/adapter.d.ts +10 -1
- package/dist/esm/client/adapter.d.ts.map +1 -1
- package/dist/esm/client/adapter.js +190 -192
- package/dist/esm/client/adapter.js.map +1 -1
- package/dist/esm/client/index.d.ts +283 -179
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +46 -58
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/component/adapterTest.d.ts +19 -0
- package/dist/esm/component/adapterTest.d.ts.map +1 -0
- package/dist/esm/component/adapterTest.js +82 -0
- package/dist/esm/component/adapterTest.js.map +1 -0
- package/dist/esm/component/lib.d.ts +308 -536
- package/dist/esm/component/lib.d.ts.map +1 -1
- package/dist/esm/component/lib.js +469 -292
- package/dist/esm/component/lib.js.map +1 -1
- package/dist/esm/component/schema.d.ts +465 -26
- package/dist/esm/component/schema.d.ts.map +1 -1
- package/dist/esm/component/schema.js +334 -18
- package/dist/esm/component/schema.js.map +1 -1
- package/dist/esm/component/util.d.ts +944 -68
- package/dist/esm/component/util.d.ts.map +1 -1
- package/dist/esm/nextjs/index.d.ts.map +1 -1
- package/dist/esm/nextjs/index.js +3 -9
- package/dist/esm/nextjs/index.js.map +1 -1
- package/dist/esm/plugins/convex/index.d.ts +14 -11
- package/dist/esm/plugins/convex/index.d.ts.map +1 -1
- package/dist/esm/plugins/convex/index.js +3 -2
- package/dist/esm/plugins/convex/index.js.map +1 -1
- package/dist/esm/plugins/cross-domain/client.d.ts +1 -1
- package/dist/esm/plugins/cross-domain/index.d.ts +5 -3
- package/dist/esm/plugins/cross-domain/index.d.ts.map +1 -1
- package/dist/esm/plugins/cross-domain/index.js +19 -5
- package/dist/esm/plugins/cross-domain/index.js.map +1 -1
- package/dist/esm/react/client.d.ts +1 -1
- package/dist/esm/react/client.d.ts.map +1 -1
- package/dist/esm/react/client.js +3 -9
- package/dist/esm/react/client.js.map +1 -1
- package/dist/esm/react-start/index.d.ts +4 -4
- package/dist/esm/react-start/index.d.ts.map +1 -1
- package/dist/esm/react-start/index.js +3 -0
- package/dist/esm/react-start/index.js.map +1 -1
- package/dist/esm/utils/index.d.ts +2 -0
- package/dist/esm/utils/index.d.ts.map +1 -0
- package/dist/esm/utils/index.js +8 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/package.json +24 -7
- package/src/client/adapter.test.ts +378 -0
- package/src/client/adapter.ts +206 -198
- package/src/client/index.ts +46 -71
- package/src/component/_generated/api.d.ts +2189 -171
- package/src/component/adapterTest.ts +141 -0
- package/src/component/lib.ts +648 -342
- package/src/component/schema.ts +349 -18
- package/src/nextjs/index.ts +3 -14
- package/src/plugins/convex/index.ts +5 -2
- package/src/plugins/cross-domain/index.ts +19 -5
- package/src/react/client.tsx +5 -11
- package/src/react-start/index.ts +4 -1
- package/src/client/cors.ts +0 -425
- /package/src/{util.ts → utils/index.ts} +0 -0
package/src/component/lib.ts
CHANGED
|
@@ -1,74 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
action,
|
|
3
|
-
mutation,
|
|
4
|
-
query,
|
|
5
|
-
QueryCtx,
|
|
6
|
-
} from "../component/_generated/server";
|
|
1
|
+
import { mutation, query, QueryCtx } from "../component/_generated/server";
|
|
7
2
|
import { asyncMap } from "convex-helpers";
|
|
8
|
-
import { v } from "convex/values";
|
|
9
|
-
import { api } from "../component/_generated/api";
|
|
3
|
+
import { Infer, v } from "convex/values";
|
|
10
4
|
import { Doc, Id, TableNames } from "../component/_generated/dataModel";
|
|
11
|
-
import schema from "../component/schema";
|
|
12
|
-
import {
|
|
13
|
-
|
|
5
|
+
import schema, { specialFields } from "../component/schema";
|
|
6
|
+
import {
|
|
7
|
+
PaginationOptions,
|
|
8
|
+
paginationOptsValidator,
|
|
9
|
+
PaginationResult,
|
|
10
|
+
} from "convex/server";
|
|
14
11
|
import { partial } from "convex-helpers/validators";
|
|
12
|
+
import { stream } from "convex-helpers/server/stream";
|
|
13
|
+
import { mergedStream } from "convex-helpers/server/stream";
|
|
14
|
+
import { stripIndent, stripIndents } from "common-tags";
|
|
15
15
|
|
|
16
|
-
export const
|
|
17
|
-
return {
|
|
18
|
-
...Object.fromEntries(
|
|
19
|
-
Object.entries(data).map(([key, value]) => {
|
|
20
|
-
if (value instanceof Date) {
|
|
21
|
-
return [key, value.getTime()];
|
|
22
|
-
}
|
|
23
|
-
return [key, value];
|
|
24
|
-
})
|
|
25
|
-
),
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const transformOutput = (
|
|
30
|
-
{ _id, _creationTime, ...data }: Doc<TableNames>,
|
|
31
|
-
_model: string
|
|
32
|
-
) => {
|
|
33
|
-
// Provide the expected id field, but it can be overwritten if
|
|
34
|
-
// the model has an id field
|
|
35
|
-
return { id: _id, ...data };
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// Get the session via sessionId in jwt claims
|
|
39
|
-
export const getCurrentSession = query({
|
|
40
|
-
args: {},
|
|
41
|
-
handler: async (ctx) => {
|
|
42
|
-
const identity = await ctx.auth.getUserIdentity();
|
|
43
|
-
if (!identity) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
return ctx.db.get(identity.sessionId as Id<"session">);
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
export const getByHelper = async (
|
|
51
|
-
ctx: QueryCtx,
|
|
52
|
-
args: {
|
|
53
|
-
table: string;
|
|
54
|
-
field: string;
|
|
55
|
-
value: any;
|
|
56
|
-
unique?: boolean;
|
|
57
|
-
}
|
|
58
|
-
) => {
|
|
59
|
-
if (args.field === "id") {
|
|
60
|
-
return ctx.db.get(args.value);
|
|
61
|
-
}
|
|
62
|
-
const query = ctx.db
|
|
63
|
-
.query(args.table as any)
|
|
64
|
-
.withIndex(args.field as any, (q) => q.eq(args.field, args.value));
|
|
65
|
-
return args.unique ? await query.unique() : await query.first();
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const getByArgsValidator = {
|
|
69
|
-
table: v.string(),
|
|
16
|
+
export const adapterWhereValidator = v.object({
|
|
70
17
|
field: v.string(),
|
|
71
|
-
|
|
18
|
+
operator: v.optional(
|
|
19
|
+
v.union(
|
|
20
|
+
v.literal("lt"),
|
|
21
|
+
v.literal("lte"),
|
|
22
|
+
v.literal("gt"),
|
|
23
|
+
v.literal("gte"),
|
|
24
|
+
v.literal("eq"),
|
|
25
|
+
v.literal("in"),
|
|
26
|
+
v.literal("ne"),
|
|
27
|
+
v.literal("contains"),
|
|
28
|
+
v.literal("starts_with"),
|
|
29
|
+
v.literal("ends_with")
|
|
30
|
+
)
|
|
31
|
+
),
|
|
72
32
|
value: v.union(
|
|
73
33
|
v.string(),
|
|
74
34
|
v.number(),
|
|
@@ -77,347 +37,693 @@ export const getByArgsValidator = {
|
|
|
77
37
|
v.array(v.number()),
|
|
78
38
|
v.null()
|
|
79
39
|
),
|
|
80
|
-
|
|
40
|
+
connector: v.optional(v.union(v.literal("AND"), v.literal("OR"))),
|
|
41
|
+
});
|
|
81
42
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
43
|
+
export const adapterArgsValidator = v.object({
|
|
44
|
+
model: v.string(),
|
|
45
|
+
where: v.optional(v.array(adapterWhereValidator)),
|
|
46
|
+
sortBy: v.optional(
|
|
47
|
+
v.object({
|
|
48
|
+
field: v.string(),
|
|
49
|
+
direction: v.union(v.literal("asc"), v.literal("desc")),
|
|
50
|
+
})
|
|
51
|
+
),
|
|
52
|
+
select: v.optional(v.array(v.string())),
|
|
53
|
+
limit: v.optional(v.number()),
|
|
54
|
+
offset: v.optional(v.number()),
|
|
55
|
+
unique: v.optional(v.boolean()),
|
|
92
56
|
});
|
|
93
|
-
export { getByQuery as getBy };
|
|
94
57
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
58
|
+
const isUniqueField = (model: TableNames, field: string) => {
|
|
59
|
+
const fields = specialFields[model as keyof typeof specialFields];
|
|
60
|
+
if (!fields) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return Object.entries(fields)
|
|
64
|
+
.filter(([, value]) => value.unique)
|
|
65
|
+
.map(([key]) => key)
|
|
66
|
+
.includes(field);
|
|
67
|
+
};
|
|
68
|
+
const hasUniqueFields = (model: TableNames, input: Record<string, any>) => {
|
|
69
|
+
for (const field of Object.keys(input)) {
|
|
70
|
+
if (isUniqueField(model, field)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const checkUniqueFields = async (
|
|
78
|
+
ctx: QueryCtx,
|
|
79
|
+
table: TableNames,
|
|
80
|
+
input: Record<string, any>,
|
|
81
|
+
doc?: Doc<any>
|
|
82
|
+
) => {
|
|
83
|
+
if (!hasUniqueFields(table, input)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
for (const field of Object.keys(input)) {
|
|
87
|
+
if (!isUniqueField(table, field)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const existingDoc = await ctx.db
|
|
91
|
+
.query(table as any)
|
|
92
|
+
.withIndex(field as any, (q) =>
|
|
93
|
+
q.eq(field, input[field as keyof typeof input])
|
|
103
94
|
)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const { table, ...input } = args.input;
|
|
108
|
-
const id = await ctx.db.insert(table as any, {
|
|
109
|
-
...input,
|
|
110
|
-
});
|
|
111
|
-
const doc = await ctx.db.get(id);
|
|
112
|
-
if (!doc) {
|
|
113
|
-
throw new Error(`Failed to create ${table}`);
|
|
95
|
+
.unique();
|
|
96
|
+
if (existingDoc && existingDoc._id !== doc?._id) {
|
|
97
|
+
throw new Error(`${table} ${field} already exists`);
|
|
114
98
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
118
101
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
102
|
+
const findIndex = (args: {
|
|
103
|
+
model: string;
|
|
104
|
+
where?: {
|
|
105
|
+
field: string;
|
|
106
|
+
operator?:
|
|
107
|
+
| "lt"
|
|
108
|
+
| "lte"
|
|
109
|
+
| "gt"
|
|
110
|
+
| "gte"
|
|
111
|
+
| "eq"
|
|
112
|
+
| "in"
|
|
113
|
+
| "ne"
|
|
114
|
+
| "contains"
|
|
115
|
+
| "starts_with"
|
|
116
|
+
| "ends_with";
|
|
117
|
+
value: string | number | boolean | null | string[] | number[];
|
|
118
|
+
connector?: "AND" | "OR";
|
|
119
|
+
}[];
|
|
120
|
+
sortBy?: {
|
|
121
|
+
field: string;
|
|
122
|
+
direction: "asc" | "desc";
|
|
123
|
+
};
|
|
124
|
+
}) => {
|
|
125
|
+
if (
|
|
126
|
+
(args.where?.length ?? 0) > 1 &&
|
|
127
|
+
args.where?.some((w) => w.connector === "OR")
|
|
128
|
+
) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`OR connector not supported with multiple where statements in findIndex, split up the where statements before calling findIndex: ${JSON.stringify(args.where)}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const where = args.where?.filter((w) => {
|
|
134
|
+
return (
|
|
135
|
+
(!w.operator ||
|
|
136
|
+
["lt", "lte", "gt", "gte", "eq", "in"].includes(w.operator)) &&
|
|
137
|
+
w.field !== "id"
|
|
138
|
+
);
|
|
124
139
|
});
|
|
140
|
+
if (!where?.length && !args.sortBy) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const lowerBounds =
|
|
144
|
+
where?.filter((w) => w.operator === "lt" || w.operator === "lte") ?? [];
|
|
145
|
+
if (lowerBounds.length > 1) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`cannot have more than one lower bound where clause: ${JSON.stringify(where)}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const upperBounds =
|
|
151
|
+
where?.filter((w) => w.operator === "gt" || w.operator === "gte") ?? [];
|
|
152
|
+
if (upperBounds.length > 1) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`cannot have more than one upper bound where clause: ${JSON.stringify(where)}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const lowerBound = lowerBounds[0];
|
|
158
|
+
const upperBound = upperBounds[0];
|
|
159
|
+
if (lowerBound && upperBound && lowerBound.field !== upperBound.field) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`lower bound and upper bound must have the same field: ${JSON.stringify(where)}`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const boundField = lowerBound?.field || upperBound?.field;
|
|
165
|
+
if (
|
|
166
|
+
boundField &&
|
|
167
|
+
where?.some(
|
|
168
|
+
(w) => w.field === boundField && w !== lowerBound && w !== upperBound
|
|
169
|
+
)
|
|
170
|
+
) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`too many where clauses on the bound field: ${JSON.stringify(where)}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const indexEqFields =
|
|
176
|
+
where
|
|
177
|
+
?.filter((w) => !w.operator || w.operator === "eq")
|
|
178
|
+
.sort((a, b) => {
|
|
179
|
+
return a.field.localeCompare(b.field);
|
|
180
|
+
})
|
|
181
|
+
.map((w) => [w.field, w.value]) ?? [];
|
|
182
|
+
if (!indexEqFields?.length && !boundField && !args.sortBy) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const indexes =
|
|
186
|
+
schema.tables[args.model as keyof typeof schema.tables][" indexes"]();
|
|
187
|
+
const sortField = args.sortBy?.field;
|
|
188
|
+
|
|
189
|
+
// We internally use _creationTime in place of Better Auth's createdAt
|
|
190
|
+
const indexFields = indexEqFields
|
|
191
|
+
.map(([field]) => field)
|
|
192
|
+
.concat(
|
|
193
|
+
boundField && boundField !== "createdAt"
|
|
194
|
+
? `${indexEqFields.length ? "_" : ""}${boundField}`
|
|
195
|
+
: ""
|
|
196
|
+
)
|
|
197
|
+
.concat(
|
|
198
|
+
sortField && sortField !== "createdAt" && boundField !== sortField
|
|
199
|
+
? `${indexEqFields.length || boundField ? "_" : ""}${sortField}`
|
|
200
|
+
: ""
|
|
201
|
+
)
|
|
202
|
+
.filter(Boolean);
|
|
203
|
+
if (!indexFields.length && !boundField && !sortField) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Use the built in _creationTime index if bounding or sorting by createdAt
|
|
207
|
+
// with no other fields
|
|
208
|
+
const index = !indexFields.length
|
|
209
|
+
? {
|
|
210
|
+
indexDescriptor: "by_creation_time",
|
|
211
|
+
fields: [],
|
|
212
|
+
}
|
|
213
|
+
: indexes.find(({ fields }) => {
|
|
214
|
+
const fieldsMatch = indexFields.every(
|
|
215
|
+
(field, idx) => field === fields[idx]
|
|
216
|
+
);
|
|
217
|
+
// If sorting by createdAt, no intermediate fields can be on the index
|
|
218
|
+
// as they may override the createdAt sort order.
|
|
219
|
+
const boundFieldMatch =
|
|
220
|
+
boundField === "createdAt" || sortField === "createdAt"
|
|
221
|
+
? indexFields.length === fields.length
|
|
222
|
+
: true;
|
|
223
|
+
return fieldsMatch && boundFieldMatch;
|
|
224
|
+
});
|
|
225
|
+
if (!index) {
|
|
226
|
+
return { indexFields };
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
index: {
|
|
230
|
+
indexDescriptor: index.indexDescriptor,
|
|
231
|
+
fields: [...index.fields, "_creationTime"],
|
|
232
|
+
},
|
|
233
|
+
boundField,
|
|
234
|
+
sortField,
|
|
235
|
+
values: {
|
|
236
|
+
eq: indexEqFields.map(([, value]) => value),
|
|
237
|
+
lt: lowerBound?.operator === "lt" ? lowerBound.value : undefined,
|
|
238
|
+
lte: lowerBound?.operator === "lte" ? lowerBound.value : undefined,
|
|
239
|
+
gt: upperBound?.operator === "gt" ? upperBound.value : undefined,
|
|
240
|
+
gte: upperBound?.operator === "gte" ? upperBound.value : undefined,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
125
243
|
};
|
|
126
244
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
245
|
+
const selectFields = <T extends TableNames, D extends Doc<T>>(
|
|
246
|
+
doc: D | null,
|
|
247
|
+
select?: string[]
|
|
248
|
+
) => {
|
|
249
|
+
if (!doc) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
if (!select?.length) {
|
|
253
|
+
return doc;
|
|
254
|
+
}
|
|
255
|
+
return select.reduce((acc, field) => {
|
|
256
|
+
(acc as any)[field] = doc[field];
|
|
257
|
+
return acc;
|
|
258
|
+
}, {} as D);
|
|
134
259
|
};
|
|
135
260
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
261
|
+
// Manually filter an individual document by where clauses. This is used to
|
|
262
|
+
// simplify queries that can only return 0 or 1 documents, or "in" clauses that
|
|
263
|
+
// query multiple single documents in parallel.
|
|
264
|
+
const filterByWhere = (
|
|
265
|
+
doc: Doc<any>,
|
|
266
|
+
where?: Infer<typeof adapterWhereValidator>[],
|
|
267
|
+
filterWhere?: (w: Infer<typeof adapterWhereValidator>) => any
|
|
268
|
+
) => {
|
|
269
|
+
if (!doc) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
for (const w of where ?? []) {
|
|
273
|
+
if (filterWhere && !filterWhere(w)) {
|
|
274
|
+
continue;
|
|
146
275
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
276
|
+
const value = doc[w.field as keyof typeof doc] as Infer<
|
|
277
|
+
typeof adapterWhereValidator
|
|
278
|
+
>["value"];
|
|
279
|
+
const isLessThan = (val: typeof value, wVal: typeof w.value) => {
|
|
280
|
+
if (!wVal) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
if (!val) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
return val < wVal;
|
|
287
|
+
};
|
|
288
|
+
const isGreaterThan = (val: typeof value, wVal: typeof w.value) => {
|
|
289
|
+
if (!val) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
if (!wVal) {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
return val > wVal;
|
|
296
|
+
};
|
|
297
|
+
switch (w.operator) {
|
|
298
|
+
case undefined:
|
|
299
|
+
case "eq": {
|
|
300
|
+
return value === w.value;
|
|
301
|
+
}
|
|
302
|
+
case "in": {
|
|
303
|
+
return Array.isArray(w.value) && (w.value as any[]).includes(value);
|
|
304
|
+
}
|
|
305
|
+
case "lt": {
|
|
306
|
+
return isLessThan(value, w.value);
|
|
307
|
+
}
|
|
308
|
+
case "lte": {
|
|
309
|
+
return value === w.value || isLessThan(value, w.value);
|
|
310
|
+
}
|
|
311
|
+
case "gt": {
|
|
312
|
+
return isGreaterThan(value, w.value);
|
|
313
|
+
}
|
|
314
|
+
case "gte": {
|
|
315
|
+
return value === w.value || isGreaterThan(value, w.value);
|
|
316
|
+
}
|
|
317
|
+
case "ne": {
|
|
318
|
+
return value !== w.value;
|
|
319
|
+
}
|
|
320
|
+
case "contains": {
|
|
321
|
+
return typeof value === "string" && value.includes(w.value as string);
|
|
322
|
+
}
|
|
323
|
+
case "starts_with": {
|
|
324
|
+
return typeof value === "string" && value.startsWith(w.value as string);
|
|
325
|
+
}
|
|
326
|
+
case "ends_with": {
|
|
327
|
+
return typeof value === "string" && value.endsWith(w.value as string);
|
|
328
|
+
}
|
|
151
329
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
};
|
|
155
333
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
334
|
+
const generateQuery = (
|
|
335
|
+
ctx: QueryCtx,
|
|
336
|
+
args: Infer<typeof adapterArgsValidator>
|
|
337
|
+
) => {
|
|
338
|
+
const { index, values, boundField, indexFields } = findIndex(args) ?? {};
|
|
339
|
+
const query = stream(ctx.db, schema).query(args.model as any);
|
|
340
|
+
const hasValues =
|
|
341
|
+
values?.eq?.length ||
|
|
342
|
+
values?.lt ||
|
|
343
|
+
values?.lte ||
|
|
344
|
+
values?.gt ||
|
|
345
|
+
values?.gte;
|
|
346
|
+
const indexedQuery =
|
|
347
|
+
index && index.indexDescriptor !== "by_creation_time"
|
|
348
|
+
? query.withIndex(
|
|
349
|
+
index.indexDescriptor,
|
|
350
|
+
hasValues
|
|
351
|
+
? (q: any) => {
|
|
352
|
+
for (const [idx, value] of (values?.eq ?? []).entries()) {
|
|
353
|
+
q = q.eq(index.fields[idx], value);
|
|
354
|
+
}
|
|
355
|
+
if (values?.lt) {
|
|
356
|
+
q = q.lt(boundField, values.lt);
|
|
357
|
+
}
|
|
358
|
+
if (values?.lte) {
|
|
359
|
+
q = q.lte(boundField, values.lte);
|
|
360
|
+
}
|
|
361
|
+
if (values?.gt) {
|
|
362
|
+
q = q.gt(boundField, values.gt);
|
|
363
|
+
}
|
|
364
|
+
if (values?.gte) {
|
|
365
|
+
q = q.gte(boundField, values.gte);
|
|
366
|
+
}
|
|
367
|
+
return q;
|
|
368
|
+
}
|
|
369
|
+
: undefined
|
|
370
|
+
)
|
|
371
|
+
: query;
|
|
372
|
+
const orderedQuery = args.sortBy
|
|
373
|
+
? indexedQuery.order(args.sortBy.direction === "asc" ? "asc" : "desc")
|
|
374
|
+
: indexedQuery;
|
|
375
|
+
const filteredQuery = orderedQuery.filterWith(async (doc) => {
|
|
376
|
+
if (!index && indexFields?.length) {
|
|
377
|
+
console.warn(
|
|
378
|
+
stripIndent`
|
|
379
|
+
Querying without an index on table "${args.model}".
|
|
380
|
+
This can cause performance issues, and may hit the document read limit.
|
|
381
|
+
To fix, add an index that begins with the following fields in order:
|
|
382
|
+
[${indexFields.join(", ")}]
|
|
383
|
+
`
|
|
384
|
+
);
|
|
385
|
+
return filterByWhere(doc, args.where);
|
|
162
386
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
387
|
+
return filterByWhere(
|
|
388
|
+
doc,
|
|
389
|
+
args.where,
|
|
390
|
+
(w) =>
|
|
391
|
+
w.operator &&
|
|
392
|
+
["contains", "starts_with", "ends_with", "ne"].includes(w.operator)
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
return filteredQuery;
|
|
396
|
+
};
|
|
169
397
|
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
398
|
+
// This is the core function for reading from the database, it parses and
|
|
399
|
+
// validates where conditions, selects indexes, and allows the caller to
|
|
400
|
+
// optionally paginate as needed. Every response is a pagination result.
|
|
401
|
+
const paginate = async (
|
|
402
|
+
ctx: QueryCtx,
|
|
403
|
+
args: Infer<typeof adapterArgsValidator> & {
|
|
404
|
+
paginationOpts: PaginationOptions;
|
|
405
|
+
}
|
|
406
|
+
): Promise<PaginationResult<Doc<any>>> => {
|
|
407
|
+
if (args.offset) {
|
|
408
|
+
throw new Error(`offset not supported: ${JSON.stringify(args.offset)}`);
|
|
409
|
+
}
|
|
410
|
+
if (args.where?.some((w) => w.connector === "OR") && args.where?.length > 1) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`OR connector not supported with multiple where statements in paginate, split up the where statements before calling paginate: ${JSON.stringify(args.where)}`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
if (
|
|
416
|
+
args.where?.some(
|
|
417
|
+
(w) =>
|
|
418
|
+
w.field === "id" && w.operator && !["eq", "in"].includes(w.operator)
|
|
419
|
+
)
|
|
420
|
+
) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
`id can only be used with eq or in operator: ${JSON.stringify(args.where)}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
// If any where clause is "eq" (or missing operator) on a unique field,
|
|
426
|
+
// we can only return a single document, so we get it and use any other
|
|
427
|
+
// where clauses as static filters.
|
|
428
|
+
const uniqueWhere = args.where?.find(
|
|
429
|
+
(w) =>
|
|
430
|
+
(!w.operator || w.operator === "eq") &&
|
|
431
|
+
(isUniqueField(args.model as TableNames, w.field) || w.field === "id")
|
|
432
|
+
);
|
|
433
|
+
if (uniqueWhere) {
|
|
434
|
+
const doc =
|
|
435
|
+
uniqueWhere.field === "id"
|
|
436
|
+
? await ctx.db.get(uniqueWhere.value as Id<TableNames>)
|
|
437
|
+
: await ctx.db
|
|
438
|
+
.query(args.model as any)
|
|
439
|
+
.withIndex(uniqueWhere.field as any, (q) =>
|
|
440
|
+
q.eq(uniqueWhere.field, uniqueWhere.value)
|
|
441
|
+
)
|
|
442
|
+
.unique();
|
|
183
443
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
444
|
+
if (filterByWhere(doc, args.where, (w) => w !== uniqueWhere)) {
|
|
445
|
+
return {
|
|
446
|
+
page: [selectFields(doc, args.select)].filter(Boolean),
|
|
447
|
+
isDone: true,
|
|
448
|
+
continueCursor: "",
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
page: [],
|
|
453
|
+
isDone: true,
|
|
454
|
+
continueCursor: "",
|
|
455
|
+
};
|
|
456
|
+
}
|
|
196
457
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const docs = args.limit
|
|
204
|
-
? await query.take(args.limit)
|
|
205
|
-
: await query.collect();
|
|
206
|
-
return docs.map((doc) => transformOutput(doc, "jwks"));
|
|
207
|
-
},
|
|
208
|
-
});
|
|
458
|
+
const paginationOpts = {
|
|
459
|
+
...args.paginationOpts,
|
|
460
|
+
// If maximumRowsRead is not at least 1 higher than numItems, bad cursors
|
|
461
|
+
// and incorrect paging will result (at least with convex-test).
|
|
462
|
+
maximumRowsRead: Math.max((args.paginationOpts.numItems ?? 0) + 1, 200),
|
|
463
|
+
};
|
|
209
464
|
|
|
210
|
-
|
|
465
|
+
// Large queries using "in" clause will crash, but these are only currently
|
|
466
|
+
// possible with the organization plugin listing all members with a high
|
|
467
|
+
// limit. For cases like this we need to create proper convex queries in
|
|
468
|
+
// the component as an alternative to using Better Auth api's.
|
|
469
|
+
const inWhere = args.where?.find((w) => w.operator === "in");
|
|
470
|
+
if (inWhere) {
|
|
471
|
+
if (!Array.isArray(inWhere.value)) {
|
|
472
|
+
throw new Error("in clause value must be an array");
|
|
473
|
+
}
|
|
474
|
+
// For ids, just use asyncMap + .get()
|
|
475
|
+
if (inWhere.field === "id") {
|
|
476
|
+
const docs = await asyncMap(inWhere.value as any[], async (value) => {
|
|
477
|
+
return ctx.db.get(value as Id<TableNames>);
|
|
478
|
+
});
|
|
479
|
+
const filteredDocs = docs
|
|
480
|
+
.flatMap((doc) => doc || [])
|
|
481
|
+
.filter((doc) => filterByWhere(doc, args.where, (w) => w !== inWhere));
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
page: filteredDocs.sort((a, b) => {
|
|
485
|
+
if (args.sortBy?.field === "createdAt") {
|
|
486
|
+
return args.sortBy.direction === "asc"
|
|
487
|
+
? a._creationTime - b._creationTime
|
|
488
|
+
: b._creationTime - a._creationTime;
|
|
489
|
+
}
|
|
490
|
+
if (args.sortBy) {
|
|
491
|
+
const aValue = a[args.sortBy.field as keyof typeof a];
|
|
492
|
+
const bValue = b[args.sortBy.field as keyof typeof b];
|
|
493
|
+
if (aValue === bValue) {
|
|
494
|
+
return 0;
|
|
495
|
+
}
|
|
496
|
+
return args.sortBy.direction === "asc"
|
|
497
|
+
? aValue > bValue
|
|
498
|
+
? 1
|
|
499
|
+
: -1
|
|
500
|
+
: aValue > bValue
|
|
501
|
+
? -1
|
|
502
|
+
: 1;
|
|
503
|
+
}
|
|
504
|
+
return 0;
|
|
505
|
+
}),
|
|
506
|
+
isDone: true,
|
|
507
|
+
continueCursor: "",
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
const streams = inWhere.value.map((value) => {
|
|
511
|
+
return generateQuery(ctx, {
|
|
512
|
+
...args,
|
|
513
|
+
where: args.where?.map((w) => {
|
|
514
|
+
if (w === inWhere) {
|
|
515
|
+
return { ...w, operator: "eq", value };
|
|
516
|
+
}
|
|
517
|
+
return w;
|
|
518
|
+
}),
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
const result = await mergedStream(
|
|
522
|
+
streams,
|
|
523
|
+
[
|
|
524
|
+
args.sortBy?.field !== "createdAt" && args.sortBy?.field,
|
|
525
|
+
"_creationTime",
|
|
526
|
+
].flatMap((f) => (f ? [f] : []))
|
|
527
|
+
)
|
|
528
|
+
.filterWith(async (doc) =>
|
|
529
|
+
filterByWhere(
|
|
530
|
+
doc,
|
|
531
|
+
args.where,
|
|
532
|
+
(w) =>
|
|
533
|
+
w.operator &&
|
|
534
|
+
["contains", "starts_with", "ends_with", "ne"].includes(w.operator)
|
|
535
|
+
)
|
|
536
|
+
)
|
|
537
|
+
.paginate(paginationOpts);
|
|
538
|
+
return {
|
|
539
|
+
...result,
|
|
540
|
+
page: result.page.map((doc) => selectFields(doc, args.select)),
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const query = generateQuery(ctx, args);
|
|
545
|
+
const result = await query.paginate(paginationOpts);
|
|
546
|
+
return {
|
|
547
|
+
...result,
|
|
548
|
+
page: result.page.map((doc) => selectFields(doc, args.select)),
|
|
549
|
+
};
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const listOne = async (
|
|
553
|
+
ctx: QueryCtx,
|
|
554
|
+
args: Infer<typeof adapterArgsValidator>
|
|
555
|
+
): Promise<Doc<any> | null> => {
|
|
556
|
+
return (
|
|
557
|
+
await paginate(ctx, {
|
|
558
|
+
...args,
|
|
559
|
+
paginationOpts: {
|
|
560
|
+
numItems: 1,
|
|
561
|
+
cursor: null,
|
|
562
|
+
},
|
|
563
|
+
})
|
|
564
|
+
).page[0];
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
export const create = mutation({
|
|
211
568
|
args: {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
569
|
+
input: v.union(
|
|
570
|
+
...Object.entries(schema.tables).map(([model, table]) =>
|
|
571
|
+
v.object({
|
|
572
|
+
model: v.literal(model),
|
|
573
|
+
where: v.optional(v.array(adapterWhereValidator)),
|
|
574
|
+
data: v.object(table.validator.fields),
|
|
575
|
+
})
|
|
576
|
+
)
|
|
218
577
|
),
|
|
219
|
-
limit: v.optional(v.number()),
|
|
220
578
|
},
|
|
221
579
|
handler: async (ctx, args) => {
|
|
222
|
-
|
|
223
|
-
|
|
580
|
+
await checkUniqueFields(
|
|
581
|
+
ctx,
|
|
582
|
+
args.input.model as TableNames,
|
|
583
|
+
args.input.data
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
const id = await ctx.db.insert(args.input.model as any, args.input.data);
|
|
587
|
+
const doc = await ctx.db.get(id);
|
|
588
|
+
if (!doc) {
|
|
589
|
+
throw new Error(`Failed to create ${args.input.model}`);
|
|
224
590
|
}
|
|
225
|
-
|
|
226
|
-
.query("verification")
|
|
227
|
-
.withIndex("identifier", (q) => q.eq("identifier", args.identifier))
|
|
228
|
-
.order(
|
|
229
|
-
args.sortBy?.field === "createdAt" && args.sortBy?.direction
|
|
230
|
-
? args.sortBy.direction
|
|
231
|
-
: "asc"
|
|
232
|
-
);
|
|
233
|
-
const docs = args.limit
|
|
234
|
-
? await query.take(args.limit)
|
|
235
|
-
: await query.collect();
|
|
236
|
-
return docs.map((doc) => transformOutput(doc, "verification"));
|
|
591
|
+
return doc;
|
|
237
592
|
},
|
|
238
593
|
});
|
|
239
594
|
|
|
240
|
-
export const
|
|
241
|
-
args:
|
|
242
|
-
currentTimestamp: v.number(),
|
|
243
|
-
paginationOpts: v.optional(paginationOptsValidator),
|
|
244
|
-
},
|
|
595
|
+
export const findOne = query({
|
|
596
|
+
args: adapterArgsValidator,
|
|
245
597
|
handler: async (ctx, args) => {
|
|
246
|
-
|
|
247
|
-
numItems: 500,
|
|
248
|
-
cursor: null,
|
|
249
|
-
};
|
|
250
|
-
const { page, ...result } = await paginator(ctx.db, schema)
|
|
251
|
-
.query("verification")
|
|
252
|
-
.withIndex("expiresAt", (q) => q.lt("expiresAt", args.currentTimestamp))
|
|
253
|
-
.paginate(paginationOpts);
|
|
254
|
-
await asyncMap(page, async (doc) => {
|
|
255
|
-
await ctx.db.delete(doc._id);
|
|
256
|
-
});
|
|
257
|
-
return { ...result, count: page.length };
|
|
598
|
+
return await listOne(ctx, args);
|
|
258
599
|
},
|
|
259
600
|
});
|
|
260
601
|
|
|
261
|
-
export const
|
|
602
|
+
export const findMany = query({
|
|
262
603
|
args: {
|
|
263
|
-
|
|
604
|
+
...adapterArgsValidator.fields,
|
|
605
|
+
paginationOpts: paginationOptsValidator,
|
|
264
606
|
},
|
|
265
607
|
handler: async (ctx, args) => {
|
|
266
|
-
|
|
267
|
-
let cursor = null;
|
|
268
|
-
let isDone = false;
|
|
269
|
-
do {
|
|
270
|
-
const result: Omit<PaginationResult<Doc<"verification">>, "page"> & {
|
|
271
|
-
count: number;
|
|
272
|
-
} = await ctx.runMutation(api.lib.deleteOldVerificationsPage, {
|
|
273
|
-
currentTimestamp: args.currentTimestamp,
|
|
274
|
-
paginationOpts: {
|
|
275
|
-
numItems: 500,
|
|
276
|
-
cursor,
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
count += result.count;
|
|
280
|
-
cursor =
|
|
281
|
-
result.pageStatus &&
|
|
282
|
-
result.splitCursor &&
|
|
283
|
-
["SplitRecommended", "SplitRequired"].includes(result.pageStatus)
|
|
284
|
-
? result.splitCursor
|
|
285
|
-
: result.continueCursor;
|
|
286
|
-
isDone = result.isDone;
|
|
287
|
-
} while (!isDone);
|
|
288
|
-
return count;
|
|
608
|
+
return await paginate(ctx, args);
|
|
289
609
|
},
|
|
290
610
|
});
|
|
291
611
|
|
|
292
|
-
export const
|
|
612
|
+
export const updateOne = mutation({
|
|
293
613
|
args: {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
q.eq("userId", args.userId).lt("expiresAt", args.expiresAt)
|
|
614
|
+
input: v.union(
|
|
615
|
+
...Object.entries(schema.tables).map(([model, table]) =>
|
|
616
|
+
v.object({
|
|
617
|
+
model: v.literal(model),
|
|
618
|
+
where: v.optional(v.array(adapterWhereValidator)),
|
|
619
|
+
update: v.object(partial(table.validator.fields)),
|
|
620
|
+
})
|
|
302
621
|
)
|
|
303
|
-
|
|
304
|
-
await asyncMap(docs, async (doc) => {
|
|
305
|
-
await ctx.db.delete(doc._id);
|
|
306
|
-
});
|
|
307
|
-
return docs.length;
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
export const deleteAllForUserPage = mutation({
|
|
312
|
-
args: {
|
|
313
|
-
table: v.string(),
|
|
314
|
-
userId: v.string(),
|
|
315
|
-
paginationOpts: v.optional(paginationOptsValidator),
|
|
622
|
+
),
|
|
316
623
|
},
|
|
317
624
|
handler: async (ctx, args) => {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
.
|
|
325
|
-
.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
625
|
+
const doc = await listOne(ctx, args.input);
|
|
626
|
+
if (!doc) {
|
|
627
|
+
throw new Error(`Failed to update ${args.input.model}`);
|
|
628
|
+
}
|
|
629
|
+
await checkUniqueFields(
|
|
630
|
+
ctx,
|
|
631
|
+
args.input.model as TableNames,
|
|
632
|
+
args.input.update,
|
|
633
|
+
doc
|
|
634
|
+
);
|
|
635
|
+
await ctx.db.patch(doc._id, args.input.update as any);
|
|
636
|
+
const updatedDoc = await ctx.db.get(doc._id);
|
|
637
|
+
if (!updatedDoc) {
|
|
638
|
+
throw new Error(`Failed to update ${args.input.model}`);
|
|
639
|
+
}
|
|
640
|
+
return updatedDoc;
|
|
330
641
|
},
|
|
331
642
|
});
|
|
332
643
|
|
|
333
|
-
export const
|
|
644
|
+
export const updateMany = mutation({
|
|
334
645
|
args: {
|
|
335
|
-
|
|
336
|
-
|
|
646
|
+
input: v.union(
|
|
647
|
+
...Object.entries(schema.tables).map(([model, table]) =>
|
|
648
|
+
v.object({
|
|
649
|
+
...adapterArgsValidator.fields,
|
|
650
|
+
model: v.literal(model),
|
|
651
|
+
where: v.optional(v.array(adapterWhereValidator)),
|
|
652
|
+
update: v.object(partial(table.validator.fields)),
|
|
653
|
+
paginationOpts: paginationOptsValidator,
|
|
654
|
+
})
|
|
655
|
+
)
|
|
656
|
+
),
|
|
337
657
|
},
|
|
338
658
|
handler: async (ctx, args) => {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
659
|
+
const { page, ...result } = await paginate(ctx, args.input);
|
|
660
|
+
if (args.input.update) {
|
|
661
|
+
if (
|
|
662
|
+
hasUniqueFields(
|
|
663
|
+
args.input.model as TableNames,
|
|
664
|
+
args.input.update ?? {}
|
|
665
|
+
) &&
|
|
666
|
+
page.length > 1
|
|
667
|
+
) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
`Attempted to set unique fields in multiple documents in ${args.input.model} with the same value. Fields: ${Object.keys(args.input.update ?? {}).join(", ")}`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
await asyncMap(page, async (doc) => {
|
|
673
|
+
await checkUniqueFields(
|
|
674
|
+
ctx,
|
|
675
|
+
args.input.model as TableNames,
|
|
676
|
+
args.input.update ?? {},
|
|
677
|
+
doc
|
|
678
|
+
);
|
|
679
|
+
await ctx.db.patch(doc._id, args.input.update as any);
|
|
352
680
|
});
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
? result.splitCursor
|
|
359
|
-
: result.continueCursor;
|
|
360
|
-
isDone = result.isDone;
|
|
361
|
-
} while (!isDone);
|
|
362
|
-
return count;
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
...result,
|
|
684
|
+
count: page.length,
|
|
685
|
+
};
|
|
363
686
|
},
|
|
364
687
|
});
|
|
365
688
|
|
|
366
|
-
export const
|
|
367
|
-
args:
|
|
689
|
+
export const deleteOne = mutation({
|
|
690
|
+
args: adapterArgsValidator,
|
|
368
691
|
handler: async (ctx, args) => {
|
|
369
|
-
const doc = await ctx
|
|
370
|
-
.query("account")
|
|
371
|
-
.withIndex("providerId_accountId", (q) =>
|
|
372
|
-
q.eq("providerId", args.providerId).eq("accountId", args.accountId)
|
|
373
|
-
)
|
|
374
|
-
.unique();
|
|
692
|
+
const doc = await listOne(ctx, args);
|
|
375
693
|
if (!doc) {
|
|
376
694
|
return;
|
|
377
695
|
}
|
|
378
|
-
|
|
696
|
+
await ctx.db.delete(doc._id);
|
|
697
|
+
return doc;
|
|
379
698
|
},
|
|
380
699
|
});
|
|
381
700
|
|
|
382
|
-
export const
|
|
701
|
+
export const deleteMany = mutation({
|
|
383
702
|
args: {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
update: v.object(partial(schema.tables.account.validator.fields)),
|
|
703
|
+
...adapterArgsValidator.fields,
|
|
704
|
+
paginationOpts: paginationOptsValidator,
|
|
387
705
|
},
|
|
388
706
|
handler: async (ctx, args) => {
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
.
|
|
392
|
-
q.eq("userId", args.userId).eq("providerId", args.providerId)
|
|
393
|
-
)
|
|
394
|
-
.collect();
|
|
395
|
-
if (docs.length === 0) {
|
|
396
|
-
return 0;
|
|
397
|
-
}
|
|
398
|
-
await asyncMap(docs, async (doc) => {
|
|
399
|
-
await ctx.db.patch(doc._id, args.update);
|
|
707
|
+
const { page, ...result } = await paginate(ctx, args);
|
|
708
|
+
await asyncMap(page, async (doc) => {
|
|
709
|
+
await ctx.db.delete(doc._id);
|
|
400
710
|
});
|
|
401
|
-
return
|
|
711
|
+
return {
|
|
712
|
+
...result,
|
|
713
|
+
count: page.length,
|
|
714
|
+
};
|
|
402
715
|
},
|
|
403
716
|
});
|
|
404
717
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
.withIndex("userId", (q) => q.eq("userId", args.userId))
|
|
414
|
-
.collect();
|
|
415
|
-
if (docs.length === 0) {
|
|
416
|
-
return 0;
|
|
718
|
+
// Get the session via sessionId in jwt claims
|
|
719
|
+
// TODO: this needs a refresh, subquery only necessary for actions
|
|
720
|
+
export const getCurrentSession = query({
|
|
721
|
+
args: {},
|
|
722
|
+
handler: async (ctx) => {
|
|
723
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
724
|
+
if (!identity) {
|
|
725
|
+
return null;
|
|
417
726
|
}
|
|
418
|
-
|
|
419
|
-
await ctx.db.patch(doc._id, args.update);
|
|
420
|
-
});
|
|
421
|
-
return docs.length;
|
|
727
|
+
return ctx.db.get(identity.sessionId as Id<"session">);
|
|
422
728
|
},
|
|
423
729
|
});
|