better-convex 0.5.8 → 0.6.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/auth/index.d.ts +123 -107
- package/dist/auth/index.js +562 -112
- package/dist/auth-client/index.d.ts +2 -1
- package/dist/auth-client/index.js +2 -1
- package/dist/auth-config/index.d.ts +45 -0
- package/dist/auth-config/index.js +24 -0
- package/dist/auth-nextjs/index.d.ts +2 -2
- package/dist/auth-nextjs/index.js +2 -2
- package/dist/{caller-factory-CeZ07fQ2.js → caller-factory-B1FvYSKr.js} +18 -12
- package/dist/cli.mjs +246 -0
- package/dist/{codegen-CSeApTME.cjs → codegen-DkpPBVPn.mjs} +28 -60
- package/dist/context-utils-DSuX99Da.d.ts +17 -0
- package/dist/{create-schema-B4CUvqik.js → create-schema-DhWXOhnU.js} +1 -1
- package/dist/create-schema-orm-DplxTtYj.js +145 -0
- package/dist/crpc/index.d.ts +3 -3
- package/dist/crpc/index.js +4 -3
- package/dist/customFunctions-C1okqCzL.js +377 -0
- package/dist/{http-types-BrMbHGYR.d.ts → http-types-BRLY10NX.d.ts} +70 -20
- package/dist/index-BQkhP2ny.d.ts +1326 -0
- package/dist/orm/index.d.ts +2977 -0
- package/dist/orm/index.js +3 -0
- package/dist/orm-Banm-XXb.js +8812 -0
- package/dist/react/index.d.ts +46 -9
- package/dist/react/index.js +362 -123
- package/dist/rsc/index.d.ts +7 -4
- package/dist/rsc/index.js +10 -7
- package/dist/server/index.d.ts +4 -617
- package/dist/server/index.js +914 -50
- package/dist/transformer-CTNSPjwp.js +194 -0
- package/dist/types-jftzhhuc.d.ts +42 -0
- package/dist/{types-DrFf50wo.d.ts → types-o-5rYcTr.d.ts} +1 -1
- package/dist/watcher.mjs +41 -0
- package/package.json +10 -12
- package/dist/cli.cjs +0 -215
- package/dist/watcher.cjs +0 -29
- /package/dist/{error-BPjr9_gg.js → error-BZUhlhYz.js} +0 -0
- /package/dist/{meta-utils-DS5fA5GB.js → meta-utils-DCpLSBWB.js} +0 -0
- /package/dist/{query-options-Dbyr-NY1.js → query-options-BL1Q0X7q.js} +0 -0
package/dist/auth/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { isRunMutationCtx } from "
|
|
1
|
+
import { a as partial, h as asyncMap, l as isQueryCtx, n as customCtx, r as customMutation, u as isRunMutationCtx } from "../customFunctions-C1okqCzL.js";
|
|
2
|
+
import { C as stream, D as unsetToken, L as eq, S as mergedStream } from "../orm-Banm-XXb.js";
|
|
3
|
+
import { convex } from "@convex-dev/better-auth/plugins";
|
|
2
4
|
import { createAdapterFactory } from "better-auth/adapters";
|
|
3
5
|
import { getAuthTables } from "better-auth/db";
|
|
4
|
-
import { createFunctionHandle, internalActionGeneric, internalMutationGeneric, internalQueryGeneric, paginationOptsValidator } from "convex/server";
|
|
5
|
-
import {
|
|
6
|
-
import { prop, sortBy, unique } from "remeda";
|
|
6
|
+
import { ROUTABLE_HTTP_METHODS, createFunctionHandle, httpActionGeneric, httpRouter, internalActionGeneric, internalMutationGeneric, internalQueryGeneric, paginationOptsValidator } from "convex/server";
|
|
7
|
+
import { prop, sortBy, uniqueBy } from "remeda";
|
|
7
8
|
import { v } from "convex/values";
|
|
8
|
-
import { partial } from "convex-helpers/validators";
|
|
9
9
|
import { stripIndent } from "common-tags";
|
|
10
|
-
import { mergedStream, stream } from "convex-helpers/server/stream";
|
|
11
10
|
|
|
12
11
|
//#region src/auth/adapter-utils.ts
|
|
13
12
|
const adapterWhereValidator = v.object({
|
|
@@ -209,9 +208,13 @@ const paginate = async (ctx, schema, betterAuthSchema, args) => {
|
|
|
209
208
|
page: []
|
|
210
209
|
};
|
|
211
210
|
}
|
|
211
|
+
const paginationLimit = args.paginationOpts.numItems ?? args.limit ?? 200;
|
|
212
|
+
const paginationMaxScan = Math.max(args.paginationOpts.maximumRowsRead ?? 0, paginationLimit + 1, 200);
|
|
212
213
|
const paginationOpts = {
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
cursor: args.paginationOpts.cursor,
|
|
215
|
+
endCursor: args.paginationOpts.endCursor,
|
|
216
|
+
limit: paginationLimit,
|
|
217
|
+
maxScan: paginationMaxScan
|
|
215
218
|
};
|
|
216
219
|
const inWhere = args.where?.find((w) => w.operator === "in");
|
|
217
220
|
if (inWhere) {
|
|
@@ -281,134 +284,224 @@ const whereValidator = (schema, tableName) => v.object({
|
|
|
281
284
|
operator: v.optional(v.union(v.literal("lt"), v.literal("lte"), v.literal("gt"), v.literal("gte"), v.literal("eq"), v.literal("in"), v.literal("not_in"), v.literal("ne"), v.literal("contains"), v.literal("starts_with"), v.literal("ends_with"))),
|
|
282
285
|
value: v.union(v.string(), v.number(), v.boolean(), v.array(v.string()), v.array(v.number()), v.null())
|
|
283
286
|
});
|
|
287
|
+
const resolveSchemaTableName = (schema, betterAuthSchema, model) => {
|
|
288
|
+
if (schema.tables[model]) return model;
|
|
289
|
+
const modelConfig = betterAuthSchema?.[model];
|
|
290
|
+
if (modelConfig?.modelName && schema.tables[modelConfig.modelName]) return modelConfig.modelName;
|
|
291
|
+
for (const [key, value] of Object.entries(betterAuthSchema ?? {})) {
|
|
292
|
+
if (value?.modelName !== model) continue;
|
|
293
|
+
if (schema.tables[key]) return key;
|
|
294
|
+
if (schema.tables[value.modelName]) return value.modelName;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const resolveOrmTable = (ctx, schema, betterAuthSchema, model) => {
|
|
298
|
+
if (!ctx?.orm || typeof ctx.orm.insert !== "function" || typeof ctx.orm.update !== "function" || typeof ctx.orm.delete !== "function") return;
|
|
299
|
+
const tableName = resolveSchemaTableName(schema, betterAuthSchema, model);
|
|
300
|
+
if (!tableName) return;
|
|
301
|
+
const table = schema.tables[tableName];
|
|
302
|
+
if (!table || !table._id) return;
|
|
303
|
+
return {
|
|
304
|
+
table,
|
|
305
|
+
tableName
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
const normalizeUpdateForOrm = (update) => Object.fromEntries(Object.entries(update).map(([key, value]) => [key, value === void 0 ? unsetToken : value]));
|
|
309
|
+
const ormInsert = async (ctx, table, data) => (await ctx.orm.insert(table).values(data).returning())[0];
|
|
310
|
+
const ormUpdate = async (ctx, table, id, update) => (await ctx.orm.update(table).set(normalizeUpdateForOrm(update)).returning().where(eq(table._id, id)))[0];
|
|
311
|
+
const ormDelete = async (ctx, table, id) => {
|
|
312
|
+
await ctx.orm.delete(table).where(eq(table._id, id));
|
|
313
|
+
};
|
|
314
|
+
const withBothIdFields = (doc) => {
|
|
315
|
+
const existingUnderscoreId = doc._id;
|
|
316
|
+
const existingId = doc.id;
|
|
317
|
+
const id = existingUnderscoreId ?? existingId;
|
|
318
|
+
if (!id) return doc;
|
|
319
|
+
return {
|
|
320
|
+
...doc,
|
|
321
|
+
_id: existingUnderscoreId ?? id,
|
|
322
|
+
id: existingId ?? id
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
const isPlainObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
326
|
+
const serializeDatesForConvex = (value) => {
|
|
327
|
+
if (value instanceof Date) return value.getTime();
|
|
328
|
+
if (Array.isArray(value)) {
|
|
329
|
+
let result;
|
|
330
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
331
|
+
const entry = value[index];
|
|
332
|
+
const serialized = serializeDatesForConvex(entry);
|
|
333
|
+
if (serialized !== entry) {
|
|
334
|
+
if (!result) result = value.slice();
|
|
335
|
+
result[index] = serialized;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return result ?? value;
|
|
339
|
+
}
|
|
340
|
+
if (!isPlainObject(value)) return value;
|
|
341
|
+
let serialized;
|
|
342
|
+
for (const key in value) {
|
|
343
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
344
|
+
const nested = value[key];
|
|
345
|
+
const encoded = serializeDatesForConvex(nested);
|
|
346
|
+
if (encoded !== nested) {
|
|
347
|
+
if (!serialized) serialized = { ...value };
|
|
348
|
+
serialized[key] = encoded;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return serialized ?? value;
|
|
352
|
+
};
|
|
353
|
+
const toConvexSafe = (value) => serializeDatesForConvex(value);
|
|
284
354
|
const createHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
285
355
|
let data = args.input.data;
|
|
286
356
|
if (!args.skipBeforeHooks && args.beforeCreateHandle) {
|
|
287
|
-
const transformedData = await ctx.runMutation(args.beforeCreateHandle, {
|
|
357
|
+
const transformedData = await ctx.runMutation(args.beforeCreateHandle, serializeDatesForConvex({
|
|
288
358
|
data,
|
|
289
359
|
model: args.input.model
|
|
290
|
-
});
|
|
360
|
+
}));
|
|
291
361
|
if (transformedData !== void 0) data = transformedData;
|
|
292
362
|
}
|
|
293
363
|
await checkUniqueFields(ctx, schema, betterAuthSchema, args.input.model, data);
|
|
294
|
-
const
|
|
295
|
-
const doc = await ctx.
|
|
364
|
+
const ormTable = resolveOrmTable(ctx, schema, betterAuthSchema, args.input.model);
|
|
365
|
+
const doc = ormTable ? await ormInsert(ctx, ormTable.table, data) : await (async () => {
|
|
366
|
+
const id = await ctx.db.insert(args.input.model, data);
|
|
367
|
+
return ctx.db.get(id);
|
|
368
|
+
})();
|
|
296
369
|
if (!doc) throw new Error(`Failed to create ${args.input.model}`);
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
370
|
+
const normalizedDoc = ormTable ? withBothIdFields(doc) : doc;
|
|
371
|
+
const result = await selectFields(normalizedDoc, args.select);
|
|
372
|
+
if (args.onCreateHandle) {
|
|
373
|
+
const hookDoc = normalizedDoc;
|
|
374
|
+
await ctx.runMutation(args.onCreateHandle, serializeDatesForConvex({
|
|
375
|
+
doc: hookDoc,
|
|
376
|
+
model: args.input.model
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
return toConvexSafe(result);
|
|
303
380
|
};
|
|
304
|
-
const findOneHandler = async (ctx, args, schema, betterAuthSchema) => await listOne(ctx, schema, betterAuthSchema, args);
|
|
305
|
-
const findManyHandler = async (ctx, args, schema, betterAuthSchema) => await paginate(ctx, schema, betterAuthSchema, args);
|
|
381
|
+
const findOneHandler = async (ctx, args, schema, betterAuthSchema) => toConvexSafe(await listOne(ctx, schema, betterAuthSchema, args));
|
|
382
|
+
const findManyHandler = async (ctx, args, schema, betterAuthSchema) => toConvexSafe(await paginate(ctx, schema, betterAuthSchema, args));
|
|
306
383
|
const updateOneHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
307
384
|
const doc = await listOne(ctx, schema, betterAuthSchema, args.input);
|
|
308
385
|
if (!doc) throw new Error(`Failed to update ${args.input.model}`);
|
|
309
386
|
let update = args.input.update;
|
|
310
387
|
if (args.beforeUpdateHandle) {
|
|
311
|
-
const transformedUpdate = await ctx.runMutation(args.beforeUpdateHandle, {
|
|
388
|
+
const transformedUpdate = await ctx.runMutation(args.beforeUpdateHandle, serializeDatesForConvex({
|
|
312
389
|
doc,
|
|
313
390
|
model: args.input.model,
|
|
314
391
|
update
|
|
315
|
-
});
|
|
392
|
+
}));
|
|
316
393
|
if (transformedUpdate !== void 0) update = transformedUpdate;
|
|
317
394
|
}
|
|
318
395
|
await checkUniqueFields(ctx, schema, betterAuthSchema, args.input.model, update, doc);
|
|
319
|
-
|
|
320
|
-
const updatedDoc = await ctx.
|
|
396
|
+
const ormTable = resolveOrmTable(ctx, schema, betterAuthSchema, args.input.model);
|
|
397
|
+
const updatedDoc = ormTable ? await ormUpdate(ctx, ormTable.table, doc._id, update) : await (async () => {
|
|
398
|
+
await ctx.db.patch(doc._id, update);
|
|
399
|
+
return ctx.db.get(doc._id);
|
|
400
|
+
})();
|
|
321
401
|
if (!updatedDoc) throw new Error(`Failed to update ${args.input.model}`);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
402
|
+
const normalizedUpdatedDoc = ormTable ? withBothIdFields(updatedDoc) : updatedDoc;
|
|
403
|
+
if (args.onUpdateHandle) {
|
|
404
|
+
const hookNewDoc = normalizedUpdatedDoc;
|
|
405
|
+
await ctx.runMutation(args.onUpdateHandle, serializeDatesForConvex({
|
|
406
|
+
model: args.input.model,
|
|
407
|
+
newDoc: hookNewDoc,
|
|
408
|
+
oldDoc: doc
|
|
409
|
+
}));
|
|
410
|
+
}
|
|
411
|
+
return toConvexSafe(normalizedUpdatedDoc);
|
|
328
412
|
};
|
|
329
413
|
const updateManyHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
330
414
|
const { page, ...result } = await paginate(ctx, schema, betterAuthSchema, {
|
|
331
415
|
...args.input,
|
|
332
416
|
paginationOpts: args.paginationOpts
|
|
333
417
|
});
|
|
418
|
+
const ormTable = resolveOrmTable(ctx, schema, betterAuthSchema, args.input.model);
|
|
334
419
|
if (args.input.update) {
|
|
335
420
|
if (hasUniqueFields(betterAuthSchema, args.input.model, args.input.update ?? {}) && page.length > 1) throw new Error(`Attempted to set unique fields in multiple documents in ${args.input.model} with the same value. Fields: ${Object.keys(args.input.update ?? {}).join(", ")}`);
|
|
336
421
|
await asyncMap(page, async (doc) => {
|
|
337
422
|
let update = args.input.update;
|
|
338
423
|
if (args.beforeUpdateHandle) {
|
|
339
|
-
const transformedUpdate = await ctx.runMutation(args.beforeUpdateHandle, {
|
|
424
|
+
const transformedUpdate = await ctx.runMutation(args.beforeUpdateHandle, serializeDatesForConvex({
|
|
340
425
|
doc,
|
|
341
426
|
model: args.input.model,
|
|
342
427
|
update
|
|
343
|
-
});
|
|
428
|
+
}));
|
|
344
429
|
if (transformedUpdate !== void 0) update = transformedUpdate;
|
|
345
430
|
}
|
|
346
431
|
await checkUniqueFields(ctx, schema, betterAuthSchema, args.input.model, update ?? {}, doc);
|
|
347
|
-
await ctx.
|
|
432
|
+
const newDoc = ormTable ? await ormUpdate(ctx, ormTable.table, doc._id, update ?? {}) : await (async () => {
|
|
433
|
+
await ctx.db.patch(doc._id, update);
|
|
434
|
+
return ctx.db.get(doc._id);
|
|
435
|
+
})();
|
|
348
436
|
if (args.onUpdateHandle) {
|
|
349
|
-
const
|
|
350
|
-
await ctx.runMutation(args.onUpdateHandle, {
|
|
437
|
+
const hookNewDoc = ormTable ? withBothIdFields(newDoc) : newDoc;
|
|
438
|
+
await ctx.runMutation(args.onUpdateHandle, serializeDatesForConvex({
|
|
351
439
|
model: args.input.model,
|
|
352
|
-
newDoc,
|
|
440
|
+
newDoc: hookNewDoc,
|
|
353
441
|
oldDoc: doc
|
|
354
|
-
});
|
|
442
|
+
}));
|
|
355
443
|
}
|
|
356
444
|
});
|
|
357
445
|
}
|
|
358
|
-
return {
|
|
446
|
+
return toConvexSafe({
|
|
359
447
|
...result,
|
|
360
448
|
count: page.length,
|
|
361
449
|
ids: page.map((doc) => doc._id)
|
|
362
|
-
};
|
|
450
|
+
});
|
|
363
451
|
};
|
|
364
452
|
const deleteOneHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
365
453
|
const doc = await listOne(ctx, schema, betterAuthSchema, args.input);
|
|
366
454
|
if (!doc) return;
|
|
367
455
|
let hookDoc = doc;
|
|
368
456
|
if (!args.skipBeforeHooks && args.beforeDeleteHandle) {
|
|
369
|
-
const transformedDoc = await ctx.runMutation(args.beforeDeleteHandle, {
|
|
457
|
+
const transformedDoc = await ctx.runMutation(args.beforeDeleteHandle, serializeDatesForConvex({
|
|
370
458
|
doc,
|
|
371
459
|
model: args.input.model
|
|
372
|
-
});
|
|
460
|
+
}));
|
|
373
461
|
if (transformedDoc !== void 0) hookDoc = transformedDoc;
|
|
374
462
|
}
|
|
375
|
-
|
|
376
|
-
if (
|
|
463
|
+
const ormTable = resolveOrmTable(ctx, schema, betterAuthSchema, args.input.model);
|
|
464
|
+
if (ormTable) await ormDelete(ctx, ormTable.table, doc._id);
|
|
465
|
+
else await ctx.db.delete(doc._id);
|
|
466
|
+
if (args.onDeleteHandle) await ctx.runMutation(args.onDeleteHandle, serializeDatesForConvex({
|
|
377
467
|
doc: hookDoc,
|
|
378
468
|
model: args.input.model
|
|
379
|
-
});
|
|
380
|
-
return hookDoc;
|
|
469
|
+
}));
|
|
470
|
+
return toConvexSafe(hookDoc);
|
|
381
471
|
};
|
|
382
472
|
const deleteManyHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
383
473
|
const { page, ...result } = await paginate(ctx, schema, betterAuthSchema, {
|
|
384
474
|
...args.input,
|
|
385
475
|
paginationOpts: args.paginationOpts
|
|
386
476
|
});
|
|
477
|
+
const ormTable = resolveOrmTable(ctx, schema, betterAuthSchema, args.input.model);
|
|
387
478
|
await asyncMap(page, async (doc) => {
|
|
388
479
|
let hookDoc = doc;
|
|
389
480
|
if (!args.skipBeforeHooks && args.beforeDeleteHandle) {
|
|
390
|
-
const transformedDoc = await ctx.runMutation(args.beforeDeleteHandle, {
|
|
481
|
+
const transformedDoc = await ctx.runMutation(args.beforeDeleteHandle, serializeDatesForConvex({
|
|
391
482
|
doc,
|
|
392
483
|
model: args.input.model
|
|
393
|
-
});
|
|
484
|
+
}));
|
|
394
485
|
if (transformedDoc !== void 0) hookDoc = transformedDoc;
|
|
395
486
|
}
|
|
396
|
-
await ctx.
|
|
397
|
-
|
|
487
|
+
if (ormTable) await ormDelete(ctx, ormTable.table, doc._id);
|
|
488
|
+
else await ctx.db.delete(doc._id);
|
|
489
|
+
if (args.onDeleteHandle) await ctx.runMutation(args.onDeleteHandle, serializeDatesForConvex({
|
|
398
490
|
doc: hookDoc,
|
|
399
491
|
model: args.input.model
|
|
400
|
-
});
|
|
492
|
+
}));
|
|
401
493
|
});
|
|
402
|
-
return {
|
|
494
|
+
return toConvexSafe({
|
|
403
495
|
...result,
|
|
404
496
|
count: page.length,
|
|
405
497
|
ids: page.map((doc) => doc._id)
|
|
406
|
-
};
|
|
498
|
+
});
|
|
407
499
|
};
|
|
408
|
-
const createApi = (schema,
|
|
409
|
-
const betterAuthSchema = getAuthTables(
|
|
410
|
-
const { internalMutation, skipValidation } = options ?? {};
|
|
411
|
-
const
|
|
500
|
+
const createApi = (schema, getAuth, options) => {
|
|
501
|
+
const betterAuthSchema = getAuthTables(getAuth({}).options);
|
|
502
|
+
const { internalMutation, skipValidation, context } = options ?? {};
|
|
503
|
+
const mutationBuilderBase = internalMutation ?? internalMutationGeneric;
|
|
504
|
+
const mutationBuilder = context ? customMutation(mutationBuilderBase, customCtx(async (ctx) => await context?.(ctx) ?? ctx)) : mutationBuilderBase;
|
|
412
505
|
const anyInput = v.object({
|
|
413
506
|
data: v.any(),
|
|
414
507
|
model: v.string()
|
|
@@ -516,13 +609,13 @@ const createApi = (schema, createAuth, options) => {
|
|
|
516
609
|
getLatestJwks: internalActionGeneric({
|
|
517
610
|
args: {},
|
|
518
611
|
handler: async (ctx) => {
|
|
519
|
-
return
|
|
612
|
+
return getAuth(ctx).api.getLatestJwks();
|
|
520
613
|
}
|
|
521
614
|
}),
|
|
522
615
|
rotateKeys: internalActionGeneric({
|
|
523
616
|
args: {},
|
|
524
617
|
handler: async (ctx) => {
|
|
525
|
-
return
|
|
618
|
+
return getAuth(ctx).api.rotateKeys();
|
|
526
619
|
}
|
|
527
620
|
})
|
|
528
621
|
};
|
|
@@ -569,6 +662,10 @@ const parseWhere = (where) => {
|
|
|
569
662
|
return w;
|
|
570
663
|
});
|
|
571
664
|
};
|
|
665
|
+
const uniqueDocs = (docs) => uniqueBy(docs, (doc) => {
|
|
666
|
+
if (doc && typeof doc === "object") return doc._id ?? doc.id ?? doc;
|
|
667
|
+
return doc;
|
|
668
|
+
});
|
|
572
669
|
const adapterConfig = {
|
|
573
670
|
adapterId: "convex",
|
|
574
671
|
adapterName: "Convex Adapter",
|
|
@@ -591,19 +688,36 @@ const adapterConfig = {
|
|
|
591
688
|
return data;
|
|
592
689
|
}
|
|
593
690
|
};
|
|
594
|
-
const
|
|
691
|
+
const ORM_SCHEMA_OPTIONS = Symbol.for("better-convex:OrmSchemaOptions");
|
|
692
|
+
const hasOrmSchemaMetadata = (schema) => !!schema && typeof schema === "object" && ORM_SCHEMA_OPTIONS in schema;
|
|
693
|
+
const createAuthSchema = async ({ file, schema, tables }) => {
|
|
694
|
+
if (hasOrmSchemaMetadata(schema)) {
|
|
695
|
+
const { createSchemaOrm } = await import("../create-schema-orm-DplxTtYj.js");
|
|
696
|
+
return createSchemaOrm({
|
|
697
|
+
file,
|
|
698
|
+
tables
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
const { createSchema } = await import("../create-schema-DhWXOhnU.js");
|
|
702
|
+
return createSchema({
|
|
703
|
+
file,
|
|
704
|
+
tables
|
|
705
|
+
});
|
|
706
|
+
};
|
|
707
|
+
const httpAdapter = (ctx, { authFunctions, debugLogs, schema, triggers }) => {
|
|
595
708
|
return createAdapterFactory({
|
|
596
709
|
config: {
|
|
597
710
|
...adapterConfig,
|
|
598
711
|
debugLogs: debugLogs || false
|
|
599
712
|
},
|
|
600
713
|
adapter: ({ options }) => {
|
|
714
|
+
const getTriggers = (model) => triggers?.[model];
|
|
601
715
|
options.telemetry = { enabled: false };
|
|
602
716
|
return {
|
|
603
717
|
id: "convex",
|
|
604
718
|
options: { isRunMutationCtx: isRunMutationCtx(ctx) },
|
|
605
719
|
count: async (data) => {
|
|
606
|
-
if (data.where?.some((w) => w.connector === "OR")) return
|
|
720
|
+
if (data.where?.some((w) => w.connector === "OR")) return uniqueDocs((await asyncMap(data.where, async (w) => handlePagination(async ({ paginationOpts }) => await ctx.runQuery(authFunctions.findMany, {
|
|
607
721
|
...data,
|
|
608
722
|
paginationOpts,
|
|
609
723
|
where: parseWhere(w)
|
|
@@ -616,8 +730,8 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, triggers }) => {
|
|
|
616
730
|
},
|
|
617
731
|
create: async ({ data, model, select }) => {
|
|
618
732
|
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
619
|
-
const onCreateHandle = authFunctions.onCreate &&
|
|
620
|
-
const beforeCreateHandle = authFunctions.beforeCreate &&
|
|
733
|
+
const onCreateHandle = authFunctions.onCreate && getTriggers(model)?.onCreate ? await createFunctionHandle(authFunctions.onCreate) : void 0;
|
|
734
|
+
const beforeCreateHandle = authFunctions.beforeCreate && getTriggers(model)?.beforeCreate ? await createFunctionHandle(authFunctions.beforeCreate) : void 0;
|
|
621
735
|
return await ctx.runMutation(authFunctions.create, {
|
|
622
736
|
beforeCreateHandle,
|
|
623
737
|
input: {
|
|
@@ -628,17 +742,15 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, triggers }) => {
|
|
|
628
742
|
onCreateHandle
|
|
629
743
|
});
|
|
630
744
|
},
|
|
631
|
-
createSchema: async ({ file, tables }) => {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
});
|
|
637
|
-
},
|
|
745
|
+
createSchema: async ({ file, tables }) => createAuthSchema({
|
|
746
|
+
file,
|
|
747
|
+
schema,
|
|
748
|
+
tables
|
|
749
|
+
}),
|
|
638
750
|
delete: async (data) => {
|
|
639
751
|
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
640
|
-
const onDeleteHandle = authFunctions.onDelete &&
|
|
641
|
-
const beforeDeleteHandle = authFunctions.beforeDelete &&
|
|
752
|
+
const onDeleteHandle = authFunctions.onDelete && getTriggers(data.model)?.onDelete ? await createFunctionHandle(authFunctions.onDelete) : void 0;
|
|
753
|
+
const beforeDeleteHandle = authFunctions.beforeDelete && getTriggers(data.model)?.beforeDelete ? await createFunctionHandle(authFunctions.beforeDelete) : void 0;
|
|
642
754
|
await ctx.runMutation(authFunctions.deleteOne, {
|
|
643
755
|
beforeDeleteHandle,
|
|
644
756
|
input: {
|
|
@@ -650,8 +762,8 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, triggers }) => {
|
|
|
650
762
|
},
|
|
651
763
|
deleteMany: async (data) => {
|
|
652
764
|
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
653
|
-
const onDeleteHandle = authFunctions.onDelete &&
|
|
654
|
-
const beforeDeleteHandle = authFunctions.beforeDelete &&
|
|
765
|
+
const onDeleteHandle = authFunctions.onDelete && getTriggers(data.model)?.onDelete ? await createFunctionHandle(authFunctions.onDelete) : void 0;
|
|
766
|
+
const beforeDeleteHandle = authFunctions.beforeDelete && getTriggers(data.model)?.beforeDelete ? await createFunctionHandle(authFunctions.beforeDelete) : void 0;
|
|
655
767
|
return (await handlePagination(async ({ paginationOpts }) => await ctx.runMutation(authFunctions.deleteMany, {
|
|
656
768
|
beforeDeleteHandle,
|
|
657
769
|
input: {
|
|
@@ -665,7 +777,7 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, triggers }) => {
|
|
|
665
777
|
findMany: async (data) => {
|
|
666
778
|
if (data.offset) throw new Error("offset not supported");
|
|
667
779
|
if (data.where?.some((w) => w.connector === "OR")) {
|
|
668
|
-
const docs =
|
|
780
|
+
const docs = uniqueDocs((await asyncMap(data.where, async (w) => handlePagination(async ({ paginationOpts }) => await ctx.runQuery(authFunctions.findMany, {
|
|
669
781
|
...data,
|
|
670
782
|
paginationOpts,
|
|
671
783
|
where: parseWhere(w)
|
|
@@ -703,8 +815,8 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, triggers }) => {
|
|
|
703
815
|
}), { limit: 2 });
|
|
704
816
|
if (countResult.docs.length === 0) throw new Error(`No ${data.model} found matching criteria`);
|
|
705
817
|
if (countResult.docs.length > 1) throw new Error(`Multiple ${data.model} found matching criteria. Expected exactly 1.`);
|
|
706
|
-
const onUpdateHandle = authFunctions.onUpdate &&
|
|
707
|
-
const beforeUpdateHandle = authFunctions.beforeUpdate &&
|
|
818
|
+
const onUpdateHandle = authFunctions.onUpdate && getTriggers(data.model)?.onUpdate ? await createFunctionHandle(authFunctions.onUpdate) : void 0;
|
|
819
|
+
const beforeUpdateHandle = authFunctions.beforeUpdate && getTriggers(data.model)?.beforeUpdate ? await createFunctionHandle(authFunctions.beforeUpdate) : void 0;
|
|
708
820
|
return await ctx.runMutation(authFunctions.updateOne, {
|
|
709
821
|
beforeUpdateHandle,
|
|
710
822
|
input: {
|
|
@@ -719,8 +831,8 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, triggers }) => {
|
|
|
719
831
|
},
|
|
720
832
|
updateMany: async (data) => {
|
|
721
833
|
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
722
|
-
const onUpdateHandle = authFunctions.onUpdate &&
|
|
723
|
-
const beforeUpdateHandle = authFunctions.beforeUpdate &&
|
|
834
|
+
const onUpdateHandle = authFunctions.onUpdate && getTriggers(data.model)?.onUpdate ? await createFunctionHandle(authFunctions.onUpdate) : void 0;
|
|
835
|
+
const beforeUpdateHandle = authFunctions.beforeUpdate && getTriggers(data.model)?.beforeUpdate ? await createFunctionHandle(authFunctions.beforeUpdate) : void 0;
|
|
724
836
|
return (await handlePagination(async ({ paginationOpts }) => await ctx.runMutation(authFunctions.updateMany, {
|
|
725
837
|
beforeUpdateHandle,
|
|
726
838
|
input: {
|
|
@@ -735,20 +847,21 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, triggers }) => {
|
|
|
735
847
|
}
|
|
736
848
|
});
|
|
737
849
|
};
|
|
738
|
-
const dbAdapter = (ctx,
|
|
739
|
-
const betterAuthSchema = getAuthTables(
|
|
850
|
+
const dbAdapter = (ctx, getAuthOptions, { authFunctions, debugLogs, schema, triggers }) => {
|
|
851
|
+
const betterAuthSchema = getAuthTables(getAuthOptions({}));
|
|
740
852
|
return createAdapterFactory({
|
|
741
853
|
config: {
|
|
742
854
|
...adapterConfig,
|
|
743
855
|
debugLogs: debugLogs || false
|
|
744
856
|
},
|
|
745
857
|
adapter: ({ options }) => {
|
|
858
|
+
const getTriggers = (model) => triggers?.[model];
|
|
746
859
|
options.telemetry = { enabled: false };
|
|
747
860
|
return {
|
|
748
861
|
id: "convex",
|
|
749
862
|
options: { isRunMutationCtx: isRunMutationCtx(ctx) },
|
|
750
863
|
count: async (data) => {
|
|
751
|
-
if (data.where?.some((w) => w.connector === "OR")) return
|
|
864
|
+
if (data.where?.some((w) => w.connector === "OR")) return uniqueDocs((await asyncMap(data.where, async (w) => handlePagination(async ({ paginationOpts }) => await findManyHandler(ctx, {
|
|
752
865
|
...data,
|
|
753
866
|
paginationOpts,
|
|
754
867
|
where: parseWhere(w)
|
|
@@ -760,9 +873,9 @@ const dbAdapter = (ctx, createAuthOptions, { authFunctions, debugLogs, schema, t
|
|
|
760
873
|
}, schema, betterAuthSchema))).docs.length;
|
|
761
874
|
},
|
|
762
875
|
create: async ({ data, model, select }) => {
|
|
763
|
-
const onCreateHandle = authFunctions.onCreate &&
|
|
876
|
+
const onCreateHandle = authFunctions.onCreate && getTriggers(model)?.onCreate ? await createFunctionHandle(authFunctions.onCreate) : void 0;
|
|
764
877
|
return await createHandler(ctx, {
|
|
765
|
-
beforeCreateHandle: authFunctions.beforeCreate &&
|
|
878
|
+
beforeCreateHandle: authFunctions.beforeCreate && getTriggers(model)?.beforeCreate ? await createFunctionHandle(authFunctions.beforeCreate) : void 0,
|
|
766
879
|
input: {
|
|
767
880
|
data,
|
|
768
881
|
model
|
|
@@ -771,17 +884,15 @@ const dbAdapter = (ctx, createAuthOptions, { authFunctions, debugLogs, schema, t
|
|
|
771
884
|
onCreateHandle
|
|
772
885
|
}, schema, betterAuthSchema);
|
|
773
886
|
},
|
|
774
|
-
createSchema: async ({ file, tables }) => {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
});
|
|
780
|
-
},
|
|
887
|
+
createSchema: async ({ file, tables }) => createAuthSchema({
|
|
888
|
+
file,
|
|
889
|
+
schema,
|
|
890
|
+
tables
|
|
891
|
+
}),
|
|
781
892
|
delete: async (data) => {
|
|
782
|
-
const onDeleteHandle = authFunctions.onDelete &&
|
|
893
|
+
const onDeleteHandle = authFunctions.onDelete && getTriggers(data.model)?.onDelete ? await createFunctionHandle(authFunctions.onDelete) : void 0;
|
|
783
894
|
await deleteOneHandler(ctx, {
|
|
784
|
-
beforeDeleteHandle: authFunctions.beforeDelete &&
|
|
895
|
+
beforeDeleteHandle: authFunctions.beforeDelete && getTriggers(data.model)?.beforeDelete ? await createFunctionHandle(authFunctions.beforeDelete) : void 0,
|
|
785
896
|
input: {
|
|
786
897
|
model: data.model,
|
|
787
898
|
where: parseWhere(data.where)
|
|
@@ -790,8 +901,8 @@ const dbAdapter = (ctx, createAuthOptions, { authFunctions, debugLogs, schema, t
|
|
|
790
901
|
}, schema, betterAuthSchema);
|
|
791
902
|
},
|
|
792
903
|
deleteMany: async (data) => {
|
|
793
|
-
const onDeleteHandle = authFunctions.onDelete &&
|
|
794
|
-
const beforeDeleteHandle = authFunctions.beforeDelete &&
|
|
904
|
+
const onDeleteHandle = authFunctions.onDelete && getTriggers(data.model)?.onDelete ? await createFunctionHandle(authFunctions.onDelete) : void 0;
|
|
905
|
+
const beforeDeleteHandle = authFunctions.beforeDelete && getTriggers(data.model)?.beforeDelete ? await createFunctionHandle(authFunctions.beforeDelete) : void 0;
|
|
795
906
|
return (await handlePagination(async ({ paginationOpts }) => await deleteManyHandler(ctx, {
|
|
796
907
|
beforeDeleteHandle,
|
|
797
908
|
input: {
|
|
@@ -805,7 +916,7 @@ const dbAdapter = (ctx, createAuthOptions, { authFunctions, debugLogs, schema, t
|
|
|
805
916
|
findMany: async (data) => {
|
|
806
917
|
if (data.offset) throw new Error("offset not supported");
|
|
807
918
|
if (data.where?.some((w) => w.connector === "OR")) {
|
|
808
|
-
const docs =
|
|
919
|
+
const docs = uniqueDocs((await asyncMap(data.where, async (w) => handlePagination(async ({ paginationOpts }) => await findManyHandler(ctx, {
|
|
809
920
|
...data,
|
|
810
921
|
paginationOpts,
|
|
811
922
|
where: parseWhere(w)
|
|
@@ -841,9 +952,9 @@ const dbAdapter = (ctx, createAuthOptions, { authFunctions, debugLogs, schema, t
|
|
|
841
952
|
}, schema, betterAuthSchema), { limit: 2 });
|
|
842
953
|
if (countResult.docs.length === 0) throw new Error(`No ${data.model} found matching criteria`);
|
|
843
954
|
if (countResult.docs.length > 1) throw new Error(`Multiple ${data.model} found matching criteria. Expected exactly 1.`);
|
|
844
|
-
const onUpdateHandle = authFunctions.onUpdate &&
|
|
955
|
+
const onUpdateHandle = authFunctions.onUpdate && getTriggers(data.model)?.onUpdate ? await createFunctionHandle(authFunctions.onUpdate) : void 0;
|
|
845
956
|
return await updateOneHandler(ctx, {
|
|
846
|
-
beforeUpdateHandle: authFunctions.beforeUpdate &&
|
|
957
|
+
beforeUpdateHandle: authFunctions.beforeUpdate && getTriggers(data.model)?.beforeUpdate ? await createFunctionHandle(authFunctions.beforeUpdate) : void 0,
|
|
847
958
|
input: {
|
|
848
959
|
model: data.model,
|
|
849
960
|
update: data.update,
|
|
@@ -855,8 +966,8 @@ const dbAdapter = (ctx, createAuthOptions, { authFunctions, debugLogs, schema, t
|
|
|
855
966
|
throw new Error("where clause not supported");
|
|
856
967
|
},
|
|
857
968
|
updateMany: async (data) => {
|
|
858
|
-
const onUpdateHandle = authFunctions.onUpdate &&
|
|
859
|
-
const beforeUpdateHandle = authFunctions.beforeUpdate &&
|
|
969
|
+
const onUpdateHandle = authFunctions.onUpdate && getTriggers(data.model)?.onUpdate ? await createFunctionHandle(authFunctions.onUpdate) : void 0;
|
|
970
|
+
const beforeUpdateHandle = authFunctions.beforeUpdate && getTriggers(data.model)?.beforeUpdate ? await createFunctionHandle(authFunctions.beforeUpdate) : void 0;
|
|
860
971
|
return (await handlePagination(async ({ paginationOpts }) => await updateManyHandler(ctx, {
|
|
861
972
|
beforeUpdateHandle,
|
|
862
973
|
input: {
|
|
@@ -877,24 +988,34 @@ const dbAdapter = (ctx, createAuthOptions, { authFunctions, debugLogs, schema, t
|
|
|
877
988
|
const createClient = (config) => ({
|
|
878
989
|
authFunctions: config.authFunctions,
|
|
879
990
|
triggers: config.triggers,
|
|
880
|
-
adapter: (ctx,
|
|
881
|
-
httpAdapter: (ctx) => httpAdapter(ctx, config),
|
|
991
|
+
adapter: (ctx, getAuthOptions) => isQueryCtx(ctx) ? dbAdapter(ctx, getAuthOptions, config) : httpAdapter(ctx, config),
|
|
882
992
|
triggersApi: () => {
|
|
883
|
-
const
|
|
993
|
+
const mutationBuilderBase = config.internalMutation ?? internalMutationGeneric;
|
|
994
|
+
const hasMutationCtxTransforms = config.context !== void 0;
|
|
995
|
+
const transformMutationCtx = async (ctx) => await config.context?.(ctx) ?? ctx;
|
|
996
|
+
const mutationBuilder = hasMutationCtxTransforms ? customMutation(mutationBuilderBase, customCtx(async (ctx) => await transformMutationCtx(ctx))) : mutationBuilderBase;
|
|
997
|
+
const getTriggers = (model) => config.triggers?.[model];
|
|
998
|
+
const resolveTriggerCtx = async (ctx) => hasMutationCtxTransforms ? ctx : await transformMutationCtx(ctx);
|
|
884
999
|
return {
|
|
885
1000
|
beforeCreate: mutationBuilder({
|
|
886
1001
|
args: {
|
|
887
1002
|
data: v.any(),
|
|
888
1003
|
model: v.string()
|
|
889
1004
|
},
|
|
890
|
-
handler: async (ctx, args) =>
|
|
1005
|
+
handler: async (ctx, args) => {
|
|
1006
|
+
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1007
|
+
return await getTriggers(args.model)?.beforeCreate?.(triggerCtx, args.data) ?? args.data;
|
|
1008
|
+
}
|
|
891
1009
|
}),
|
|
892
1010
|
beforeDelete: mutationBuilder({
|
|
893
1011
|
args: {
|
|
894
1012
|
doc: v.any(),
|
|
895
1013
|
model: v.string()
|
|
896
1014
|
},
|
|
897
|
-
handler: async (ctx, args) =>
|
|
1015
|
+
handler: async (ctx, args) => {
|
|
1016
|
+
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1017
|
+
return await getTriggers(args.model)?.beforeDelete?.(triggerCtx, args.doc) ?? args.doc;
|
|
1018
|
+
}
|
|
898
1019
|
}),
|
|
899
1020
|
beforeUpdate: mutationBuilder({
|
|
900
1021
|
args: {
|
|
@@ -902,7 +1023,10 @@ const createClient = (config) => ({
|
|
|
902
1023
|
model: v.string(),
|
|
903
1024
|
update: v.any()
|
|
904
1025
|
},
|
|
905
|
-
handler: async (ctx, args) =>
|
|
1026
|
+
handler: async (ctx, args) => {
|
|
1027
|
+
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1028
|
+
return await getTriggers(args.model)?.beforeUpdate?.(triggerCtx, args.doc, args.update) ?? args.update;
|
|
1029
|
+
}
|
|
906
1030
|
}),
|
|
907
1031
|
onCreate: mutationBuilder({
|
|
908
1032
|
args: {
|
|
@@ -910,7 +1034,8 @@ const createClient = (config) => ({
|
|
|
910
1034
|
model: v.string()
|
|
911
1035
|
},
|
|
912
1036
|
handler: async (ctx, args) => {
|
|
913
|
-
await
|
|
1037
|
+
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1038
|
+
await getTriggers(args.model)?.onCreate?.(triggerCtx, args.doc);
|
|
914
1039
|
}
|
|
915
1040
|
}),
|
|
916
1041
|
onDelete: mutationBuilder({
|
|
@@ -919,7 +1044,8 @@ const createClient = (config) => ({
|
|
|
919
1044
|
model: v.string()
|
|
920
1045
|
},
|
|
921
1046
|
handler: async (ctx, args) => {
|
|
922
|
-
await
|
|
1047
|
+
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1048
|
+
await getTriggers(args.model)?.onDelete?.(triggerCtx, args.doc);
|
|
923
1049
|
}
|
|
924
1050
|
}),
|
|
925
1051
|
onUpdate: mutationBuilder({
|
|
@@ -929,7 +1055,8 @@ const createClient = (config) => ({
|
|
|
929
1055
|
oldDoc: v.any()
|
|
930
1056
|
},
|
|
931
1057
|
handler: async (ctx, args) => {
|
|
932
|
-
await
|
|
1058
|
+
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1059
|
+
await getTriggers(args.model)?.onUpdate?.(triggerCtx, args.newDoc, args.oldDoc);
|
|
933
1060
|
}
|
|
934
1061
|
})
|
|
935
1062
|
};
|
|
@@ -959,6 +1086,7 @@ const getSession = async (ctx, _sessionId) => {
|
|
|
959
1086
|
if (!identity) return null;
|
|
960
1087
|
sessionId = identity.sessionId;
|
|
961
1088
|
}
|
|
1089
|
+
if (!sessionId) return null;
|
|
962
1090
|
return await ctx.db.get(sessionId);
|
|
963
1091
|
};
|
|
964
1092
|
const getHeaders = async (ctx, session) => {
|
|
@@ -984,18 +1112,18 @@ const getHeaders = async (ctx, session) => {
|
|
|
984
1112
|
*
|
|
985
1113
|
* const app = new Hono();
|
|
986
1114
|
* app.use('/api/*', cors({ origin: process.env.SITE_URL, credentials: true }));
|
|
987
|
-
* app.use(authMiddleware(
|
|
1115
|
+
* app.use(authMiddleware(getAuth));
|
|
988
1116
|
*
|
|
989
1117
|
* export default createHttpRouter(app, appRouter);
|
|
990
1118
|
* ```
|
|
991
1119
|
*/
|
|
992
|
-
function authMiddleware(
|
|
1120
|
+
function authMiddleware(getAuth, opts = {}) {
|
|
993
1121
|
const basePath = opts.basePath ?? "/api/auth";
|
|
994
1122
|
return async (c, next) => {
|
|
995
1123
|
if (c.req.path === "/.well-known/openid-configuration") return c.redirect(`${process.env.CONVEX_SITE_URL}${basePath}/convex/.well-known/openid-configuration`);
|
|
996
1124
|
if (c.req.path.startsWith(basePath)) {
|
|
997
1125
|
if (opts.verbose) console.log("request headers", c.req.raw.headers);
|
|
998
|
-
const response = await
|
|
1126
|
+
const response = await getAuth(c.env).handler(c.req.raw);
|
|
999
1127
|
if (opts.verbose) console.log("response headers", response.headers);
|
|
1000
1128
|
return response;
|
|
1001
1129
|
}
|
|
@@ -1004,4 +1132,326 @@ function authMiddleware(createAuth, opts = {}) {
|
|
|
1004
1132
|
}
|
|
1005
1133
|
|
|
1006
1134
|
//#endregion
|
|
1007
|
-
|
|
1135
|
+
//#region src/internal/upstream/server/cors.ts
|
|
1136
|
+
/** biome-ignore-all lint: vendored upstream helper source */
|
|
1137
|
+
/**
|
|
1138
|
+
* Vendored from upstream helper repository at commit c5e52c8.
|
|
1139
|
+
* Source path: packages/convex_helpers/server/cors.ts
|
|
1140
|
+
*/
|
|
1141
|
+
/**
|
|
1142
|
+
* This file defines a CorsHttpRouter class that extends Convex's HttpRouter.
|
|
1143
|
+
* It provides CORS (Cross-Origin Resource Sharing) support for HTTP routes.
|
|
1144
|
+
*
|
|
1145
|
+
* The CorsHttpRouter:
|
|
1146
|
+
* 1. Allows specifying allowed origins for CORS.
|
|
1147
|
+
* 2. Overrides the route method to add CORS headers to all non-OPTIONS requests.
|
|
1148
|
+
* 3. Automatically adds an OPTIONS route to handle CORS preflight requests.
|
|
1149
|
+
* 4. Uses the handleCors helper function to apply CORS headers consistently.
|
|
1150
|
+
*
|
|
1151
|
+
* This router simplifies the process of making Convex HTTP endpoints
|
|
1152
|
+
* accessible to web applications hosted on different domains while
|
|
1153
|
+
* maintaining proper CORS configuration.
|
|
1154
|
+
*/
|
|
1155
|
+
const DEFAULT_EXPOSED_HEADERS = ["Content-Range", "Accept-Ranges"];
|
|
1156
|
+
/**
|
|
1157
|
+
* Factory function to create a router that adds CORS support to routes.
|
|
1158
|
+
* @param allowedOrigins An array of allowed origins for CORS.
|
|
1159
|
+
* @returns A function to use instead of http.route when you want CORS.
|
|
1160
|
+
*/
|
|
1161
|
+
const corsRouter = (http, corsConfig) => {
|
|
1162
|
+
const allowedExactMethodsByPath = /* @__PURE__ */ new Map();
|
|
1163
|
+
const allowedPrefixMethodsByPath = /* @__PURE__ */ new Map();
|
|
1164
|
+
return {
|
|
1165
|
+
http,
|
|
1166
|
+
route: (routeSpec) => {
|
|
1167
|
+
const tempRouter = httpRouter();
|
|
1168
|
+
tempRouter.exactRoutes = http.exactRoutes;
|
|
1169
|
+
tempRouter.prefixRoutes = http.prefixRoutes;
|
|
1170
|
+
const config = {
|
|
1171
|
+
...corsConfig,
|
|
1172
|
+
...routeSpec
|
|
1173
|
+
};
|
|
1174
|
+
const httpCorsHandler = handleCors({
|
|
1175
|
+
originalHandler: routeSpec.handler,
|
|
1176
|
+
allowedMethods: [routeSpec.method],
|
|
1177
|
+
...config
|
|
1178
|
+
});
|
|
1179
|
+
/**
|
|
1180
|
+
* Figure out what kind of route we're adding: exact or prefix and handle
|
|
1181
|
+
* accordingly.
|
|
1182
|
+
*/
|
|
1183
|
+
if ("path" in routeSpec) {
|
|
1184
|
+
let methods = allowedExactMethodsByPath.get(routeSpec.path);
|
|
1185
|
+
if (!methods) {
|
|
1186
|
+
methods = /* @__PURE__ */ new Set();
|
|
1187
|
+
allowedExactMethodsByPath.set(routeSpec.path, methods);
|
|
1188
|
+
}
|
|
1189
|
+
methods.add(routeSpec.method);
|
|
1190
|
+
tempRouter.route({
|
|
1191
|
+
path: routeSpec.path,
|
|
1192
|
+
method: routeSpec.method,
|
|
1193
|
+
handler: httpCorsHandler
|
|
1194
|
+
});
|
|
1195
|
+
handleExactRoute(tempRouter, routeSpec, config, Array.from(methods));
|
|
1196
|
+
} else {
|
|
1197
|
+
let methods = allowedPrefixMethodsByPath.get(routeSpec.pathPrefix);
|
|
1198
|
+
if (!methods) {
|
|
1199
|
+
methods = /* @__PURE__ */ new Set();
|
|
1200
|
+
allowedPrefixMethodsByPath.set(routeSpec.pathPrefix, methods);
|
|
1201
|
+
}
|
|
1202
|
+
methods.add(routeSpec.method);
|
|
1203
|
+
tempRouter.route({
|
|
1204
|
+
pathPrefix: routeSpec.pathPrefix,
|
|
1205
|
+
method: routeSpec.method,
|
|
1206
|
+
handler: httpCorsHandler
|
|
1207
|
+
});
|
|
1208
|
+
handlePrefixRoute(tempRouter, routeSpec, config, Array.from(methods));
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Copy the routes from the temporary router to the main router.
|
|
1212
|
+
*/
|
|
1213
|
+
http.exactRoutes = new Map(tempRouter.exactRoutes);
|
|
1214
|
+
http.prefixRoutes = new Map(tempRouter.prefixRoutes);
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
};
|
|
1218
|
+
/**
|
|
1219
|
+
* Handles exact route matching and adds OPTIONS handler.
|
|
1220
|
+
* @param tempRouter Temporary router instance.
|
|
1221
|
+
* @param routeSpec Route specification for exact matching.
|
|
1222
|
+
*/
|
|
1223
|
+
function handleExactRoute(tempRouter, routeSpec, config, allowedMethods) {
|
|
1224
|
+
const currentMethodsForPath = tempRouter.exactRoutes.get(routeSpec.path);
|
|
1225
|
+
/**
|
|
1226
|
+
* Add the OPTIONS handler for the given path
|
|
1227
|
+
*/
|
|
1228
|
+
const optionsHandler = createOptionsHandlerForMethods(allowedMethods, config);
|
|
1229
|
+
currentMethodsForPath?.set("OPTIONS", optionsHandler);
|
|
1230
|
+
tempRouter.exactRoutes.set(routeSpec.path, new Map(currentMethodsForPath));
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Handles prefix route matching and adds OPTIONS handler.
|
|
1234
|
+
* @param tempRouter Temporary router instance.
|
|
1235
|
+
* @param routeSpec Route specification for prefix matching.
|
|
1236
|
+
*/
|
|
1237
|
+
function handlePrefixRoute(tempRouter, routeSpec, config, allowedMethods) {
|
|
1238
|
+
/**
|
|
1239
|
+
* prefixRoutes is structured differently than exactRoutes. It's defined as
|
|
1240
|
+
* a Map<string, Map<string, PublicHttpAction>> where the KEY is the
|
|
1241
|
+
* METHOD and the VALUE is a map of paths and handlers.
|
|
1242
|
+
*/
|
|
1243
|
+
const optionsHandler = createOptionsHandlerForMethods(allowedMethods, config);
|
|
1244
|
+
const optionsPrefixes = tempRouter.prefixRoutes.get("OPTIONS") || /* @__PURE__ */ new Map();
|
|
1245
|
+
optionsPrefixes.set(routeSpec.pathPrefix, optionsHandler);
|
|
1246
|
+
tempRouter.prefixRoutes.set("OPTIONS", optionsPrefixes);
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Creates an OPTIONS handler for the given HTTP methods.
|
|
1250
|
+
* @param methods Array of HTTP methods to be allowed.
|
|
1251
|
+
* @returns A CORS-enabled OPTIONS handler.
|
|
1252
|
+
*/
|
|
1253
|
+
function createOptionsHandlerForMethods(methods, config) {
|
|
1254
|
+
return handleCors({
|
|
1255
|
+
...config,
|
|
1256
|
+
allowedMethods: methods
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* handleCors() is a higher-order function that wraps a Convex HTTP action handler to add CORS support.
|
|
1261
|
+
* It allows for customization of allowed HTTP methods and origins for cross-origin requests.
|
|
1262
|
+
*
|
|
1263
|
+
* The function:
|
|
1264
|
+
* 1. Validates and normalizes the allowed HTTP methods.
|
|
1265
|
+
* 2. Generates appropriate CORS headers based on the provided configuration.
|
|
1266
|
+
* 3. Handles preflight OPTIONS requests automatically.
|
|
1267
|
+
* 4. Wraps the original handler to add CORS headers to its response.
|
|
1268
|
+
*
|
|
1269
|
+
* This helper simplifies the process of making Convex HTTP actions accessible
|
|
1270
|
+
* to web applications hosted on different domains.
|
|
1271
|
+
*/
|
|
1272
|
+
const SECONDS_IN_A_DAY = 3600 * 24;
|
|
1273
|
+
/**
|
|
1274
|
+
* Example CORS origins:
|
|
1275
|
+
* - "*" (allow all origins)
|
|
1276
|
+
* - "https://example.com" (allow a specific domain)
|
|
1277
|
+
* - "https://*.example.com" (allow all subdomains of example.com)
|
|
1278
|
+
* - "https://example1.com, https://example2.com" (allow multiple specific domains)
|
|
1279
|
+
* - "null" (allow requests from data URLs or local files)
|
|
1280
|
+
*/
|
|
1281
|
+
const handleCors = ({ originalHandler, allowedMethods = ["OPTIONS"], allowedOrigins = ["*"], allowedHeaders = ["Content-Type"], exposedHeaders = DEFAULT_EXPOSED_HEADERS, allowCredentials = false, browserCacheMaxAge = SECONDS_IN_A_DAY, enforceAllowOrigins = false, debug = false }) => {
|
|
1282
|
+
const filteredMethods = Array.from(new Set(allowedMethods.map((method) => method.toUpperCase()))).filter((method) => ROUTABLE_HTTP_METHODS.includes(method));
|
|
1283
|
+
if (filteredMethods.length === 0) throw new Error("No valid HTTP methods provided");
|
|
1284
|
+
/**
|
|
1285
|
+
* Ensure OPTIONS is not duplicated if it was passed in
|
|
1286
|
+
* E.g. if allowedMethods = ["GET", "OPTIONS"]
|
|
1287
|
+
*/
|
|
1288
|
+
const allowMethods = filteredMethods.includes("OPTIONS") ? filteredMethods.join(", ") : [...filteredMethods].join(", ");
|
|
1289
|
+
/**
|
|
1290
|
+
* Build up the set of CORS headers
|
|
1291
|
+
*/
|
|
1292
|
+
const commonHeaders = { Vary: "Origin" };
|
|
1293
|
+
if (allowCredentials) commonHeaders["Access-Control-Allow-Credentials"] = "true";
|
|
1294
|
+
if (exposedHeaders.length > 0) commonHeaders["Access-Control-Expose-Headers"] = exposedHeaders.join(", ");
|
|
1295
|
+
async function parseAllowedOrigins(request) {
|
|
1296
|
+
return Array.isArray(allowedOrigins) ? allowedOrigins : await allowedOrigins(request);
|
|
1297
|
+
}
|
|
1298
|
+
async function isAllowedOrigin(request) {
|
|
1299
|
+
const requestOrigin = request.headers.get("origin");
|
|
1300
|
+
if (!requestOrigin) return false;
|
|
1301
|
+
return (await parseAllowedOrigins(request)).some((allowed) => {
|
|
1302
|
+
if (allowed === "*") return true;
|
|
1303
|
+
if (allowed === requestOrigin) return true;
|
|
1304
|
+
if (allowed.startsWith("*.")) {
|
|
1305
|
+
const wildcardDomain = allowed.slice(1);
|
|
1306
|
+
const rootDomain = allowed.slice(2);
|
|
1307
|
+
try {
|
|
1308
|
+
const url = new URL(requestOrigin);
|
|
1309
|
+
return url.protocol === "https:" && (url.hostname.endsWith(wildcardDomain) || url.hostname === rootDomain);
|
|
1310
|
+
} catch {
|
|
1311
|
+
return false;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return false;
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Return our modified HTTP action
|
|
1319
|
+
*/
|
|
1320
|
+
return httpActionGeneric(async (ctx, request) => {
|
|
1321
|
+
if (debug) console.log("CORS request", {
|
|
1322
|
+
path: request.url,
|
|
1323
|
+
origin: request.headers.get("origin"),
|
|
1324
|
+
headers: request.headers,
|
|
1325
|
+
method: request.method,
|
|
1326
|
+
body: request.body
|
|
1327
|
+
});
|
|
1328
|
+
const requestOrigin = request.headers.get("origin");
|
|
1329
|
+
const parsedAllowedOrigins = await parseAllowedOrigins(request);
|
|
1330
|
+
if (debug) console.log("allowed origins", parsedAllowedOrigins);
|
|
1331
|
+
let allowOrigins = null;
|
|
1332
|
+
if (parsedAllowedOrigins.includes("*") && requestOrigin && !allowCredentials) allowOrigins = requestOrigin;
|
|
1333
|
+
else if (requestOrigin) {
|
|
1334
|
+
if (await isAllowedOrigin(request)) allowOrigins = requestOrigin;
|
|
1335
|
+
}
|
|
1336
|
+
if (enforceAllowOrigins && !allowOrigins) {
|
|
1337
|
+
console.error(`Request from origin ${requestOrigin} blocked, missing from allowed origins: ${parsedAllowedOrigins.join()}`);
|
|
1338
|
+
return new Response(null, { status: 403 });
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* OPTIONS has no handler and just returns headers
|
|
1342
|
+
*/
|
|
1343
|
+
if (request.method === "OPTIONS") {
|
|
1344
|
+
const responseHeaders = new Headers({
|
|
1345
|
+
...commonHeaders,
|
|
1346
|
+
...allowOrigins ? { "Access-Control-Allow-Origin": allowOrigins } : {},
|
|
1347
|
+
"Access-Control-Allow-Methods": allowMethods,
|
|
1348
|
+
"Access-Control-Allow-Headers": allowedHeaders.join(", "),
|
|
1349
|
+
"Access-Control-Max-Age": browserCacheMaxAge.toString()
|
|
1350
|
+
});
|
|
1351
|
+
if (debug) console.log("CORS OPTIONS response headers", responseHeaders);
|
|
1352
|
+
return new Response(null, {
|
|
1353
|
+
status: 204,
|
|
1354
|
+
headers: responseHeaders
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* If the method is not OPTIONS, it must pass a handler
|
|
1359
|
+
*/
|
|
1360
|
+
if (!originalHandler) throw new Error("No PublicHttpAction provider to CORS handler");
|
|
1361
|
+
const originalResponse = await ("_handler" in originalHandler ? originalHandler["_handler"] : originalHandler)(ctx, request);
|
|
1362
|
+
/**
|
|
1363
|
+
* Second, get a copy of the original response's headers and add the
|
|
1364
|
+
* allow origin header if it's allowed
|
|
1365
|
+
*/
|
|
1366
|
+
const newHeaders = new Headers(originalResponse.headers);
|
|
1367
|
+
if (allowOrigins) newHeaders.set("Access-Control-Allow-Origin", allowOrigins);
|
|
1368
|
+
/**
|
|
1369
|
+
* Third, add or update our other CORS headers
|
|
1370
|
+
*/
|
|
1371
|
+
Object.entries(commonHeaders).forEach(([key, value]) => {
|
|
1372
|
+
newHeaders.set(key, value);
|
|
1373
|
+
});
|
|
1374
|
+
if (debug) console.log("CORS response headers", newHeaders);
|
|
1375
|
+
/**
|
|
1376
|
+
* Fourth, return the modified Response.
|
|
1377
|
+
* A Response object is immutable, so we create a new one to return here.
|
|
1378
|
+
*/
|
|
1379
|
+
return new Response(originalResponse.body, {
|
|
1380
|
+
status: originalResponse.status,
|
|
1381
|
+
statusText: originalResponse.statusText,
|
|
1382
|
+
headers: newHeaders
|
|
1383
|
+
});
|
|
1384
|
+
});
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
//#endregion
|
|
1388
|
+
//#region src/auth/registerRoutes.ts
|
|
1389
|
+
/** biome-ignore-all lint/suspicious/noConsole: lib */
|
|
1390
|
+
const registerRoutes = (http, getAuth, opts = {}) => {
|
|
1391
|
+
const staticAuth = getAuth({});
|
|
1392
|
+
const path = staticAuth.options.basePath ?? "/api/auth";
|
|
1393
|
+
const authRequestHandler = httpActionGeneric(async (ctx, request) => {
|
|
1394
|
+
if (opts?.verbose) {
|
|
1395
|
+
console.log("options.baseURL", staticAuth.options.baseURL);
|
|
1396
|
+
console.log("request headers", request.headers);
|
|
1397
|
+
}
|
|
1398
|
+
const response = await getAuth(ctx).handler(request);
|
|
1399
|
+
if (opts?.verbose) console.log("response headers", response.headers);
|
|
1400
|
+
return response;
|
|
1401
|
+
});
|
|
1402
|
+
if (!http.lookup("/.well-known/openid-configuration", "GET")) http.route({
|
|
1403
|
+
handler: httpActionGeneric(async () => {
|
|
1404
|
+
const url = `${process.env.CONVEX_SITE_URL}${path}/convex/.well-known/openid-configuration`;
|
|
1405
|
+
return Response.redirect(url);
|
|
1406
|
+
}),
|
|
1407
|
+
method: "GET",
|
|
1408
|
+
path: "/.well-known/openid-configuration"
|
|
1409
|
+
});
|
|
1410
|
+
if (!opts.cors) {
|
|
1411
|
+
http.route({
|
|
1412
|
+
handler: authRequestHandler,
|
|
1413
|
+
method: "GET",
|
|
1414
|
+
pathPrefix: `${path}/`
|
|
1415
|
+
});
|
|
1416
|
+
http.route({
|
|
1417
|
+
handler: authRequestHandler,
|
|
1418
|
+
method: "POST",
|
|
1419
|
+
pathPrefix: `${path}/`
|
|
1420
|
+
});
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
const corsOpts = typeof opts.cors === "boolean" ? {
|
|
1424
|
+
allowedHeaders: [],
|
|
1425
|
+
allowedOrigins: [],
|
|
1426
|
+
exposedHeaders: []
|
|
1427
|
+
} : opts.cors;
|
|
1428
|
+
let trustedOriginsOption;
|
|
1429
|
+
const cors = corsRouter(http, {
|
|
1430
|
+
allowCredentials: true,
|
|
1431
|
+
allowedHeaders: [
|
|
1432
|
+
"Content-Type",
|
|
1433
|
+
"Better-Auth-Cookie",
|
|
1434
|
+
"Authorization"
|
|
1435
|
+
].concat(corsOpts.allowedHeaders ?? []),
|
|
1436
|
+
debug: opts?.verbose,
|
|
1437
|
+
enforceAllowOrigins: false,
|
|
1438
|
+
exposedHeaders: ["Set-Better-Auth-Cookie"].concat(corsOpts.exposedHeaders ?? []),
|
|
1439
|
+
allowedOrigins: async (request) => {
|
|
1440
|
+
trustedOriginsOption = trustedOriginsOption ?? (await staticAuth.$context).options.trustedOrigins ?? [];
|
|
1441
|
+
return (Array.isArray(trustedOriginsOption) ? trustedOriginsOption : await trustedOriginsOption?.(request) ?? []).map((origin) => origin.endsWith("*") && origin.length > 1 ? origin.slice(0, -1) : origin).concat(corsOpts.allowedOrigins ?? []);
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
cors.route({
|
|
1445
|
+
handler: authRequestHandler,
|
|
1446
|
+
method: "GET",
|
|
1447
|
+
pathPrefix: `${path}/`
|
|
1448
|
+
});
|
|
1449
|
+
cors.route({
|
|
1450
|
+
handler: authRequestHandler,
|
|
1451
|
+
method: "POST",
|
|
1452
|
+
pathPrefix: `${path}/`
|
|
1453
|
+
});
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
//#endregion
|
|
1457
|
+
export { adapterArgsValidator, adapterConfig, adapterWhereValidator, authMiddleware, checkUniqueFields, convex, createApi, createClient, createHandler, dbAdapter, deleteManyHandler, deleteOneHandler, findManyHandler, findOneHandler, getAuthUserId, getAuthUserIdentity, getHeaders, getSession, handlePagination, hasUniqueFields, httpAdapter, listOne, paginate, registerRoutes, selectFields, updateManyHandler, updateOneHandler };
|