@convex-dev/better-auth 0.7.0-alpha.10 → 0.7.0-alpha.12
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 +183 -192
- package/dist/commonjs/client/adapter.js.map +1 -1
- package/dist/commonjs/client/index.d.ts +238 -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 +218 -548
- package/dist/commonjs/component/lib.d.ts.map +1 -1
- package/dist/commonjs/component/lib.js +315 -286
- package/dist/commonjs/component/lib.js.map +1 -1
- package/dist/commonjs/component/schema.d.ts +90 -28
- package/dist/commonjs/component/schema.d.ts.map +1 -1
- package/dist/commonjs/component/schema.js +76 -18
- package/dist/commonjs/component/schema.js.map +1 -1
- package/dist/commonjs/component/util.d.ts +148 -86
- 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-start/index.d.ts +4 -4
- 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 +183 -192
- package/dist/esm/client/adapter.js.map +1 -1
- package/dist/esm/client/index.d.ts +238 -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 +218 -548
- package/dist/esm/component/lib.d.ts.map +1 -1
- package/dist/esm/component/lib.js +315 -286
- package/dist/esm/component/lib.js.map +1 -1
- package/dist/esm/component/schema.d.ts +90 -28
- package/dist/esm/component/schema.d.ts.map +1 -1
- package/dist/esm/component/schema.js +76 -18
- package/dist/esm/component/schema.js.map +1 -1
- package/dist/esm/component/util.d.ts +148 -86
- 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-start/index.d.ts +4 -4
- 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 +20 -5
- package/src/client/adapter.test.ts +144 -0
- package/src/client/adapter.ts +191 -195
- package/src/client/index.ts +46 -71
- package/src/component/_generated/api.d.ts +605 -149
- package/src/component/adapterTest.ts +141 -0
- package/src/component/lib.ts +444 -335
- package/src/component/schema.ts +81 -19
- 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/client/cors.ts +0 -425
- /package/src/{util.ts → utils/index.ts} +0 -0
package/src/component/lib.ts
CHANGED
|
@@ -1,423 +1,532 @@
|
|
|
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 {
|
|
5
|
+
import schema, { specialFields } from "../component/schema";
|
|
6
|
+
import {
|
|
7
|
+
PaginationOptions,
|
|
8
|
+
paginationOptsValidator,
|
|
9
|
+
PaginationResult,
|
|
10
|
+
} from "convex/server";
|
|
13
11
|
import { paginator } from "convex-helpers/server/pagination";
|
|
14
12
|
import { partial } from "convex-helpers/validators";
|
|
15
13
|
|
|
16
|
-
export const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
14
|
+
export const adapterWhereValidator = v.object({
|
|
15
|
+
field: v.string(),
|
|
16
|
+
operator: v.optional(
|
|
17
|
+
v.union(
|
|
18
|
+
v.literal("lt"),
|
|
19
|
+
v.literal("lte"),
|
|
20
|
+
v.literal("gt"),
|
|
21
|
+
v.literal("gte"),
|
|
22
|
+
v.literal("eq"),
|
|
23
|
+
v.literal("in"),
|
|
24
|
+
v.literal("ne"),
|
|
25
|
+
v.literal("contains"),
|
|
26
|
+
v.literal("starts_with"),
|
|
27
|
+
v.literal("ends_with")
|
|
28
|
+
)
|
|
29
|
+
),
|
|
30
|
+
value: v.union(
|
|
31
|
+
v.string(),
|
|
32
|
+
v.number(),
|
|
33
|
+
v.boolean(),
|
|
34
|
+
v.array(v.string()),
|
|
35
|
+
v.array(v.number()),
|
|
36
|
+
v.null()
|
|
37
|
+
),
|
|
38
|
+
connector: v.optional(v.union(v.literal("AND"), v.literal("OR"))),
|
|
39
|
+
});
|
|
28
40
|
|
|
29
|
-
export const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
export const adapterArgsValidator = v.object({
|
|
42
|
+
model: v.string(),
|
|
43
|
+
where: v.optional(v.array(adapterWhereValidator)),
|
|
44
|
+
sortBy: v.optional(
|
|
45
|
+
v.object({
|
|
46
|
+
field: v.string(),
|
|
47
|
+
direction: v.union(v.literal("asc"), v.literal("desc")),
|
|
48
|
+
})
|
|
49
|
+
),
|
|
50
|
+
select: v.optional(v.array(v.string())),
|
|
51
|
+
limit: v.optional(v.number()),
|
|
52
|
+
unique: v.optional(v.boolean()),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const getUniqueFields = (table: TableNames, input: Record<string, any>) => {
|
|
56
|
+
const fields = specialFields[table as keyof typeof specialFields];
|
|
57
|
+
if (!fields) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
return Object.entries(fields)
|
|
61
|
+
.filter(
|
|
62
|
+
([key, value]) =>
|
|
63
|
+
value.unique && Object.keys(input).includes(key as keyof typeof input)
|
|
64
|
+
)
|
|
65
|
+
.map(([key]) => key);
|
|
36
66
|
};
|
|
37
67
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
68
|
+
const checkUniqueFields = async (
|
|
69
|
+
ctx: QueryCtx,
|
|
70
|
+
table: TableNames,
|
|
71
|
+
input: Record<string, any>,
|
|
72
|
+
doc?: Doc<any>
|
|
73
|
+
) => {
|
|
74
|
+
const uniqueFields = getUniqueFields(table, input);
|
|
75
|
+
if (!uniqueFields.length) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const field of uniqueFields) {
|
|
79
|
+
const existingDoc = await ctx.db
|
|
80
|
+
.query(table as any)
|
|
81
|
+
.withIndex(field as any, (q) =>
|
|
82
|
+
q.eq(field, input[field as keyof typeof input])
|
|
83
|
+
)
|
|
84
|
+
.unique();
|
|
85
|
+
if (existingDoc && existingDoc._id !== doc?._id) {
|
|
86
|
+
throw new Error(`${table} ${field} already exists`);
|
|
45
87
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
49
90
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
table: string;
|
|
91
|
+
const findIndex = async (args: {
|
|
92
|
+
model: string;
|
|
93
|
+
where?: {
|
|
54
94
|
field: string;
|
|
95
|
+
operator?: string;
|
|
55
96
|
value: any;
|
|
56
|
-
|
|
97
|
+
connector?: "AND" | "OR";
|
|
98
|
+
}[];
|
|
99
|
+
sortBy?: {
|
|
100
|
+
field: string;
|
|
101
|
+
direction: "asc" | "desc";
|
|
102
|
+
};
|
|
103
|
+
}) => {
|
|
104
|
+
if (!args.where && !args.sortBy) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (args.where?.some((w) => w.field === "id")) {
|
|
108
|
+
throw new Error("id is not a valid index field");
|
|
57
109
|
}
|
|
110
|
+
if (args.where?.some((w) => w.connector && w.connector !== "AND")) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`OR connector not supported: ${JSON.stringify(args.where)}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
args.where?.some(
|
|
117
|
+
(w) =>
|
|
118
|
+
w.operator &&
|
|
119
|
+
!["lt", "lte", "gt", "gte", "eq", "in"].includes(w.operator)
|
|
120
|
+
)
|
|
121
|
+
) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`where clause not supported: ${JSON.stringify(args.where)}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const lowerBounds =
|
|
127
|
+
args.where?.filter((w) => w.operator === "lt" || w.operator === "lte") ??
|
|
128
|
+
[];
|
|
129
|
+
if (lowerBounds.length > 1) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`cannot have more than one lower bound where clause: ${JSON.stringify(args.where)}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const upperBounds =
|
|
135
|
+
args.where?.filter((w) => w.operator === "gt" || w.operator === "gte") ??
|
|
136
|
+
[];
|
|
137
|
+
if (upperBounds.length > 1) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`cannot have more than one upper bound where clause: ${JSON.stringify(args.where)}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const lowerBound = lowerBounds[0];
|
|
143
|
+
const upperBound = upperBounds[0];
|
|
144
|
+
if (lowerBound && upperBound && lowerBound.field !== upperBound.field) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`lower bound and upper bound must have the same field: ${JSON.stringify(args.where)}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const boundField = lowerBound?.field || upperBound?.field;
|
|
150
|
+
if (
|
|
151
|
+
boundField &&
|
|
152
|
+
args.where?.some(
|
|
153
|
+
(w) => w.field === boundField && w !== lowerBound && w !== upperBound
|
|
154
|
+
)
|
|
155
|
+
) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`too many where clauses on the bound field: ${JSON.stringify(args.where)}`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const indexFields =
|
|
161
|
+
args.where
|
|
162
|
+
?.filter((w) => !w.operator || w.operator === "eq")
|
|
163
|
+
.sort((a, b) => {
|
|
164
|
+
return a.field.localeCompare(b.field);
|
|
165
|
+
})
|
|
166
|
+
.map((w) => [w.field, w.value]) ?? [];
|
|
167
|
+
if (!indexFields?.length && !boundField && !args.sortBy) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const indexes =
|
|
171
|
+
schema.tables[args.model as keyof typeof schema.tables][" indexes"]();
|
|
172
|
+
const sortField = args.sortBy?.field;
|
|
173
|
+
|
|
174
|
+
// We internally use _creationTime in place of Better Auth's createdAt
|
|
175
|
+
const indexName = indexFields
|
|
176
|
+
.map(([field]) => field)
|
|
177
|
+
.join("_")
|
|
178
|
+
.concat(
|
|
179
|
+
boundField && boundField !== "createdAt"
|
|
180
|
+
? `${indexFields.length ? "_" : ""}${boundField}`
|
|
181
|
+
: ""
|
|
182
|
+
)
|
|
183
|
+
.concat(
|
|
184
|
+
sortField && sortField !== "createdAt" && boundField !== sortField
|
|
185
|
+
? `${indexFields.length || boundField ? "_" : ""}${sortField}`
|
|
186
|
+
: ""
|
|
187
|
+
);
|
|
188
|
+
if (!indexName && !boundField && !sortField) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Use the built in creationTime index if bounding or sorting by createdAt
|
|
192
|
+
// with no other fields
|
|
193
|
+
const index = !indexName
|
|
194
|
+
? {
|
|
195
|
+
indexDescriptor: "by_creation_time",
|
|
196
|
+
fields: [],
|
|
197
|
+
}
|
|
198
|
+
: indexes.find(({ indexDescriptor }) => {
|
|
199
|
+
return boundField === "createdAt" || sortField === "createdAt"
|
|
200
|
+
? indexDescriptor === indexName
|
|
201
|
+
: indexDescriptor.startsWith(indexName);
|
|
202
|
+
});
|
|
203
|
+
if (!index) {
|
|
204
|
+
throw new Error(`Index ${indexName} not found for table ${args.model}`);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
index: {
|
|
208
|
+
indexDescriptor: index.indexDescriptor,
|
|
209
|
+
fields: [...index.fields, "_creationTime"],
|
|
210
|
+
},
|
|
211
|
+
boundField,
|
|
212
|
+
sortField,
|
|
213
|
+
values: {
|
|
214
|
+
eq: indexFields.map(([, value]) => value),
|
|
215
|
+
lt: lowerBound?.operator === "lt" ? lowerBound.value : undefined,
|
|
216
|
+
lte: lowerBound?.operator === "lte" ? lowerBound.value : undefined,
|
|
217
|
+
gt: upperBound?.operator === "gt" ? upperBound.value : undefined,
|
|
218
|
+
gte: upperBound?.operator === "gte" ? upperBound.value : undefined,
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const selectFields = <T extends TableNames, D extends Doc<T>>(
|
|
224
|
+
doc: D | null,
|
|
225
|
+
select?: string[]
|
|
58
226
|
) => {
|
|
59
|
-
if (
|
|
60
|
-
return
|
|
227
|
+
if (!doc) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
if (!select?.length) {
|
|
231
|
+
return doc;
|
|
61
232
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
233
|
+
return select.reduce((acc, field) => {
|
|
234
|
+
(acc as any)[field] = doc[field];
|
|
235
|
+
return acc;
|
|
236
|
+
}, {} as D);
|
|
66
237
|
};
|
|
67
238
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
239
|
+
// This is the core function for reading from the database, it parses and
|
|
240
|
+
// validates where conditions, selects indexes, and allows the caller to
|
|
241
|
+
// optionally paginate as needed.
|
|
242
|
+
const paginate = async (
|
|
243
|
+
ctx: QueryCtx,
|
|
244
|
+
args: Infer<typeof adapterArgsValidator> & {
|
|
245
|
+
paginationOpts: PaginationOptions;
|
|
246
|
+
}
|
|
247
|
+
): Promise<PaginationResult<Doc<any>>> => {
|
|
248
|
+
// If any index is id, we can only return a single document
|
|
249
|
+
const idWhere = args.where?.find((w) => w.field === "id");
|
|
250
|
+
if (idWhere) {
|
|
251
|
+
const doc = await ctx.db.get(idWhere.value as Id<TableNames>);
|
|
252
|
+
return {
|
|
253
|
+
page: [selectFields(doc, args.select)].filter(Boolean),
|
|
254
|
+
isDone: true,
|
|
255
|
+
continueCursor: "",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const { index, values, boundField } = (await findIndex(args)) ?? {};
|
|
259
|
+
const query = paginator(ctx.db, schema).query(args.model as any);
|
|
260
|
+
const hasValues =
|
|
261
|
+
values?.eq?.length ||
|
|
262
|
+
values?.lt ||
|
|
263
|
+
values?.lte ||
|
|
264
|
+
values?.gt ||
|
|
265
|
+
values?.gte;
|
|
266
|
+
const indexedQuery =
|
|
267
|
+
index && index.indexDescriptor !== "by_creation_time"
|
|
268
|
+
? query.withIndex(
|
|
269
|
+
index.indexDescriptor,
|
|
270
|
+
hasValues
|
|
271
|
+
? (q: any) => {
|
|
272
|
+
for (const [idx, value] of (values?.eq ?? []).entries()) {
|
|
273
|
+
q = q.eq(index.fields[idx], value);
|
|
274
|
+
}
|
|
275
|
+
if (values?.lt) {
|
|
276
|
+
q = q.lt(boundField, values.lt);
|
|
277
|
+
}
|
|
278
|
+
if (values?.lte) {
|
|
279
|
+
q = q.lte(boundField, values.lte);
|
|
280
|
+
}
|
|
281
|
+
if (values?.gt) {
|
|
282
|
+
q = q.gt(boundField, values.gt);
|
|
283
|
+
}
|
|
284
|
+
if (values?.gte) {
|
|
285
|
+
q = q.gte(boundField, values.gte);
|
|
286
|
+
}
|
|
287
|
+
return q;
|
|
288
|
+
}
|
|
289
|
+
: undefined
|
|
290
|
+
)
|
|
291
|
+
: query;
|
|
292
|
+
|
|
293
|
+
const orderedQuery = args.sortBy
|
|
294
|
+
? indexedQuery.order(args.sortBy.direction === "asc" ? "asc" : "desc")
|
|
295
|
+
: indexedQuery;
|
|
296
|
+
const result = await orderedQuery.paginate(args.paginationOpts);
|
|
297
|
+
return {
|
|
298
|
+
...result,
|
|
299
|
+
page: result.page.map((doc) => selectFields(doc, args.select)),
|
|
300
|
+
};
|
|
80
301
|
};
|
|
81
302
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
args:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
303
|
+
const listOne = async (
|
|
304
|
+
ctx: QueryCtx,
|
|
305
|
+
args: Infer<typeof adapterArgsValidator>
|
|
306
|
+
): Promise<Doc<any> | null> => {
|
|
307
|
+
return (
|
|
308
|
+
await paginate(ctx, {
|
|
309
|
+
...args,
|
|
310
|
+
paginationOpts: {
|
|
311
|
+
numItems: 1,
|
|
312
|
+
cursor: null,
|
|
313
|
+
},
|
|
314
|
+
})
|
|
315
|
+
).page[0];
|
|
316
|
+
};
|
|
94
317
|
|
|
95
318
|
export const create = mutation({
|
|
96
|
-
args:
|
|
319
|
+
args: {
|
|
97
320
|
input: v.union(
|
|
98
|
-
...Object.
|
|
321
|
+
...Object.entries(schema.tables).map(([model, table]) =>
|
|
99
322
|
v.object({
|
|
100
|
-
|
|
101
|
-
|
|
323
|
+
model: v.literal(model),
|
|
324
|
+
where: v.optional(v.array(adapterWhereValidator)),
|
|
325
|
+
data: v.object(table.validator.fields),
|
|
102
326
|
})
|
|
103
327
|
)
|
|
104
328
|
),
|
|
105
|
-
}),
|
|
106
|
-
handler: async (ctx, args) => {
|
|
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}`);
|
|
114
|
-
}
|
|
115
|
-
return transformOutput(doc, table);
|
|
116
329
|
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
export const updateArgsInputValidator = <T extends TableNames>(table: T) => {
|
|
120
|
-
return v.object({
|
|
121
|
-
table: v.literal(table),
|
|
122
|
-
where: v.object({ field: v.string(), value: getByArgsValidator.value }),
|
|
123
|
-
value: v.record(v.string(), v.any()),
|
|
124
|
-
});
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const updateArgsValidator = {
|
|
128
|
-
input: v.union(
|
|
129
|
-
updateArgsInputValidator("account"),
|
|
130
|
-
updateArgsInputValidator("session"),
|
|
131
|
-
updateArgsInputValidator("verification"),
|
|
132
|
-
updateArgsInputValidator("user")
|
|
133
|
-
),
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
export const update = mutation({
|
|
137
|
-
args: updateArgsValidator,
|
|
138
330
|
handler: async (ctx, args) => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!doc) {
|
|
145
|
-
throw new Error(`Failed to update ${table}`);
|
|
146
|
-
}
|
|
147
|
-
await ctx.db.patch(doc._id, value as any);
|
|
148
|
-
const updatedDoc = await ctx.db.get(doc._id);
|
|
149
|
-
if (!updatedDoc) {
|
|
150
|
-
throw new Error(`Failed to update ${table}`);
|
|
151
|
-
}
|
|
152
|
-
return transformOutput(updatedDoc, table);
|
|
153
|
-
},
|
|
154
|
-
});
|
|
331
|
+
await checkUniqueFields(
|
|
332
|
+
ctx,
|
|
333
|
+
args.input.model as TableNames,
|
|
334
|
+
args.input.data
|
|
335
|
+
);
|
|
155
336
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
handler: async (ctx, args) => {
|
|
159
|
-
const doc = await getByHelper(ctx, args);
|
|
337
|
+
const id = await ctx.db.insert(args.input.model as any, args.input.data);
|
|
338
|
+
const doc = await ctx.db.get(id);
|
|
160
339
|
if (!doc) {
|
|
161
|
-
|
|
340
|
+
throw new Error(`Failed to create ${args.input.model}`);
|
|
162
341
|
}
|
|
163
|
-
await ctx.db.delete(doc._id);
|
|
164
|
-
// onDeleteUser requires userId from the doc,
|
|
165
|
-
// so just return the whole thing
|
|
166
342
|
return doc;
|
|
167
343
|
},
|
|
168
344
|
});
|
|
169
345
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
args: { userId: v.string(), limit: v.optional(v.number()) },
|
|
173
|
-
handler: async (ctx, args) => {
|
|
174
|
-
const query = ctx.db
|
|
175
|
-
.query("account")
|
|
176
|
-
.withIndex("userId", (q) => q.eq("userId", args.userId));
|
|
177
|
-
const docs = args.limit
|
|
178
|
-
? await query.take(args.limit)
|
|
179
|
-
: await query.collect();
|
|
180
|
-
return docs.map((doc) => transformOutput(doc, "account"));
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
export const getSessionsByUserId = query({
|
|
185
|
-
args: { userId: v.string(), limit: v.optional(v.number()) },
|
|
346
|
+
export const findOne = query({
|
|
347
|
+
args: adapterArgsValidator,
|
|
186
348
|
handler: async (ctx, args) => {
|
|
187
|
-
|
|
188
|
-
.query("session")
|
|
189
|
-
.withIndex("userId", (q) => q.eq("userId", args.userId));
|
|
190
|
-
const docs = args.limit
|
|
191
|
-
? await query.take(args.limit)
|
|
192
|
-
: await query.collect();
|
|
193
|
-
return docs.map((doc) => transformOutput(doc, "session"));
|
|
349
|
+
return await listOne(ctx, args);
|
|
194
350
|
},
|
|
195
351
|
});
|
|
196
352
|
|
|
197
|
-
export const
|
|
353
|
+
export const findMany = query({
|
|
198
354
|
args: {
|
|
199
|
-
|
|
355
|
+
...adapterArgsValidator.fields,
|
|
356
|
+
paginationOpts: paginationOptsValidator,
|
|
200
357
|
},
|
|
201
358
|
handler: async (ctx, args) => {
|
|
202
|
-
|
|
203
|
-
const docs = args.limit
|
|
204
|
-
? await query.take(args.limit)
|
|
205
|
-
: await query.collect();
|
|
206
|
-
return docs.map((doc) => transformOutput(doc, "jwks"));
|
|
359
|
+
return await paginate(ctx, args);
|
|
207
360
|
},
|
|
208
361
|
});
|
|
209
362
|
|
|
210
|
-
export const
|
|
363
|
+
export const updateOne = mutation({
|
|
211
364
|
args: {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
365
|
+
input: v.union(
|
|
366
|
+
...Object.entries(schema.tables).map(([model, table]) =>
|
|
367
|
+
v.object({
|
|
368
|
+
model: v.literal(model),
|
|
369
|
+
where: v.optional(v.array(adapterWhereValidator)),
|
|
370
|
+
update: v.object(
|
|
371
|
+
Object.fromEntries(
|
|
372
|
+
Object.entries(table.validator.fields).map(([key, value]) => [
|
|
373
|
+
key,
|
|
374
|
+
value.isOptional === "required" ? v.optional(value) : value,
|
|
375
|
+
])
|
|
376
|
+
)
|
|
377
|
+
),
|
|
378
|
+
})
|
|
379
|
+
)
|
|
218
380
|
),
|
|
219
|
-
limit: v.optional(v.number()),
|
|
220
381
|
},
|
|
221
382
|
handler: async (ctx, args) => {
|
|
222
|
-
|
|
223
|
-
|
|
383
|
+
const doc = await listOne(ctx, args.input);
|
|
384
|
+
if (!doc) {
|
|
385
|
+
throw new Error(`Failed to update ${args.input.model}`);
|
|
224
386
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
.
|
|
228
|
-
.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
export const deleteOldVerificationsPage = mutation({
|
|
241
|
-
args: {
|
|
242
|
-
currentTimestamp: v.number(),
|
|
243
|
-
paginationOpts: v.optional(paginationOptsValidator),
|
|
244
|
-
},
|
|
245
|
-
handler: async (ctx, args) => {
|
|
246
|
-
const paginationOpts = args.paginationOpts ?? {
|
|
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 };
|
|
387
|
+
await checkUniqueFields(
|
|
388
|
+
ctx,
|
|
389
|
+
args.input.model as TableNames,
|
|
390
|
+
args.input.update,
|
|
391
|
+
doc
|
|
392
|
+
);
|
|
393
|
+
await ctx.db.patch(doc._id, args.input.update as any);
|
|
394
|
+
const updatedDoc = await ctx.db.get(doc._id);
|
|
395
|
+
if (!updatedDoc) {
|
|
396
|
+
throw new Error(`Failed to update ${args.input.model}`);
|
|
397
|
+
}
|
|
398
|
+
return updatedDoc;
|
|
258
399
|
},
|
|
259
400
|
});
|
|
260
401
|
|
|
261
|
-
export const
|
|
402
|
+
export const updateMany = mutation({
|
|
262
403
|
args: {
|
|
263
|
-
|
|
404
|
+
...adapterArgsValidator.fields,
|
|
405
|
+
update: v.optional(v.object(partial(schema.tables.user.validator.fields))),
|
|
406
|
+
paginationOpts: paginationOptsValidator,
|
|
264
407
|
},
|
|
265
408
|
handler: async (ctx, args) => {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
409
|
+
const { page, ...result } = await paginate(ctx, args);
|
|
410
|
+
if (args.update) {
|
|
411
|
+
const uniqueFields = getUniqueFields(
|
|
412
|
+
args.model as TableNames,
|
|
413
|
+
args.update ?? {}
|
|
414
|
+
);
|
|
415
|
+
if (uniqueFields.length && page.length > 1) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
`Attempted to set unique fields in multiple documents in ${args.model} with the same value. Fields: ${uniqueFields.join(", ")}`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
await asyncMap(page, async (doc) => {
|
|
421
|
+
await checkUniqueFields(
|
|
422
|
+
ctx,
|
|
423
|
+
args.model as TableNames,
|
|
424
|
+
args.update ?? {},
|
|
425
|
+
doc
|
|
426
|
+
);
|
|
427
|
+
await ctx.db.patch(doc._id, args.update as any);
|
|
278
428
|
});
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
? result.splitCursor
|
|
285
|
-
: result.continueCursor;
|
|
286
|
-
isDone = result.isDone;
|
|
287
|
-
} while (!isDone);
|
|
288
|
-
return count;
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
...result,
|
|
432
|
+
count: page.length,
|
|
433
|
+
};
|
|
289
434
|
},
|
|
290
435
|
});
|
|
291
436
|
|
|
292
|
-
export const
|
|
293
|
-
args:
|
|
294
|
-
userId: v.string(),
|
|
295
|
-
expiresAt: v.number(),
|
|
296
|
-
},
|
|
437
|
+
export const deleteOne = mutation({
|
|
438
|
+
args: adapterArgsValidator,
|
|
297
439
|
handler: async (ctx, args) => {
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
await asyncMap(docs, async (doc) => {
|
|
305
|
-
await ctx.db.delete(doc._id);
|
|
306
|
-
});
|
|
307
|
-
return docs.length;
|
|
440
|
+
const doc = await listOne(ctx, args);
|
|
441
|
+
if (!doc) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
await ctx.db.delete(doc._id);
|
|
445
|
+
return doc;
|
|
308
446
|
},
|
|
309
447
|
});
|
|
310
448
|
|
|
311
|
-
export const
|
|
449
|
+
export const deleteMany = mutation({
|
|
312
450
|
args: {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
paginationOpts: v.optional(paginationOptsValidator),
|
|
451
|
+
...adapterArgsValidator.fields,
|
|
452
|
+
paginationOpts: paginationOptsValidator,
|
|
316
453
|
},
|
|
317
454
|
handler: async (ctx, args) => {
|
|
318
|
-
const
|
|
319
|
-
numItems: 500,
|
|
320
|
-
cursor: null,
|
|
321
|
-
};
|
|
322
|
-
const { page, ...result } = await paginator(ctx.db, schema)
|
|
323
|
-
.query(args.table as any)
|
|
324
|
-
.withIndex("userId", (q) => q.eq("userId", args.userId))
|
|
325
|
-
.paginate(paginationOpts);
|
|
455
|
+
const { page, ...result } = await paginate(ctx, args);
|
|
326
456
|
await asyncMap(page, async (doc) => {
|
|
327
457
|
await ctx.db.delete(doc._id);
|
|
328
458
|
});
|
|
329
|
-
return {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
export const deleteAllForUser = action({
|
|
334
|
-
args: {
|
|
335
|
-
table: v.string(),
|
|
336
|
-
userId: v.string(),
|
|
337
|
-
},
|
|
338
|
-
handler: async (ctx, args) => {
|
|
339
|
-
let count = 0;
|
|
340
|
-
let cursor = null;
|
|
341
|
-
let isDone = false;
|
|
342
|
-
do {
|
|
343
|
-
const result: Omit<PaginationResult<Doc<"session">>, "page"> & {
|
|
344
|
-
count: number;
|
|
345
|
-
} = await ctx.runMutation(api.lib.deleteAllForUserPage, {
|
|
346
|
-
table: args.table,
|
|
347
|
-
userId: args.userId,
|
|
348
|
-
paginationOpts: {
|
|
349
|
-
numItems: 500,
|
|
350
|
-
cursor,
|
|
351
|
-
},
|
|
352
|
-
});
|
|
353
|
-
count += result.count;
|
|
354
|
-
cursor =
|
|
355
|
-
result.pageStatus &&
|
|
356
|
-
result.splitCursor &&
|
|
357
|
-
["SplitRecommended", "SplitRequired"].includes(result.pageStatus)
|
|
358
|
-
? result.splitCursor
|
|
359
|
-
: result.continueCursor;
|
|
360
|
-
isDone = result.isDone;
|
|
361
|
-
} while (!isDone);
|
|
362
|
-
return count;
|
|
459
|
+
return {
|
|
460
|
+
...result,
|
|
461
|
+
count: page.length,
|
|
462
|
+
};
|
|
363
463
|
},
|
|
364
464
|
});
|
|
365
465
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
.unique();
|
|
375
|
-
if (!doc) {
|
|
376
|
-
return;
|
|
466
|
+
// Get the session via sessionId in jwt claims
|
|
467
|
+
// TODO: this needs a refresh, subquery only necessary for actions
|
|
468
|
+
export const getCurrentSession = query({
|
|
469
|
+
args: {},
|
|
470
|
+
handler: async (ctx) => {
|
|
471
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
472
|
+
if (!identity) {
|
|
473
|
+
return null;
|
|
377
474
|
}
|
|
378
|
-
return
|
|
475
|
+
return ctx.db.get(identity.sessionId as Id<"session">);
|
|
379
476
|
},
|
|
380
477
|
});
|
|
381
478
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
update: v.object(partial(schema.tables.account.validator.fields)),
|
|
387
|
-
},
|
|
479
|
+
// TODO: rewrite functions below here to be dynamic
|
|
480
|
+
|
|
481
|
+
export const getIn = query({
|
|
482
|
+
args: adapterArgsValidator,
|
|
388
483
|
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;
|
|
484
|
+
const where = args.where?.[0];
|
|
485
|
+
if (!where || where.operator !== "in" || args.where?.length !== 1) {
|
|
486
|
+
throw new Error("where must be a single in clause");
|
|
397
487
|
}
|
|
398
|
-
|
|
399
|
-
await
|
|
400
|
-
|
|
401
|
-
|
|
488
|
+
return (
|
|
489
|
+
await asyncMap(where.value as any[], async (value) => {
|
|
490
|
+
if (where.field === "id") {
|
|
491
|
+
return [await ctx.db.get(value as Id<TableNames>)];
|
|
492
|
+
}
|
|
493
|
+
const query = ctx.db
|
|
494
|
+
.query(args.model as any)
|
|
495
|
+
.withIndex(where.field as any, (q) => q.eq(where.field, value));
|
|
496
|
+
if (args.limit) {
|
|
497
|
+
return await query.take(args.limit);
|
|
498
|
+
}
|
|
499
|
+
return await query.collect();
|
|
500
|
+
})
|
|
501
|
+
)
|
|
502
|
+
.flat()
|
|
503
|
+
.filter(Boolean);
|
|
402
504
|
},
|
|
403
505
|
});
|
|
404
506
|
|
|
405
|
-
export const
|
|
507
|
+
export const deleteIn = mutation({
|
|
406
508
|
args: {
|
|
407
|
-
|
|
408
|
-
|
|
509
|
+
input: v.union(
|
|
510
|
+
v.object({
|
|
511
|
+
table: v.literal("session"),
|
|
512
|
+
field: v.literal("token"),
|
|
513
|
+
values: v.array(v.string()),
|
|
514
|
+
})
|
|
515
|
+
),
|
|
409
516
|
},
|
|
410
517
|
handler: async (ctx, args) => {
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
518
|
+
const { table, field, values } = args.input;
|
|
519
|
+
const docs = await asyncMap(values, async (value) => {
|
|
520
|
+
const doc = await ctx.db
|
|
521
|
+
.query(table)
|
|
522
|
+
.withIndex(field as any, (q) => q.eq(field, value))
|
|
523
|
+
.unique();
|
|
524
|
+
if (!doc) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
await ctx.db.delete(doc._id);
|
|
528
|
+
return doc;
|
|
420
529
|
});
|
|
421
|
-
return docs.length;
|
|
530
|
+
return docs.filter(Boolean).length;
|
|
422
531
|
},
|
|
423
532
|
});
|