better-convex 0.6.3 → 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/aggregate/index.d.ts +388 -0
- package/dist/aggregate/index.js +37 -0
- package/dist/{auth-client → auth/client}/index.js +1 -1
- package/dist/auth/http/index.d.ts +63 -0
- package/dist/auth/http/index.js +429 -0
- package/dist/auth/index.d.ts +19001 -185
- package/dist/auth/index.js +373 -686
- package/dist/{auth-nextjs → auth/nextjs}/index.d.ts +3 -4
- package/dist/{auth-nextjs → auth/nextjs}/index.js +3 -5
- package/dist/{caller-factory-B1FvYSKr.js → caller-factory-Dmgv8MLS.js} +15 -12
- package/dist/cli.mjs +2601 -13
- package/dist/codegen-Cz1idI3-.mjs +969 -0
- package/dist/{create-schema-orm-DplxTtYj.js → create-schema-orm-69VF4CFV.js} +4 -3
- package/dist/crpc/index.d.ts +2 -2
- package/dist/crpc/index.js +3 -3
- package/dist/{http-types-BRLY10NX.d.ts → http-types-BCf2wCgp.d.ts} +25 -25
- package/dist/meta-utils-DDVYp9Xf.js +117 -0
- package/dist/orm/index.d.ts +4 -3012
- package/dist/orm/index.js +9631 -2
- package/dist/{index-BQkhP2ny.d.ts → procedure-caller-CcjtUFvL.d.ts} +211 -74
- package/dist/query-context-BDSis9rT.js +1518 -0
- package/dist/query-context-DGExXZIV.d.ts +42 -0
- package/dist/react/index.d.ts +31 -35
- package/dist/react/index.js +145 -58
- package/dist/rsc/index.d.ts +4 -7
- package/dist/rsc/index.js +14 -10
- package/dist/runtime-B9xQFY8W.js +2280 -0
- package/dist/server/index.d.ts +3 -4
- package/dist/server/index.js +384 -10
- package/dist/{types-o-5rYcTr.d.ts → types-CIBGEYXq.d.ts} +4 -3
- package/dist/types-DgwvxKbT.d.ts +4 -0
- package/dist/watcher.mjs +8 -8
- package/dist/where-clause-compiler-CRP-i1Qa.d.ts +3463 -0
- package/package.json +14 -10
- package/dist/codegen-DkpPBVPn.mjs +0 -189
- package/dist/context-utils-DSuX99Da.d.ts +0 -17
- package/dist/meta-utils-DCpLSBWB.js +0 -41
- package/dist/orm-CleikBIV.js +0 -8820
- /package/dist/{auth-client → auth/client}/index.d.ts +0 -0
- /package/dist/{auth-config → auth/config}/index.d.ts +0 -0
- /package/dist/{auth-config → auth/config}/index.js +0 -0
- /package/dist/{create-schema-DhWXOhnU.js → create-schema-BdZOL6ns.js} +0 -0
- /package/dist/{customFunctions-C1okqCzL.js → customFunctions-CZnCwoR3.js} +0 -0
- /package/dist/{error-BZUhlhYz.js → error-Be4OcwwD.js} +0 -0
- /package/dist/{query-options-BL1Q0X7q.js → query-options-B0c1b6pZ.js} +0 -0
- /package/dist/{transformer-CTNSPjwp.js → transformer-Dh0w2py0.js} +0 -0
- /package/dist/{types-jftzhhuc.d.ts → types-DwGkkq2s.d.ts} +0 -0
package/dist/auth/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { a as partial, h as asyncMap, l as isQueryCtx, n as customCtx, r as customMutation, u as isRunMutationCtx } from "../customFunctions-
|
|
2
|
-
import {
|
|
1
|
+
import { a as partial, h as asyncMap, l as isQueryCtx, n as customCtx, r as customMutation, u as isRunMutationCtx } from "../customFunctions-CZnCwoR3.js";
|
|
2
|
+
import { a as mergedStream, l as unsetToken, o as stream, t as getByIdWithOrmQueryFallback, v as eq } from "../query-context-BDSis9rT.js";
|
|
3
|
+
import { v } from "convex/values";
|
|
4
|
+
import { internalActionGeneric, internalMutationGeneric, internalQueryGeneric, paginationOptsValidator } from "convex/server";
|
|
3
5
|
import { convex } from "@convex-dev/better-auth/plugins";
|
|
4
6
|
import { createAdapterFactory } from "better-auth/adapters";
|
|
5
7
|
import { getAuthTables } from "better-auth/db";
|
|
6
|
-
import { ROUTABLE_HTTP_METHODS, createFunctionHandle, httpActionGeneric, httpRouter, internalActionGeneric, internalMutationGeneric, internalQueryGeneric, paginationOptsValidator } from "convex/server";
|
|
7
8
|
import { prop, sortBy, uniqueBy } from "remeda";
|
|
8
|
-
import { v } from "convex/values";
|
|
9
9
|
import { stripIndent } from "common-tags";
|
|
10
|
+
import { betterAuth } from "better-auth";
|
|
10
11
|
|
|
11
12
|
//#region src/auth/adapter-utils.ts
|
|
12
13
|
const adapterWhereValidator = v.object({
|
|
@@ -278,6 +279,20 @@ const listOne = async (ctx, schema, betterAuthSchema, args) => (await paginate(c
|
|
|
278
279
|
|
|
279
280
|
//#endregion
|
|
280
281
|
//#region src/auth/create-api.ts
|
|
282
|
+
const AUTH_TABLE_TRIGGER_KEYS = new Set([
|
|
283
|
+
"create",
|
|
284
|
+
"update",
|
|
285
|
+
"delete",
|
|
286
|
+
"change"
|
|
287
|
+
]);
|
|
288
|
+
const LEGACY_AUTH_TRIGGER_KEYS = new Set([
|
|
289
|
+
"beforeCreate",
|
|
290
|
+
"beforeDelete",
|
|
291
|
+
"beforeUpdate",
|
|
292
|
+
"onCreate",
|
|
293
|
+
"onDelete",
|
|
294
|
+
"onUpdate"
|
|
295
|
+
]);
|
|
281
296
|
const whereValidator = (schema, tableName) => v.object({
|
|
282
297
|
connector: v.optional(v.union(v.literal("AND"), v.literal("OR"))),
|
|
283
298
|
field: v.union(...Object.keys(schema.tables[tableName].validator.fields).map((field) => v.literal(field)), v.literal("_id")),
|
|
@@ -312,17 +327,59 @@ const ormDelete = async (ctx, table, id) => {
|
|
|
312
327
|
await ctx.orm.delete(table).where(eq(table._id, id));
|
|
313
328
|
};
|
|
314
329
|
const withBothIdFields = (doc) => {
|
|
315
|
-
|
|
316
|
-
const
|
|
330
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return doc;
|
|
331
|
+
const record = doc;
|
|
332
|
+
const existingUnderscoreId = record._id;
|
|
333
|
+
const existingId = record.id;
|
|
317
334
|
const id = existingUnderscoreId ?? existingId;
|
|
318
335
|
if (!id) return doc;
|
|
319
336
|
return {
|
|
320
|
-
...
|
|
337
|
+
...record,
|
|
321
338
|
_id: existingUnderscoreId ?? id,
|
|
322
339
|
id: existingId ?? id
|
|
323
340
|
};
|
|
324
341
|
};
|
|
325
342
|
const isPlainObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
343
|
+
const isBeforeDataResult = (value) => isPlainObject(value) && "data" in value && isPlainObject(value.data);
|
|
344
|
+
const ensureRuntimeTableTriggers = (model, value) => {
|
|
345
|
+
if (value === void 0) return;
|
|
346
|
+
if (!isPlainObject(value)) throw new Error(`Invalid auth triggers for '${model}'. Expected an object with create/update/delete/change keys.`);
|
|
347
|
+
for (const key of Object.keys(value)) {
|
|
348
|
+
if (LEGACY_AUTH_TRIGGER_KEYS.has(key)) throw new Error(`Invalid auth trigger key '${key}' for '${model}'. Auth triggers now use { create, update, delete, change } shape.`);
|
|
349
|
+
if (!AUTH_TABLE_TRIGGER_KEYS.has(key)) throw new Error(`Invalid auth trigger key '${key}' for '${model}'. Allowed keys: create, update, delete, change.`);
|
|
350
|
+
}
|
|
351
|
+
const create = value.create;
|
|
352
|
+
const update = value.update;
|
|
353
|
+
const del = value.delete;
|
|
354
|
+
const change = value.change;
|
|
355
|
+
const validateOperationHook = (operation, operationHooks) => {
|
|
356
|
+
if (operationHooks === void 0) return;
|
|
357
|
+
if (!isPlainObject(operationHooks)) throw new Error(`Invalid auth trigger '${operation}' for '${model}'. Expected an object with optional before/after handlers.`);
|
|
358
|
+
if (operationHooks.before !== void 0 && typeof operationHooks.before !== "function") throw new Error(`Invalid auth trigger '${operation}.before' for '${model}'. Expected a function.`);
|
|
359
|
+
if (operationHooks.after !== void 0 && typeof operationHooks.after !== "function") throw new Error(`Invalid auth trigger '${operation}.after' for '${model}'. Expected a function.`);
|
|
360
|
+
};
|
|
361
|
+
validateOperationHook("create", create);
|
|
362
|
+
validateOperationHook("update", update);
|
|
363
|
+
validateOperationHook("delete", del);
|
|
364
|
+
if (change !== void 0 && typeof change !== "function") throw new Error(`Invalid auth trigger 'change' for '${model}'. Expected a function.`);
|
|
365
|
+
return {
|
|
366
|
+
...create ? { create } : {},
|
|
367
|
+
...update ? { update } : {},
|
|
368
|
+
...del ? { delete: del } : {},
|
|
369
|
+
...change ? { change } : {}
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
const applyBeforeHook = async (model, operation, data, beforeHook, triggerCtx) => {
|
|
373
|
+
if (!beforeHook) return data;
|
|
374
|
+
const result = await beforeHook(data, triggerCtx);
|
|
375
|
+
if (result === false) throw new Error(`Auth trigger cancelled ${operation} on '${model}'.`);
|
|
376
|
+
if (isBeforeDataResult(result)) return {
|
|
377
|
+
...data,
|
|
378
|
+
...result.data
|
|
379
|
+
};
|
|
380
|
+
return data;
|
|
381
|
+
};
|
|
382
|
+
const getDocId = (doc) => doc._id ?? doc.id;
|
|
326
383
|
const serializeDatesForConvex = (value) => {
|
|
327
384
|
if (value instanceof Date) return value.getTime();
|
|
328
385
|
if (Array.isArray(value)) {
|
|
@@ -352,14 +409,9 @@ const serializeDatesForConvex = (value) => {
|
|
|
352
409
|
};
|
|
353
410
|
const toConvexSafe = (value) => serializeDatesForConvex(value);
|
|
354
411
|
const createHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
data,
|
|
359
|
-
model: args.input.model
|
|
360
|
-
}));
|
|
361
|
-
if (transformedData !== void 0) data = transformedData;
|
|
362
|
-
}
|
|
412
|
+
const triggerCtx = args.triggerCtx ?? ctx;
|
|
413
|
+
const tableTriggers = args.tableTriggers;
|
|
414
|
+
const data = serializeDatesForConvex(await applyBeforeHook(args.input.model, "create", args.input.data, tableTriggers?.create?.before, triggerCtx));
|
|
363
415
|
await checkUniqueFields(ctx, schema, betterAuthSchema, args.input.model, data);
|
|
364
416
|
const ormTable = resolveOrmTable(ctx, schema, betterAuthSchema, args.input.model);
|
|
365
417
|
const doc = ormTable ? await ormInsert(ctx, ormTable.table, data) : await (async () => {
|
|
@@ -367,50 +419,51 @@ const createHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
|
367
419
|
return ctx.db.get(id);
|
|
368
420
|
})();
|
|
369
421
|
if (!doc) throw new Error(`Failed to create ${args.input.model}`);
|
|
370
|
-
const normalizedDoc =
|
|
422
|
+
const normalizedDoc = withBothIdFields(doc);
|
|
371
423
|
const result = await selectFields(normalizedDoc, args.select);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
424
|
+
const hookDoc = serializeDatesForConvex(normalizedDoc);
|
|
425
|
+
const id = getDocId(hookDoc);
|
|
426
|
+
await tableTriggers?.create?.after?.(hookDoc, triggerCtx);
|
|
427
|
+
await tableTriggers?.change?.({
|
|
428
|
+
id,
|
|
429
|
+
newDoc: hookDoc,
|
|
430
|
+
oldDoc: null,
|
|
431
|
+
operation: "insert"
|
|
432
|
+
}, triggerCtx);
|
|
379
433
|
return toConvexSafe(result);
|
|
380
434
|
};
|
|
381
435
|
const findOneHandler = async (ctx, args, schema, betterAuthSchema) => toConvexSafe(await listOne(ctx, schema, betterAuthSchema, args));
|
|
382
436
|
const findManyHandler = async (ctx, args, schema, betterAuthSchema) => toConvexSafe(await paginate(ctx, schema, betterAuthSchema, args));
|
|
383
437
|
const updateOneHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
438
|
+
const triggerCtx = args.triggerCtx ?? ctx;
|
|
439
|
+
const tableTriggers = args.tableTriggers;
|
|
384
440
|
const doc = await listOne(ctx, schema, betterAuthSchema, args.input);
|
|
385
441
|
if (!doc) throw new Error(`Failed to update ${args.input.model}`);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
doc,
|
|
390
|
-
model: args.input.model,
|
|
391
|
-
update
|
|
392
|
-
}));
|
|
393
|
-
if (transformedUpdate !== void 0) update = transformedUpdate;
|
|
394
|
-
}
|
|
395
|
-
await checkUniqueFields(ctx, schema, betterAuthSchema, args.input.model, update, doc);
|
|
442
|
+
const normalizedDoc = withBothIdFields(doc);
|
|
443
|
+
const update = serializeDatesForConvex(await applyBeforeHook(args.input.model, "update", args.input.update, tableTriggers?.update?.before, triggerCtx));
|
|
444
|
+
await checkUniqueFields(ctx, schema, betterAuthSchema, args.input.model, update, normalizedDoc);
|
|
396
445
|
const ormTable = resolveOrmTable(ctx, schema, betterAuthSchema, args.input.model);
|
|
397
|
-
const updatedDoc = ormTable ? await ormUpdate(ctx, ormTable.table,
|
|
398
|
-
await ctx.db.patch(
|
|
399
|
-
return ctx.db.get(
|
|
446
|
+
const updatedDoc = ormTable ? await ormUpdate(ctx, ormTable.table, normalizedDoc._id, update) : await (async () => {
|
|
447
|
+
await ctx.db.patch(normalizedDoc._id, update);
|
|
448
|
+
return ctx.db.get(normalizedDoc._id);
|
|
400
449
|
})();
|
|
401
450
|
if (!updatedDoc) throw new Error(`Failed to update ${args.input.model}`);
|
|
402
|
-
const normalizedUpdatedDoc =
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
451
|
+
const normalizedUpdatedDoc = withBothIdFields(updatedDoc);
|
|
452
|
+
const hookNewDoc = serializeDatesForConvex(normalizedUpdatedDoc);
|
|
453
|
+
const hookOldDoc = serializeDatesForConvex(normalizedDoc);
|
|
454
|
+
const id = getDocId(hookNewDoc);
|
|
455
|
+
await tableTriggers?.update?.after?.(hookNewDoc, triggerCtx);
|
|
456
|
+
await tableTriggers?.change?.({
|
|
457
|
+
id,
|
|
458
|
+
newDoc: hookNewDoc,
|
|
459
|
+
oldDoc: hookOldDoc,
|
|
460
|
+
operation: "update"
|
|
461
|
+
}, triggerCtx);
|
|
411
462
|
return toConvexSafe(normalizedUpdatedDoc);
|
|
412
463
|
};
|
|
413
464
|
const updateManyHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
465
|
+
const triggerCtx = args.triggerCtx ?? ctx;
|
|
466
|
+
const tableTriggers = args.tableTriggers;
|
|
414
467
|
const { page, ...result } = await paginate(ctx, schema, betterAuthSchema, {
|
|
415
468
|
...args.input,
|
|
416
469
|
paginationOpts: args.paginationOpts
|
|
@@ -419,89 +472,91 @@ const updateManyHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
|
419
472
|
if (args.input.update) {
|
|
420
473
|
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(", ")}`);
|
|
421
474
|
await asyncMap(page, async (doc) => {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
await
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
await ctx.runMutation(args.onUpdateHandle, serializeDatesForConvex({
|
|
439
|
-
model: args.input.model,
|
|
440
|
-
newDoc: hookNewDoc,
|
|
441
|
-
oldDoc: doc
|
|
442
|
-
}));
|
|
443
|
-
}
|
|
475
|
+
const normalizedDoc = withBothIdFields(doc);
|
|
476
|
+
const update = serializeDatesForConvex(await applyBeforeHook(args.input.model, "update", args.input.update ?? {}, tableTriggers?.update?.before, triggerCtx));
|
|
477
|
+
await checkUniqueFields(ctx, schema, betterAuthSchema, args.input.model, update ?? {}, normalizedDoc);
|
|
478
|
+
const hookNewDoc = serializeDatesForConvex(withBothIdFields(ormTable ? await ormUpdate(ctx, ormTable.table, normalizedDoc._id, update ?? {}) : await (async () => {
|
|
479
|
+
await ctx.db.patch(normalizedDoc._id, update);
|
|
480
|
+
return ctx.db.get(normalizedDoc._id);
|
|
481
|
+
})()));
|
|
482
|
+
const hookOldDoc = serializeDatesForConvex(normalizedDoc);
|
|
483
|
+
const id = getDocId(hookNewDoc);
|
|
484
|
+
await tableTriggers?.update?.after?.(hookNewDoc, triggerCtx);
|
|
485
|
+
await tableTriggers?.change?.({
|
|
486
|
+
id,
|
|
487
|
+
newDoc: hookNewDoc,
|
|
488
|
+
oldDoc: hookOldDoc,
|
|
489
|
+
operation: "update"
|
|
490
|
+
}, triggerCtx);
|
|
444
491
|
});
|
|
445
492
|
}
|
|
446
493
|
return toConvexSafe({
|
|
447
494
|
...result,
|
|
448
495
|
count: page.length,
|
|
449
|
-
ids: page.map((doc) => doc._id)
|
|
496
|
+
ids: page.map((doc) => withBothIdFields(doc)._id)
|
|
450
497
|
});
|
|
451
498
|
};
|
|
452
499
|
const deleteOneHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
500
|
+
const triggerCtx = args.triggerCtx ?? ctx;
|
|
501
|
+
const tableTriggers = args.tableTriggers;
|
|
453
502
|
const doc = await listOne(ctx, schema, betterAuthSchema, args.input);
|
|
454
503
|
if (!doc) return;
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
doc,
|
|
459
|
-
model: args.input.model
|
|
460
|
-
}));
|
|
461
|
-
if (transformedDoc !== void 0) hookDoc = transformedDoc;
|
|
462
|
-
}
|
|
504
|
+
const normalizedDoc = withBothIdFields(doc);
|
|
505
|
+
const hookDoc = withBothIdFields(serializeDatesForConvex(await applyBeforeHook(args.input.model, "delete", normalizedDoc, tableTriggers?.delete?.before, triggerCtx)));
|
|
506
|
+
const id = getDocId(normalizedDoc);
|
|
463
507
|
const ormTable = resolveOrmTable(ctx, schema, betterAuthSchema, args.input.model);
|
|
464
|
-
if (ormTable) await ormDelete(ctx, ormTable.table,
|
|
465
|
-
else await ctx.db.delete(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
508
|
+
if (ormTable) await ormDelete(ctx, ormTable.table, normalizedDoc._id);
|
|
509
|
+
else await ctx.db.delete(normalizedDoc._id);
|
|
510
|
+
await tableTriggers?.delete?.after?.(hookDoc, triggerCtx);
|
|
511
|
+
await tableTriggers?.change?.({
|
|
512
|
+
id,
|
|
513
|
+
newDoc: null,
|
|
514
|
+
oldDoc: hookDoc,
|
|
515
|
+
operation: "delete"
|
|
516
|
+
}, triggerCtx);
|
|
517
|
+
return toConvexSafe(withBothIdFields(hookDoc));
|
|
471
518
|
};
|
|
472
519
|
const deleteManyHandler = async (ctx, args, schema, betterAuthSchema) => {
|
|
520
|
+
const triggerCtx = args.triggerCtx ?? ctx;
|
|
521
|
+
const tableTriggers = args.tableTriggers;
|
|
473
522
|
const { page, ...result } = await paginate(ctx, schema, betterAuthSchema, {
|
|
474
523
|
...args.input,
|
|
475
524
|
paginationOpts: args.paginationOpts
|
|
476
525
|
});
|
|
477
526
|
const ormTable = resolveOrmTable(ctx, schema, betterAuthSchema, args.input.model);
|
|
478
527
|
await asyncMap(page, async (doc) => {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
model: args.input.model
|
|
492
|
-
}));
|
|
528
|
+
const normalizedDoc = withBothIdFields(doc);
|
|
529
|
+
const hookDoc = withBothIdFields(serializeDatesForConvex(await applyBeforeHook(args.input.model, "delete", normalizedDoc, tableTriggers?.delete?.before, triggerCtx)));
|
|
530
|
+
const id = getDocId(normalizedDoc);
|
|
531
|
+
if (ormTable) await ormDelete(ctx, ormTable.table, normalizedDoc._id);
|
|
532
|
+
else await ctx.db.delete(normalizedDoc._id);
|
|
533
|
+
await tableTriggers?.delete?.after?.(hookDoc, triggerCtx);
|
|
534
|
+
await tableTriggers?.change?.({
|
|
535
|
+
id,
|
|
536
|
+
newDoc: null,
|
|
537
|
+
oldDoc: hookDoc,
|
|
538
|
+
operation: "delete"
|
|
539
|
+
}, triggerCtx);
|
|
493
540
|
});
|
|
494
541
|
return toConvexSafe({
|
|
495
542
|
...result,
|
|
496
543
|
count: page.length,
|
|
497
|
-
ids: page.map((doc) => doc._id)
|
|
544
|
+
ids: page.map((doc) => withBothIdFields(doc)._id)
|
|
498
545
|
});
|
|
499
546
|
};
|
|
500
547
|
const createApi = (schema, getAuth, options) => {
|
|
501
|
-
const
|
|
502
|
-
|
|
548
|
+
const { internalMutation, validateInput = false, context, triggers } = options ?? {};
|
|
549
|
+
let betterAuthSchema;
|
|
550
|
+
const getBetterAuthSchema = () => {
|
|
551
|
+
betterAuthSchema ??= getAuthTables(getAuth({}).options);
|
|
552
|
+
return betterAuthSchema;
|
|
553
|
+
};
|
|
503
554
|
const mutationBuilderBase = internalMutation ?? internalMutationGeneric;
|
|
504
555
|
const mutationBuilder = context ? customMutation(mutationBuilderBase, customCtx(async (ctx) => await context?.(ctx) ?? ctx)) : mutationBuilderBase;
|
|
556
|
+
const resolveTableTriggers = (model, triggerCtx) => {
|
|
557
|
+
const tableTriggers = (typeof triggers === "function" ? triggers(triggerCtx) : triggers)?.[model];
|
|
558
|
+
return ensureRuntimeTableTriggers(model, tableTriggers);
|
|
559
|
+
};
|
|
505
560
|
const anyInput = v.object({
|
|
506
561
|
data: v.any(),
|
|
507
562
|
model: v.string()
|
|
@@ -515,55 +570,71 @@ const createApi = (schema, getAuth, options) => {
|
|
|
515
570
|
update: v.any(),
|
|
516
571
|
where: v.optional(v.array(v.any()))
|
|
517
572
|
});
|
|
518
|
-
const
|
|
573
|
+
const authSchemaForValidation = validateInput ? getBetterAuthSchema() : {};
|
|
574
|
+
const authTableNames = new Set(Object.keys(authSchemaForValidation));
|
|
519
575
|
const authTables = Object.entries(schema.tables).filter(([name]) => authTableNames.has(name));
|
|
520
576
|
const authTableKeys = authTables.map(([name]) => name);
|
|
521
|
-
const createInput =
|
|
577
|
+
const createInput = validateInput ? v.union(...authTables.map(([model, table]) => {
|
|
522
578
|
const fields = partial(table.validator.fields);
|
|
523
579
|
return v.object({
|
|
524
580
|
data: v.object(fields),
|
|
525
581
|
model: v.literal(model)
|
|
526
582
|
});
|
|
527
|
-
}));
|
|
528
|
-
const deleteInput =
|
|
583
|
+
})) : anyInput;
|
|
584
|
+
const deleteInput = validateInput ? v.union(...authTableKeys.map((tableName) => v.object({
|
|
529
585
|
model: v.literal(tableName),
|
|
530
586
|
where: v.optional(v.array(whereValidator(schema, tableName)))
|
|
531
|
-
})));
|
|
532
|
-
const modelValidator =
|
|
533
|
-
const updateInput =
|
|
587
|
+
}))) : anyInputWithWhere;
|
|
588
|
+
const modelValidator = validateInput ? v.union(...authTableKeys.map((model) => v.literal(model))) : v.string();
|
|
589
|
+
const updateInput = validateInput ? v.union(...authTables.map(([tableName, table]) => {
|
|
534
590
|
const fields = partial(table.validator.fields);
|
|
535
591
|
return v.object({
|
|
536
592
|
model: v.literal(tableName),
|
|
537
593
|
update: v.object(fields),
|
|
538
594
|
where: v.optional(v.array(whereValidator(schema, tableName)))
|
|
539
595
|
});
|
|
540
|
-
}));
|
|
596
|
+
})) : anyInputWithUpdate;
|
|
541
597
|
return {
|
|
542
598
|
create: mutationBuilder({
|
|
543
599
|
args: {
|
|
544
|
-
beforeCreateHandle: v.optional(v.string()),
|
|
545
600
|
input: createInput,
|
|
546
|
-
select: v.optional(v.array(v.string()))
|
|
547
|
-
onCreateHandle: v.optional(v.string())
|
|
601
|
+
select: v.optional(v.array(v.string()))
|
|
548
602
|
},
|
|
549
|
-
handler: async (ctx, args) =>
|
|
603
|
+
handler: async (ctx, args) => {
|
|
604
|
+
const triggerCtx = ctx;
|
|
605
|
+
return createHandler(ctx, {
|
|
606
|
+
input: args.input,
|
|
607
|
+
select: args.select,
|
|
608
|
+
tableTriggers: resolveTableTriggers(args.input.model, triggerCtx),
|
|
609
|
+
triggerCtx
|
|
610
|
+
}, schema, getBetterAuthSchema());
|
|
611
|
+
}
|
|
550
612
|
}),
|
|
551
613
|
deleteMany: mutationBuilder({
|
|
552
614
|
args: {
|
|
553
|
-
beforeDeleteHandle: v.optional(v.string()),
|
|
554
615
|
input: deleteInput,
|
|
555
|
-
paginationOpts: paginationOptsValidator
|
|
556
|
-
onDeleteHandle: v.optional(v.string())
|
|
616
|
+
paginationOpts: paginationOptsValidator
|
|
557
617
|
},
|
|
558
|
-
handler: async (ctx, args) =>
|
|
618
|
+
handler: async (ctx, args) => {
|
|
619
|
+
const triggerCtx = ctx;
|
|
620
|
+
return deleteManyHandler(ctx, {
|
|
621
|
+
input: args.input,
|
|
622
|
+
paginationOpts: args.paginationOpts,
|
|
623
|
+
tableTriggers: resolveTableTriggers(args.input.model, triggerCtx),
|
|
624
|
+
triggerCtx
|
|
625
|
+
}, schema, getBetterAuthSchema());
|
|
626
|
+
}
|
|
559
627
|
}),
|
|
560
628
|
deleteOne: mutationBuilder({
|
|
561
|
-
args: {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
629
|
+
args: { input: deleteInput },
|
|
630
|
+
handler: async (ctx, args) => {
|
|
631
|
+
const triggerCtx = ctx;
|
|
632
|
+
return deleteOneHandler(ctx, {
|
|
633
|
+
input: args.input,
|
|
634
|
+
tableTriggers: resolveTableTriggers(args.input.model, triggerCtx),
|
|
635
|
+
triggerCtx
|
|
636
|
+
}, schema, getBetterAuthSchema());
|
|
637
|
+
}
|
|
567
638
|
}),
|
|
568
639
|
findMany: internalQueryGeneric({
|
|
569
640
|
args: {
|
|
@@ -578,7 +649,7 @@ const createApi = (schema, getAuth, options) => {
|
|
|
578
649
|
where: v.optional(v.array(adapterWhereValidator)),
|
|
579
650
|
join: v.optional(v.any())
|
|
580
651
|
},
|
|
581
|
-
handler: async (ctx, args) => findManyHandler(ctx, args, schema,
|
|
652
|
+
handler: async (ctx, args) => findManyHandler(ctx, args, schema, getBetterAuthSchema())
|
|
582
653
|
}),
|
|
583
654
|
findOne: internalQueryGeneric({
|
|
584
655
|
args: {
|
|
@@ -587,24 +658,33 @@ const createApi = (schema, getAuth, options) => {
|
|
|
587
658
|
where: v.optional(v.array(adapterWhereValidator)),
|
|
588
659
|
join: v.optional(v.any())
|
|
589
660
|
},
|
|
590
|
-
handler: async (ctx, args) => findOneHandler(ctx, args, schema,
|
|
661
|
+
handler: async (ctx, args) => findOneHandler(ctx, args, schema, getBetterAuthSchema())
|
|
591
662
|
}),
|
|
592
663
|
updateMany: mutationBuilder({
|
|
593
664
|
args: {
|
|
594
|
-
beforeUpdateHandle: v.optional(v.string()),
|
|
595
665
|
input: updateInput,
|
|
596
|
-
paginationOpts: paginationOptsValidator
|
|
597
|
-
onUpdateHandle: v.optional(v.string())
|
|
666
|
+
paginationOpts: paginationOptsValidator
|
|
598
667
|
},
|
|
599
|
-
handler: async (ctx, args) =>
|
|
668
|
+
handler: async (ctx, args) => {
|
|
669
|
+
const triggerCtx = ctx;
|
|
670
|
+
return updateManyHandler(ctx, {
|
|
671
|
+
input: args.input,
|
|
672
|
+
paginationOpts: args.paginationOpts,
|
|
673
|
+
tableTriggers: resolveTableTriggers(args.input.model, triggerCtx),
|
|
674
|
+
triggerCtx
|
|
675
|
+
}, schema, getBetterAuthSchema());
|
|
676
|
+
}
|
|
600
677
|
}),
|
|
601
678
|
updateOne: mutationBuilder({
|
|
602
|
-
args: {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
679
|
+
args: { input: updateInput },
|
|
680
|
+
handler: async (ctx, args) => {
|
|
681
|
+
const triggerCtx = ctx;
|
|
682
|
+
return updateOneHandler(ctx, {
|
|
683
|
+
input: args.input,
|
|
684
|
+
tableTriggers: resolveTableTriggers(args.input.model, triggerCtx),
|
|
685
|
+
triggerCtx
|
|
686
|
+
}, schema, getBetterAuthSchema());
|
|
687
|
+
}
|
|
608
688
|
}),
|
|
609
689
|
getLatestJwks: internalActionGeneric({
|
|
610
690
|
args: {},
|
|
@@ -692,26 +772,25 @@ const ORM_SCHEMA_OPTIONS = Symbol.for("better-convex:OrmSchemaOptions");
|
|
|
692
772
|
const hasOrmSchemaMetadata = (schema) => !!schema && typeof schema === "object" && ORM_SCHEMA_OPTIONS in schema;
|
|
693
773
|
const createAuthSchema = async ({ file, schema, tables }) => {
|
|
694
774
|
if (hasOrmSchemaMetadata(schema)) {
|
|
695
|
-
const { createSchemaOrm } = await import("../create-schema-orm-
|
|
775
|
+
const { createSchemaOrm } = await import("../create-schema-orm-69VF4CFV.js");
|
|
696
776
|
return createSchemaOrm({
|
|
697
777
|
file,
|
|
698
778
|
tables
|
|
699
779
|
});
|
|
700
780
|
}
|
|
701
|
-
const { createSchema } = await import("../create-schema-
|
|
781
|
+
const { createSchema } = await import("../create-schema-BdZOL6ns.js");
|
|
702
782
|
return createSchema({
|
|
703
783
|
file,
|
|
704
784
|
tables
|
|
705
785
|
});
|
|
706
786
|
};
|
|
707
|
-
const httpAdapter = (ctx, { authFunctions, debugLogs, schema
|
|
787
|
+
const httpAdapter = (ctx, { authFunctions, debugLogs, schema }) => {
|
|
708
788
|
return createAdapterFactory({
|
|
709
789
|
config: {
|
|
710
790
|
...adapterConfig,
|
|
711
791
|
debugLogs: debugLogs || false
|
|
712
792
|
},
|
|
713
793
|
adapter: ({ options }) => {
|
|
714
|
-
const getTriggers = (model) => triggers?.[model];
|
|
715
794
|
options.telemetry = { enabled: false };
|
|
716
795
|
return {
|
|
717
796
|
id: "convex",
|
|
@@ -730,16 +809,12 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, schema, triggers }) => {
|
|
|
730
809
|
},
|
|
731
810
|
create: async ({ data, model, select }) => {
|
|
732
811
|
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
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;
|
|
735
812
|
return await ctx.runMutation(authFunctions.create, {
|
|
736
|
-
beforeCreateHandle,
|
|
737
813
|
input: {
|
|
738
814
|
data,
|
|
739
815
|
model
|
|
740
816
|
},
|
|
741
|
-
select
|
|
742
|
-
onCreateHandle
|
|
817
|
+
select
|
|
743
818
|
});
|
|
744
819
|
},
|
|
745
820
|
createSchema: async ({ file, tables }) => createAuthSchema({
|
|
@@ -749,29 +824,19 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, schema, triggers }) => {
|
|
|
749
824
|
}),
|
|
750
825
|
delete: async (data) => {
|
|
751
826
|
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
input: {
|
|
757
|
-
model: data.model,
|
|
758
|
-
where: parseWhere(data.where)
|
|
759
|
-
},
|
|
760
|
-
onDeleteHandle
|
|
761
|
-
});
|
|
827
|
+
await ctx.runMutation(authFunctions.deleteOne, { input: {
|
|
828
|
+
model: data.model,
|
|
829
|
+
where: parseWhere(data.where)
|
|
830
|
+
} });
|
|
762
831
|
},
|
|
763
832
|
deleteMany: async (data) => {
|
|
764
833
|
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
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;
|
|
767
834
|
return (await handlePagination(async ({ paginationOpts }) => await ctx.runMutation(authFunctions.deleteMany, {
|
|
768
|
-
beforeDeleteHandle,
|
|
769
835
|
input: {
|
|
770
836
|
...data,
|
|
771
837
|
where: parseWhere(data.where)
|
|
772
838
|
},
|
|
773
|
-
paginationOpts
|
|
774
|
-
onDeleteHandle
|
|
839
|
+
paginationOpts
|
|
775
840
|
}))).count;
|
|
776
841
|
},
|
|
777
842
|
findMany: async (data) => {
|
|
@@ -815,39 +880,29 @@ const httpAdapter = (ctx, { authFunctions, debugLogs, schema, triggers }) => {
|
|
|
815
880
|
}), { limit: 2 });
|
|
816
881
|
if (countResult.docs.length === 0) throw new Error(`No ${data.model} found matching criteria`);
|
|
817
882
|
if (countResult.docs.length > 1) throw new Error(`Multiple ${data.model} found matching criteria. Expected exactly 1.`);
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
model: data.model,
|
|
824
|
-
update: data.update,
|
|
825
|
-
where: parseWhere(data.where)
|
|
826
|
-
},
|
|
827
|
-
onUpdateHandle
|
|
828
|
-
});
|
|
883
|
+
return await ctx.runMutation(authFunctions.updateOne, { input: {
|
|
884
|
+
model: data.model,
|
|
885
|
+
update: data.update,
|
|
886
|
+
where: parseWhere(data.where)
|
|
887
|
+
} });
|
|
829
888
|
}
|
|
830
889
|
throw new Error("where clause not supported");
|
|
831
890
|
},
|
|
832
891
|
updateMany: async (data) => {
|
|
833
892
|
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
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;
|
|
836
893
|
return (await handlePagination(async ({ paginationOpts }) => await ctx.runMutation(authFunctions.updateMany, {
|
|
837
|
-
beforeUpdateHandle,
|
|
838
894
|
input: {
|
|
839
895
|
...data,
|
|
840
896
|
where: parseWhere(data.where)
|
|
841
897
|
},
|
|
842
|
-
paginationOpts
|
|
843
|
-
onUpdateHandle
|
|
898
|
+
paginationOpts
|
|
844
899
|
}))).count;
|
|
845
900
|
}
|
|
846
901
|
};
|
|
847
902
|
}
|
|
848
903
|
});
|
|
849
904
|
};
|
|
850
|
-
const dbAdapter = (ctx, getAuthOptions, { authFunctions, debugLogs, schema
|
|
905
|
+
const dbAdapter = (ctx, getAuthOptions, { authFunctions, debugLogs, schema }) => {
|
|
851
906
|
const betterAuthSchema = getAuthTables(getAuthOptions({}));
|
|
852
907
|
return createAdapterFactory({
|
|
853
908
|
config: {
|
|
@@ -855,7 +910,6 @@ const dbAdapter = (ctx, getAuthOptions, { authFunctions, debugLogs, schema, trig
|
|
|
855
910
|
debugLogs: debugLogs || false
|
|
856
911
|
},
|
|
857
912
|
adapter: ({ options }) => {
|
|
858
|
-
const getTriggers = (model) => triggers?.[model];
|
|
859
913
|
options.telemetry = { enabled: false };
|
|
860
914
|
return {
|
|
861
915
|
id: "convex",
|
|
@@ -873,16 +927,14 @@ const dbAdapter = (ctx, getAuthOptions, { authFunctions, debugLogs, schema, trig
|
|
|
873
927
|
}, schema, betterAuthSchema))).docs.length;
|
|
874
928
|
},
|
|
875
929
|
create: async ({ data, model, select }) => {
|
|
876
|
-
|
|
877
|
-
return await
|
|
878
|
-
beforeCreateHandle: authFunctions.beforeCreate && getTriggers(model)?.beforeCreate ? await createFunctionHandle(authFunctions.beforeCreate) : void 0,
|
|
930
|
+
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
931
|
+
return await ctx.runMutation(authFunctions.create, {
|
|
879
932
|
input: {
|
|
880
933
|
data,
|
|
881
934
|
model
|
|
882
935
|
},
|
|
883
|
-
select
|
|
884
|
-
|
|
885
|
-
}, schema, betterAuthSchema);
|
|
936
|
+
select
|
|
937
|
+
});
|
|
886
938
|
},
|
|
887
939
|
createSchema: async ({ file, tables }) => createAuthSchema({
|
|
888
940
|
file,
|
|
@@ -890,28 +942,21 @@ const dbAdapter = (ctx, getAuthOptions, { authFunctions, debugLogs, schema, trig
|
|
|
890
942
|
tables
|
|
891
943
|
}),
|
|
892
944
|
delete: async (data) => {
|
|
893
|
-
|
|
894
|
-
await
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
where: parseWhere(data.where)
|
|
899
|
-
},
|
|
900
|
-
onDeleteHandle
|
|
901
|
-
}, schema, betterAuthSchema);
|
|
945
|
+
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
946
|
+
await ctx.runMutation(authFunctions.deleteOne, { input: {
|
|
947
|
+
model: data.model,
|
|
948
|
+
where: parseWhere(data.where)
|
|
949
|
+
} });
|
|
902
950
|
},
|
|
903
951
|
deleteMany: async (data) => {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
return (await handlePagination(async ({ paginationOpts }) => await deleteManyHandler(ctx, {
|
|
907
|
-
beforeDeleteHandle,
|
|
952
|
+
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
953
|
+
return (await handlePagination(async ({ paginationOpts }) => await ctx.runMutation(authFunctions.deleteMany, {
|
|
908
954
|
input: {
|
|
909
955
|
...data,
|
|
910
956
|
where: parseWhere(data.where)
|
|
911
957
|
},
|
|
912
|
-
paginationOpts
|
|
913
|
-
|
|
914
|
-
}, schema, betterAuthSchema))).count;
|
|
958
|
+
paginationOpts
|
|
959
|
+
}))).count;
|
|
915
960
|
},
|
|
916
961
|
findMany: async (data) => {
|
|
917
962
|
if (data.offset) throw new Error("offset not supported");
|
|
@@ -952,31 +997,24 @@ const dbAdapter = (ctx, getAuthOptions, { authFunctions, debugLogs, schema, trig
|
|
|
952
997
|
}, schema, betterAuthSchema), { limit: 2 });
|
|
953
998
|
if (countResult.docs.length === 0) throw new Error(`No ${data.model} found matching criteria`);
|
|
954
999
|
if (countResult.docs.length > 1) throw new Error(`Multiple ${data.model} found matching criteria. Expected exactly 1.`);
|
|
955
|
-
|
|
956
|
-
return await
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
where: parseWhere(data.where)
|
|
962
|
-
},
|
|
963
|
-
onUpdateHandle
|
|
964
|
-
}, schema, betterAuthSchema);
|
|
1000
|
+
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
1001
|
+
return await ctx.runMutation(authFunctions.updateOne, { input: {
|
|
1002
|
+
model: data.model,
|
|
1003
|
+
update: data.update,
|
|
1004
|
+
where: parseWhere(data.where)
|
|
1005
|
+
} });
|
|
965
1006
|
}
|
|
966
1007
|
throw new Error("where clause not supported");
|
|
967
1008
|
},
|
|
968
1009
|
updateMany: async (data) => {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
return (await handlePagination(async ({ paginationOpts }) => await updateManyHandler(ctx, {
|
|
972
|
-
beforeUpdateHandle,
|
|
1010
|
+
if (!("runMutation" in ctx)) throw new Error("ctx is not a mutation ctx");
|
|
1011
|
+
return (await handlePagination(async ({ paginationOpts }) => await ctx.runMutation(authFunctions.updateMany, {
|
|
973
1012
|
input: {
|
|
974
1013
|
...data,
|
|
975
1014
|
where: parseWhere(data.where)
|
|
976
1015
|
},
|
|
977
|
-
paginationOpts
|
|
978
|
-
|
|
979
|
-
}, schema, betterAuthSchema))).count;
|
|
1016
|
+
paginationOpts
|
|
1017
|
+
}))).count;
|
|
980
1018
|
}
|
|
981
1019
|
};
|
|
982
1020
|
}
|
|
@@ -988,81 +1026,124 @@ const dbAdapter = (ctx, getAuthOptions, { authFunctions, debugLogs, schema, trig
|
|
|
988
1026
|
const createClient = (config) => ({
|
|
989
1027
|
authFunctions: config.authFunctions,
|
|
990
1028
|
triggers: config.triggers,
|
|
991
|
-
adapter: (ctx, getAuthOptions) => isQueryCtx(ctx) ? dbAdapter(ctx, getAuthOptions, config) : httpAdapter(ctx, config)
|
|
992
|
-
triggersApi: () => {
|
|
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);
|
|
999
|
-
return {
|
|
1000
|
-
beforeCreate: mutationBuilder({
|
|
1001
|
-
args: {
|
|
1002
|
-
data: v.any(),
|
|
1003
|
-
model: v.string()
|
|
1004
|
-
},
|
|
1005
|
-
handler: async (ctx, args) => {
|
|
1006
|
-
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1007
|
-
return await getTriggers(args.model)?.beforeCreate?.(triggerCtx, args.data) ?? args.data;
|
|
1008
|
-
}
|
|
1009
|
-
}),
|
|
1010
|
-
beforeDelete: mutationBuilder({
|
|
1011
|
-
args: {
|
|
1012
|
-
doc: v.any(),
|
|
1013
|
-
model: v.string()
|
|
1014
|
-
},
|
|
1015
|
-
handler: async (ctx, args) => {
|
|
1016
|
-
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1017
|
-
return await getTriggers(args.model)?.beforeDelete?.(triggerCtx, args.doc) ?? args.doc;
|
|
1018
|
-
}
|
|
1019
|
-
}),
|
|
1020
|
-
beforeUpdate: mutationBuilder({
|
|
1021
|
-
args: {
|
|
1022
|
-
doc: v.any(),
|
|
1023
|
-
model: v.string(),
|
|
1024
|
-
update: v.any()
|
|
1025
|
-
},
|
|
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
|
-
}
|
|
1030
|
-
}),
|
|
1031
|
-
onCreate: mutationBuilder({
|
|
1032
|
-
args: {
|
|
1033
|
-
doc: v.any(),
|
|
1034
|
-
model: v.string()
|
|
1035
|
-
},
|
|
1036
|
-
handler: async (ctx, args) => {
|
|
1037
|
-
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1038
|
-
await getTriggers(args.model)?.onCreate?.(triggerCtx, args.doc);
|
|
1039
|
-
}
|
|
1040
|
-
}),
|
|
1041
|
-
onDelete: mutationBuilder({
|
|
1042
|
-
args: {
|
|
1043
|
-
doc: v.any(),
|
|
1044
|
-
model: v.string()
|
|
1045
|
-
},
|
|
1046
|
-
handler: async (ctx, args) => {
|
|
1047
|
-
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1048
|
-
await getTriggers(args.model)?.onDelete?.(triggerCtx, args.doc);
|
|
1049
|
-
}
|
|
1050
|
-
}),
|
|
1051
|
-
onUpdate: mutationBuilder({
|
|
1052
|
-
args: {
|
|
1053
|
-
model: v.string(),
|
|
1054
|
-
newDoc: v.any(),
|
|
1055
|
-
oldDoc: v.any()
|
|
1056
|
-
},
|
|
1057
|
-
handler: async (ctx, args) => {
|
|
1058
|
-
const triggerCtx = await resolveTriggerCtx(ctx);
|
|
1059
|
-
await getTriggers(args.model)?.onUpdate?.(triggerCtx, args.newDoc, args.oldDoc);
|
|
1060
|
-
}
|
|
1061
|
-
})
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1029
|
+
adapter: (ctx, getAuthOptions) => isQueryCtx(ctx) ? dbAdapter(ctx, getAuthOptions, config) : httpAdapter(ctx, config)
|
|
1064
1030
|
});
|
|
1065
1031
|
|
|
1032
|
+
//#endregion
|
|
1033
|
+
//#region src/auth/define-auth.ts
|
|
1034
|
+
const defineAuth = (definition) => definition;
|
|
1035
|
+
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/auth/generated-contract.ts
|
|
1038
|
+
const GENERATED_AUTH_DISABLED_REASONS = {
|
|
1039
|
+
default_export_unavailable: "Auth runtime is disabled. convex/functions/auth.ts default export is unavailable. Export `default defineAuth((ctx) => ({ ...options, triggers }))` and run `better-convex codegen`.",
|
|
1040
|
+
missing_auth_file: "Auth runtime is disabled. Create convex/functions/auth.ts with `export default defineAuth(...)` and run `better-convex codegen`.",
|
|
1041
|
+
missing_default_export: "Auth runtime is disabled. convex/functions/auth.ts exists but does not export a default auth definition. Export `default defineAuth((ctx) => ({ ...options, triggers }))` and run `better-convex codegen`."
|
|
1042
|
+
};
|
|
1043
|
+
const getGeneratedAuthDisabledReason = (kind) => GENERATED_AUTH_DISABLED_REASONS[kind];
|
|
1044
|
+
const DEFAULT_DISABLED_AUTH_MESSAGE = getGeneratedAuthDisabledReason("missing_auth_file");
|
|
1045
|
+
const resolveGeneratedAuthDefinition = (input, reason) => {
|
|
1046
|
+
const resolved = typeof input === "function" ? input : typeof input === "object" && input !== null && "default" in input && typeof input.default === "function" ? input.default : void 0;
|
|
1047
|
+
if (typeof resolved === "function") return resolved;
|
|
1048
|
+
throw new Error(reason);
|
|
1049
|
+
};
|
|
1050
|
+
const withDatabase = (authOptions, ctx, adapter) => ({
|
|
1051
|
+
...authOptions,
|
|
1052
|
+
database: adapter(ctx)
|
|
1053
|
+
});
|
|
1054
|
+
const withoutTriggers = (authOptions) => {
|
|
1055
|
+
const { triggers: _triggers, ...options } = authOptions;
|
|
1056
|
+
return options;
|
|
1057
|
+
};
|
|
1058
|
+
const createDisabledError = (message, exportName) => () => {
|
|
1059
|
+
throw new Error(`${message} (${exportName})`);
|
|
1060
|
+
};
|
|
1061
|
+
const createLazyAuthProxy = (resolve) => new Proxy({}, { get(_target, prop, receiver) {
|
|
1062
|
+
const auth = resolve();
|
|
1063
|
+
const value = Reflect.get(auth, prop, receiver);
|
|
1064
|
+
return typeof value === "function" ? value.bind(auth) : value;
|
|
1065
|
+
} });
|
|
1066
|
+
const AUTH_RUNTIME_PROCEDURE_TYPES = {
|
|
1067
|
+
create: "mutation",
|
|
1068
|
+
deleteMany: "mutation",
|
|
1069
|
+
deleteOne: "mutation",
|
|
1070
|
+
findMany: "query",
|
|
1071
|
+
findOne: "query",
|
|
1072
|
+
getLatestJwks: "action",
|
|
1073
|
+
rotateKeys: "action",
|
|
1074
|
+
updateMany: "mutation",
|
|
1075
|
+
updateOne: "mutation"
|
|
1076
|
+
};
|
|
1077
|
+
const decorateProcedureExport = (value, procedureType) => {
|
|
1078
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return;
|
|
1079
|
+
const procedure = value;
|
|
1080
|
+
if (typeof procedure._handler !== "function") return;
|
|
1081
|
+
procedure._crpcMeta = {
|
|
1082
|
+
...procedure._crpcMeta ?? {},
|
|
1083
|
+
internal: true,
|
|
1084
|
+
type: procedureType
|
|
1085
|
+
};
|
|
1086
|
+
if (typeof procedure.__betterConvexRawHandler !== "function") procedure.__betterConvexRawHandler = ({ ctx, input }) => procedure._handler?.(ctx, input);
|
|
1087
|
+
};
|
|
1088
|
+
const decorateAuthRuntimeProcedures = (exportsObject) => {
|
|
1089
|
+
for (const [name, procedureType] of Object.entries(AUTH_RUNTIME_PROCEDURE_TYPES)) decorateProcedureExport(exportsObject[name], procedureType);
|
|
1090
|
+
return exportsObject;
|
|
1091
|
+
};
|
|
1092
|
+
const createAuthRuntime = (config) => {
|
|
1093
|
+
const authDefinition = resolveGeneratedAuthDefinition(config.auth, "Invalid auth definition export. Expected convex/functions/auth.ts default export to be `defineAuth((ctx) => ({ ... }))`.");
|
|
1094
|
+
const authFunctions = config.internal[config.moduleName];
|
|
1095
|
+
const resolveRuntimeTriggers = (ctx) => authDefinition(ctx).triggers;
|
|
1096
|
+
const authClient = createClient({
|
|
1097
|
+
authFunctions,
|
|
1098
|
+
schema: config.schema,
|
|
1099
|
+
...config.context ? { context: config.context } : {},
|
|
1100
|
+
triggers: resolveRuntimeTriggers
|
|
1101
|
+
});
|
|
1102
|
+
const adapterGetAuthOptions = ((ctx) => withoutTriggers(authDefinition(ctx)));
|
|
1103
|
+
const resolveAuthOptions = (ctx) => withDatabase(withoutTriggers(authDefinition(ctx)), ctx, (_ctx) => authClient.adapter(_ctx, adapterGetAuthOptions));
|
|
1104
|
+
const getAuth = (ctx) => betterAuth(resolveAuthOptions(ctx));
|
|
1105
|
+
const decoratedAuthApi = decorateAuthRuntimeProcedures(createApi(config.schema, getAuth, {
|
|
1106
|
+
...config.context ? { context: config.context } : {},
|
|
1107
|
+
triggers: resolveRuntimeTriggers
|
|
1108
|
+
}));
|
|
1109
|
+
let staticAuth;
|
|
1110
|
+
const getStaticAuth = () => {
|
|
1111
|
+
staticAuth ??= betterAuth(resolveAuthOptions({}));
|
|
1112
|
+
return staticAuth;
|
|
1113
|
+
};
|
|
1114
|
+
return {
|
|
1115
|
+
authEnabled: true,
|
|
1116
|
+
authClient,
|
|
1117
|
+
getAuth,
|
|
1118
|
+
auth: createLazyAuthProxy(getStaticAuth),
|
|
1119
|
+
...decoratedAuthApi
|
|
1120
|
+
};
|
|
1121
|
+
};
|
|
1122
|
+
const createDisabledAuthRuntime = (config) => {
|
|
1123
|
+
const message = config?.reason ?? DEFAULT_DISABLED_AUTH_MESSAGE;
|
|
1124
|
+
return {
|
|
1125
|
+
authEnabled: false,
|
|
1126
|
+
authClient: {
|
|
1127
|
+
authFunctions: {},
|
|
1128
|
+
triggers: void 0,
|
|
1129
|
+
adapter: createDisabledError(message, "authClient.adapter")
|
|
1130
|
+
},
|
|
1131
|
+
auth: new Proxy({}, { get() {
|
|
1132
|
+
throw new Error(`${message} (auth)`);
|
|
1133
|
+
} }),
|
|
1134
|
+
getAuth: createDisabledError(message, "getAuth"),
|
|
1135
|
+
create: createDisabledError(message, "create"),
|
|
1136
|
+
deleteMany: createDisabledError(message, "deleteMany"),
|
|
1137
|
+
deleteOne: createDisabledError(message, "deleteOne"),
|
|
1138
|
+
findMany: createDisabledError(message, "findMany"),
|
|
1139
|
+
findOne: createDisabledError(message, "findOne"),
|
|
1140
|
+
updateMany: createDisabledError(message, "updateMany"),
|
|
1141
|
+
updateOne: createDisabledError(message, "updateOne"),
|
|
1142
|
+
getLatestJwks: createDisabledError(message, "getLatestJwks"),
|
|
1143
|
+
rotateKeys: createDisabledError(message, "rotateKeys")
|
|
1144
|
+
};
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1066
1147
|
//#endregion
|
|
1067
1148
|
//#region src/auth/helpers.ts
|
|
1068
1149
|
const getAuthUserIdentity = async (ctx) => {
|
|
@@ -1079,7 +1160,7 @@ const getAuthUserId = async (ctx) => {
|
|
|
1079
1160
|
if (!identity) return null;
|
|
1080
1161
|
return identity.subject;
|
|
1081
1162
|
};
|
|
1082
|
-
|
|
1163
|
+
async function getSession(ctx, _sessionId) {
|
|
1083
1164
|
let sessionId = _sessionId;
|
|
1084
1165
|
if (!sessionId) {
|
|
1085
1166
|
const identity = await getAuthUserIdentity(ctx);
|
|
@@ -1087,8 +1168,8 @@ const getSession = async (ctx, _sessionId) => {
|
|
|
1087
1168
|
sessionId = identity.sessionId;
|
|
1088
1169
|
}
|
|
1089
1170
|
if (!sessionId) return null;
|
|
1090
|
-
return await ctx
|
|
1091
|
-
}
|
|
1171
|
+
return await getByIdWithOrmQueryFallback(ctx, "session", sessionId);
|
|
1172
|
+
}
|
|
1092
1173
|
const getHeaders = async (ctx, session) => {
|
|
1093
1174
|
const resolvedSession = session ?? await getSession(ctx);
|
|
1094
1175
|
if (!resolvedSession) return new Headers();
|
|
@@ -1099,398 +1180,4 @@ const getHeaders = async (ctx, session) => {
|
|
|
1099
1180
|
};
|
|
1100
1181
|
|
|
1101
1182
|
//#endregion
|
|
1102
|
-
|
|
1103
|
-
const isApiErrorLike = (error) => !!error && typeof error === "object" && (error.name === "APIError" && "statusCode" in error || typeof error.statusCode === "number");
|
|
1104
|
-
const toResponseInit = (error) => {
|
|
1105
|
-
const init = {
|
|
1106
|
-
headers: new Headers(error.headers ?? {}),
|
|
1107
|
-
status: typeof error.statusCode === "number" ? error.statusCode : 500
|
|
1108
|
-
};
|
|
1109
|
-
if (typeof error.status === "string") init.statusText = error.status;
|
|
1110
|
-
return init;
|
|
1111
|
-
};
|
|
1112
|
-
const toAuthErrorResponse = (error) => {
|
|
1113
|
-
if (!isApiErrorLike(error)) return null;
|
|
1114
|
-
const init = toResponseInit(error);
|
|
1115
|
-
const { body } = error;
|
|
1116
|
-
if (body === void 0) return new Response(null, init);
|
|
1117
|
-
if (typeof body === "string") {
|
|
1118
|
-
if (init.headers instanceof Headers && !init.headers.has("content-type")) init.headers.set("content-type", "text/plain");
|
|
1119
|
-
return new Response(body, init);
|
|
1120
|
-
}
|
|
1121
|
-
return Response.json(body, init);
|
|
1122
|
-
};
|
|
1123
|
-
|
|
1124
|
-
//#endregion
|
|
1125
|
-
//#region src/auth/middleware.ts
|
|
1126
|
-
/**
|
|
1127
|
-
* Create auth middleware that handles auth routes and OpenID well-known redirect.
|
|
1128
|
-
*
|
|
1129
|
-
* @example
|
|
1130
|
-
* ```ts
|
|
1131
|
-
* import { Hono } from 'hono';
|
|
1132
|
-
* import { cors } from 'hono/cors';
|
|
1133
|
-
* import { authMiddleware } from 'better-convex/auth';
|
|
1134
|
-
* import { createHttpRouter } from 'better-convex/server';
|
|
1135
|
-
*
|
|
1136
|
-
* const app = new Hono();
|
|
1137
|
-
* app.use('/api/*', cors({ origin: process.env.SITE_URL, credentials: true }));
|
|
1138
|
-
* app.use(authMiddleware(getAuth));
|
|
1139
|
-
*
|
|
1140
|
-
* export default createHttpRouter(app, appRouter);
|
|
1141
|
-
* ```
|
|
1142
|
-
*/
|
|
1143
|
-
function authMiddleware(getAuth, opts = {}) {
|
|
1144
|
-
const basePath = opts.basePath ?? "/api/auth";
|
|
1145
|
-
return async (c, next) => {
|
|
1146
|
-
if (c.req.path === "/.well-known/openid-configuration") return c.redirect(`${process.env.CONVEX_SITE_URL}${basePath}/convex/.well-known/openid-configuration`);
|
|
1147
|
-
if (c.req.path.startsWith(basePath)) {
|
|
1148
|
-
if (opts.verbose) console.log("request headers", c.req.raw.headers);
|
|
1149
|
-
const auth = getAuth(c.env);
|
|
1150
|
-
let response;
|
|
1151
|
-
try {
|
|
1152
|
-
response = await auth.handler(c.req.raw);
|
|
1153
|
-
} catch (error) {
|
|
1154
|
-
const errorResponse = toAuthErrorResponse(error);
|
|
1155
|
-
if (errorResponse) return errorResponse;
|
|
1156
|
-
throw error;
|
|
1157
|
-
}
|
|
1158
|
-
if (opts.verbose) console.log("response headers", response.headers);
|
|
1159
|
-
return response;
|
|
1160
|
-
}
|
|
1161
|
-
return next();
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
//#endregion
|
|
1166
|
-
//#region src/internal/upstream/server/cors.ts
|
|
1167
|
-
/** biome-ignore-all lint: vendored upstream helper source */
|
|
1168
|
-
/**
|
|
1169
|
-
* Vendored from upstream helper repository at commit c5e52c8.
|
|
1170
|
-
* Source path: packages/convex_helpers/server/cors.ts
|
|
1171
|
-
*/
|
|
1172
|
-
/**
|
|
1173
|
-
* This file defines a CorsHttpRouter class that extends Convex's HttpRouter.
|
|
1174
|
-
* It provides CORS (Cross-Origin Resource Sharing) support for HTTP routes.
|
|
1175
|
-
*
|
|
1176
|
-
* The CorsHttpRouter:
|
|
1177
|
-
* 1. Allows specifying allowed origins for CORS.
|
|
1178
|
-
* 2. Overrides the route method to add CORS headers to all non-OPTIONS requests.
|
|
1179
|
-
* 3. Automatically adds an OPTIONS route to handle CORS preflight requests.
|
|
1180
|
-
* 4. Uses the handleCors helper function to apply CORS headers consistently.
|
|
1181
|
-
*
|
|
1182
|
-
* This router simplifies the process of making Convex HTTP endpoints
|
|
1183
|
-
* accessible to web applications hosted on different domains while
|
|
1184
|
-
* maintaining proper CORS configuration.
|
|
1185
|
-
*/
|
|
1186
|
-
const DEFAULT_EXPOSED_HEADERS = ["Content-Range", "Accept-Ranges"];
|
|
1187
|
-
/**
|
|
1188
|
-
* Factory function to create a router that adds CORS support to routes.
|
|
1189
|
-
* @param allowedOrigins An array of allowed origins for CORS.
|
|
1190
|
-
* @returns A function to use instead of http.route when you want CORS.
|
|
1191
|
-
*/
|
|
1192
|
-
const corsRouter = (http, corsConfig) => {
|
|
1193
|
-
const allowedExactMethodsByPath = /* @__PURE__ */ new Map();
|
|
1194
|
-
const allowedPrefixMethodsByPath = /* @__PURE__ */ new Map();
|
|
1195
|
-
return {
|
|
1196
|
-
http,
|
|
1197
|
-
route: (routeSpec) => {
|
|
1198
|
-
const tempRouter = httpRouter();
|
|
1199
|
-
tempRouter.exactRoutes = http.exactRoutes;
|
|
1200
|
-
tempRouter.prefixRoutes = http.prefixRoutes;
|
|
1201
|
-
const config = {
|
|
1202
|
-
...corsConfig,
|
|
1203
|
-
...routeSpec
|
|
1204
|
-
};
|
|
1205
|
-
const httpCorsHandler = handleCors({
|
|
1206
|
-
originalHandler: routeSpec.handler,
|
|
1207
|
-
allowedMethods: [routeSpec.method],
|
|
1208
|
-
...config
|
|
1209
|
-
});
|
|
1210
|
-
/**
|
|
1211
|
-
* Figure out what kind of route we're adding: exact or prefix and handle
|
|
1212
|
-
* accordingly.
|
|
1213
|
-
*/
|
|
1214
|
-
if ("path" in routeSpec) {
|
|
1215
|
-
let methods = allowedExactMethodsByPath.get(routeSpec.path);
|
|
1216
|
-
if (!methods) {
|
|
1217
|
-
methods = /* @__PURE__ */ new Set();
|
|
1218
|
-
allowedExactMethodsByPath.set(routeSpec.path, methods);
|
|
1219
|
-
}
|
|
1220
|
-
methods.add(routeSpec.method);
|
|
1221
|
-
tempRouter.route({
|
|
1222
|
-
path: routeSpec.path,
|
|
1223
|
-
method: routeSpec.method,
|
|
1224
|
-
handler: httpCorsHandler
|
|
1225
|
-
});
|
|
1226
|
-
handleExactRoute(tempRouter, routeSpec, config, Array.from(methods));
|
|
1227
|
-
} else {
|
|
1228
|
-
let methods = allowedPrefixMethodsByPath.get(routeSpec.pathPrefix);
|
|
1229
|
-
if (!methods) {
|
|
1230
|
-
methods = /* @__PURE__ */ new Set();
|
|
1231
|
-
allowedPrefixMethodsByPath.set(routeSpec.pathPrefix, methods);
|
|
1232
|
-
}
|
|
1233
|
-
methods.add(routeSpec.method);
|
|
1234
|
-
tempRouter.route({
|
|
1235
|
-
pathPrefix: routeSpec.pathPrefix,
|
|
1236
|
-
method: routeSpec.method,
|
|
1237
|
-
handler: httpCorsHandler
|
|
1238
|
-
});
|
|
1239
|
-
handlePrefixRoute(tempRouter, routeSpec, config, Array.from(methods));
|
|
1240
|
-
}
|
|
1241
|
-
/**
|
|
1242
|
-
* Copy the routes from the temporary router to the main router.
|
|
1243
|
-
*/
|
|
1244
|
-
http.exactRoutes = new Map(tempRouter.exactRoutes);
|
|
1245
|
-
http.prefixRoutes = new Map(tempRouter.prefixRoutes);
|
|
1246
|
-
}
|
|
1247
|
-
};
|
|
1248
|
-
};
|
|
1249
|
-
/**
|
|
1250
|
-
* Handles exact route matching and adds OPTIONS handler.
|
|
1251
|
-
* @param tempRouter Temporary router instance.
|
|
1252
|
-
* @param routeSpec Route specification for exact matching.
|
|
1253
|
-
*/
|
|
1254
|
-
function handleExactRoute(tempRouter, routeSpec, config, allowedMethods) {
|
|
1255
|
-
const currentMethodsForPath = tempRouter.exactRoutes.get(routeSpec.path);
|
|
1256
|
-
/**
|
|
1257
|
-
* Add the OPTIONS handler for the given path
|
|
1258
|
-
*/
|
|
1259
|
-
const optionsHandler = createOptionsHandlerForMethods(allowedMethods, config);
|
|
1260
|
-
currentMethodsForPath?.set("OPTIONS", optionsHandler);
|
|
1261
|
-
tempRouter.exactRoutes.set(routeSpec.path, new Map(currentMethodsForPath));
|
|
1262
|
-
}
|
|
1263
|
-
/**
|
|
1264
|
-
* Handles prefix route matching and adds OPTIONS handler.
|
|
1265
|
-
* @param tempRouter Temporary router instance.
|
|
1266
|
-
* @param routeSpec Route specification for prefix matching.
|
|
1267
|
-
*/
|
|
1268
|
-
function handlePrefixRoute(tempRouter, routeSpec, config, allowedMethods) {
|
|
1269
|
-
/**
|
|
1270
|
-
* prefixRoutes is structured differently than exactRoutes. It's defined as
|
|
1271
|
-
* a Map<string, Map<string, PublicHttpAction>> where the KEY is the
|
|
1272
|
-
* METHOD and the VALUE is a map of paths and handlers.
|
|
1273
|
-
*/
|
|
1274
|
-
const optionsHandler = createOptionsHandlerForMethods(allowedMethods, config);
|
|
1275
|
-
const optionsPrefixes = tempRouter.prefixRoutes.get("OPTIONS") || /* @__PURE__ */ new Map();
|
|
1276
|
-
optionsPrefixes.set(routeSpec.pathPrefix, optionsHandler);
|
|
1277
|
-
tempRouter.prefixRoutes.set("OPTIONS", optionsPrefixes);
|
|
1278
|
-
}
|
|
1279
|
-
/**
|
|
1280
|
-
* Creates an OPTIONS handler for the given HTTP methods.
|
|
1281
|
-
* @param methods Array of HTTP methods to be allowed.
|
|
1282
|
-
* @returns A CORS-enabled OPTIONS handler.
|
|
1283
|
-
*/
|
|
1284
|
-
function createOptionsHandlerForMethods(methods, config) {
|
|
1285
|
-
return handleCors({
|
|
1286
|
-
...config,
|
|
1287
|
-
allowedMethods: methods
|
|
1288
|
-
});
|
|
1289
|
-
}
|
|
1290
|
-
/**
|
|
1291
|
-
* handleCors() is a higher-order function that wraps a Convex HTTP action handler to add CORS support.
|
|
1292
|
-
* It allows for customization of allowed HTTP methods and origins for cross-origin requests.
|
|
1293
|
-
*
|
|
1294
|
-
* The function:
|
|
1295
|
-
* 1. Validates and normalizes the allowed HTTP methods.
|
|
1296
|
-
* 2. Generates appropriate CORS headers based on the provided configuration.
|
|
1297
|
-
* 3. Handles preflight OPTIONS requests automatically.
|
|
1298
|
-
* 4. Wraps the original handler to add CORS headers to its response.
|
|
1299
|
-
*
|
|
1300
|
-
* This helper simplifies the process of making Convex HTTP actions accessible
|
|
1301
|
-
* to web applications hosted on different domains.
|
|
1302
|
-
*/
|
|
1303
|
-
const SECONDS_IN_A_DAY = 3600 * 24;
|
|
1304
|
-
/**
|
|
1305
|
-
* Example CORS origins:
|
|
1306
|
-
* - "*" (allow all origins)
|
|
1307
|
-
* - "https://example.com" (allow a specific domain)
|
|
1308
|
-
* - "https://*.example.com" (allow all subdomains of example.com)
|
|
1309
|
-
* - "https://example1.com, https://example2.com" (allow multiple specific domains)
|
|
1310
|
-
* - "null" (allow requests from data URLs or local files)
|
|
1311
|
-
*/
|
|
1312
|
-
const handleCors = ({ originalHandler, allowedMethods = ["OPTIONS"], allowedOrigins = ["*"], allowedHeaders = ["Content-Type"], exposedHeaders = DEFAULT_EXPOSED_HEADERS, allowCredentials = false, browserCacheMaxAge = SECONDS_IN_A_DAY, enforceAllowOrigins = false, debug = false }) => {
|
|
1313
|
-
const filteredMethods = Array.from(new Set(allowedMethods.map((method) => method.toUpperCase()))).filter((method) => ROUTABLE_HTTP_METHODS.includes(method));
|
|
1314
|
-
if (filteredMethods.length === 0) throw new Error("No valid HTTP methods provided");
|
|
1315
|
-
/**
|
|
1316
|
-
* Ensure OPTIONS is not duplicated if it was passed in
|
|
1317
|
-
* E.g. if allowedMethods = ["GET", "OPTIONS"]
|
|
1318
|
-
*/
|
|
1319
|
-
const allowMethods = filteredMethods.includes("OPTIONS") ? filteredMethods.join(", ") : [...filteredMethods].join(", ");
|
|
1320
|
-
/**
|
|
1321
|
-
* Build up the set of CORS headers
|
|
1322
|
-
*/
|
|
1323
|
-
const commonHeaders = { Vary: "Origin" };
|
|
1324
|
-
if (allowCredentials) commonHeaders["Access-Control-Allow-Credentials"] = "true";
|
|
1325
|
-
if (exposedHeaders.length > 0) commonHeaders["Access-Control-Expose-Headers"] = exposedHeaders.join(", ");
|
|
1326
|
-
async function parseAllowedOrigins(request) {
|
|
1327
|
-
return Array.isArray(allowedOrigins) ? allowedOrigins : await allowedOrigins(request);
|
|
1328
|
-
}
|
|
1329
|
-
async function isAllowedOrigin(request) {
|
|
1330
|
-
const requestOrigin = request.headers.get("origin");
|
|
1331
|
-
if (!requestOrigin) return false;
|
|
1332
|
-
return (await parseAllowedOrigins(request)).some((allowed) => {
|
|
1333
|
-
if (allowed === "*") return true;
|
|
1334
|
-
if (allowed === requestOrigin) return true;
|
|
1335
|
-
if (allowed.startsWith("*.")) {
|
|
1336
|
-
const wildcardDomain = allowed.slice(1);
|
|
1337
|
-
const rootDomain = allowed.slice(2);
|
|
1338
|
-
try {
|
|
1339
|
-
const url = new URL(requestOrigin);
|
|
1340
|
-
return url.protocol === "https:" && (url.hostname.endsWith(wildcardDomain) || url.hostname === rootDomain);
|
|
1341
|
-
} catch {
|
|
1342
|
-
return false;
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
return false;
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
/**
|
|
1349
|
-
* Return our modified HTTP action
|
|
1350
|
-
*/
|
|
1351
|
-
return httpActionGeneric(async (ctx, request) => {
|
|
1352
|
-
if (debug) console.log("CORS request", {
|
|
1353
|
-
path: request.url,
|
|
1354
|
-
origin: request.headers.get("origin"),
|
|
1355
|
-
headers: request.headers,
|
|
1356
|
-
method: request.method,
|
|
1357
|
-
body: request.body
|
|
1358
|
-
});
|
|
1359
|
-
const requestOrigin = request.headers.get("origin");
|
|
1360
|
-
const parsedAllowedOrigins = await parseAllowedOrigins(request);
|
|
1361
|
-
if (debug) console.log("allowed origins", parsedAllowedOrigins);
|
|
1362
|
-
let allowOrigins = null;
|
|
1363
|
-
if (parsedAllowedOrigins.includes("*") && requestOrigin && !allowCredentials) allowOrigins = requestOrigin;
|
|
1364
|
-
else if (requestOrigin) {
|
|
1365
|
-
if (await isAllowedOrigin(request)) allowOrigins = requestOrigin;
|
|
1366
|
-
}
|
|
1367
|
-
if (enforceAllowOrigins && !allowOrigins) {
|
|
1368
|
-
console.error(`Request from origin ${requestOrigin} blocked, missing from allowed origins: ${parsedAllowedOrigins.join()}`);
|
|
1369
|
-
return new Response(null, { status: 403 });
|
|
1370
|
-
}
|
|
1371
|
-
/**
|
|
1372
|
-
* OPTIONS has no handler and just returns headers
|
|
1373
|
-
*/
|
|
1374
|
-
if (request.method === "OPTIONS") {
|
|
1375
|
-
const responseHeaders = new Headers({
|
|
1376
|
-
...commonHeaders,
|
|
1377
|
-
...allowOrigins ? { "Access-Control-Allow-Origin": allowOrigins } : {},
|
|
1378
|
-
"Access-Control-Allow-Methods": allowMethods,
|
|
1379
|
-
"Access-Control-Allow-Headers": allowedHeaders.join(", "),
|
|
1380
|
-
"Access-Control-Max-Age": browserCacheMaxAge.toString()
|
|
1381
|
-
});
|
|
1382
|
-
if (debug) console.log("CORS OPTIONS response headers", responseHeaders);
|
|
1383
|
-
return new Response(null, {
|
|
1384
|
-
status: 204,
|
|
1385
|
-
headers: responseHeaders
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* If the method is not OPTIONS, it must pass a handler
|
|
1390
|
-
*/
|
|
1391
|
-
if (!originalHandler) throw new Error("No PublicHttpAction provider to CORS handler");
|
|
1392
|
-
const originalResponse = await ("_handler" in originalHandler ? originalHandler["_handler"] : originalHandler)(ctx, request);
|
|
1393
|
-
/**
|
|
1394
|
-
* Second, get a copy of the original response's headers and add the
|
|
1395
|
-
* allow origin header if it's allowed
|
|
1396
|
-
*/
|
|
1397
|
-
const newHeaders = new Headers(originalResponse.headers);
|
|
1398
|
-
if (allowOrigins) newHeaders.set("Access-Control-Allow-Origin", allowOrigins);
|
|
1399
|
-
/**
|
|
1400
|
-
* Third, add or update our other CORS headers
|
|
1401
|
-
*/
|
|
1402
|
-
Object.entries(commonHeaders).forEach(([key, value]) => {
|
|
1403
|
-
newHeaders.set(key, value);
|
|
1404
|
-
});
|
|
1405
|
-
if (debug) console.log("CORS response headers", newHeaders);
|
|
1406
|
-
/**
|
|
1407
|
-
* Fourth, return the modified Response.
|
|
1408
|
-
* A Response object is immutable, so we create a new one to return here.
|
|
1409
|
-
*/
|
|
1410
|
-
return new Response(originalResponse.body, {
|
|
1411
|
-
status: originalResponse.status,
|
|
1412
|
-
statusText: originalResponse.statusText,
|
|
1413
|
-
headers: newHeaders
|
|
1414
|
-
});
|
|
1415
|
-
});
|
|
1416
|
-
};
|
|
1417
|
-
|
|
1418
|
-
//#endregion
|
|
1419
|
-
//#region src/auth/registerRoutes.ts
|
|
1420
|
-
/** biome-ignore-all lint/suspicious/noConsole: lib */
|
|
1421
|
-
const registerRoutes = (http, getAuth, opts = {}) => {
|
|
1422
|
-
const staticAuth = getAuth({});
|
|
1423
|
-
const path = staticAuth.options.basePath ?? "/api/auth";
|
|
1424
|
-
const authRequestHandler = httpActionGeneric(async (ctx, request) => {
|
|
1425
|
-
if (opts?.verbose) {
|
|
1426
|
-
console.log("options.baseURL", staticAuth.options.baseURL);
|
|
1427
|
-
console.log("request headers", request.headers);
|
|
1428
|
-
}
|
|
1429
|
-
const auth = getAuth(ctx);
|
|
1430
|
-
let response;
|
|
1431
|
-
try {
|
|
1432
|
-
response = await auth.handler(request);
|
|
1433
|
-
} catch (error) {
|
|
1434
|
-
const errorResponse = toAuthErrorResponse(error);
|
|
1435
|
-
if (errorResponse) return errorResponse;
|
|
1436
|
-
throw error;
|
|
1437
|
-
}
|
|
1438
|
-
if (opts?.verbose) console.log("response headers", response.headers);
|
|
1439
|
-
return response;
|
|
1440
|
-
});
|
|
1441
|
-
if (!http.lookup("/.well-known/openid-configuration", "GET")) http.route({
|
|
1442
|
-
handler: httpActionGeneric(async () => {
|
|
1443
|
-
const url = `${process.env.CONVEX_SITE_URL}${path}/convex/.well-known/openid-configuration`;
|
|
1444
|
-
return Response.redirect(url);
|
|
1445
|
-
}),
|
|
1446
|
-
method: "GET",
|
|
1447
|
-
path: "/.well-known/openid-configuration"
|
|
1448
|
-
});
|
|
1449
|
-
if (!opts.cors) {
|
|
1450
|
-
http.route({
|
|
1451
|
-
handler: authRequestHandler,
|
|
1452
|
-
method: "GET",
|
|
1453
|
-
pathPrefix: `${path}/`
|
|
1454
|
-
});
|
|
1455
|
-
http.route({
|
|
1456
|
-
handler: authRequestHandler,
|
|
1457
|
-
method: "POST",
|
|
1458
|
-
pathPrefix: `${path}/`
|
|
1459
|
-
});
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
const corsOpts = typeof opts.cors === "boolean" ? {
|
|
1463
|
-
allowedHeaders: [],
|
|
1464
|
-
allowedOrigins: [],
|
|
1465
|
-
exposedHeaders: []
|
|
1466
|
-
} : opts.cors;
|
|
1467
|
-
let trustedOriginsOption;
|
|
1468
|
-
const cors = corsRouter(http, {
|
|
1469
|
-
allowCredentials: true,
|
|
1470
|
-
allowedHeaders: [
|
|
1471
|
-
"Content-Type",
|
|
1472
|
-
"Better-Auth-Cookie",
|
|
1473
|
-
"Authorization"
|
|
1474
|
-
].concat(corsOpts.allowedHeaders ?? []),
|
|
1475
|
-
debug: opts?.verbose,
|
|
1476
|
-
enforceAllowOrigins: false,
|
|
1477
|
-
exposedHeaders: ["Set-Better-Auth-Cookie"].concat(corsOpts.exposedHeaders ?? []),
|
|
1478
|
-
allowedOrigins: async (request) => {
|
|
1479
|
-
trustedOriginsOption = trustedOriginsOption ?? (await staticAuth.$context).options.trustedOrigins ?? [];
|
|
1480
|
-
return (Array.isArray(trustedOriginsOption) ? trustedOriginsOption : await trustedOriginsOption?.(request) ?? []).map((origin) => origin.endsWith("*") && origin.length > 1 ? origin.slice(0, -1) : origin).concat(corsOpts.allowedOrigins ?? []);
|
|
1481
|
-
}
|
|
1482
|
-
});
|
|
1483
|
-
cors.route({
|
|
1484
|
-
handler: authRequestHandler,
|
|
1485
|
-
method: "GET",
|
|
1486
|
-
pathPrefix: `${path}/`
|
|
1487
|
-
});
|
|
1488
|
-
cors.route({
|
|
1489
|
-
handler: authRequestHandler,
|
|
1490
|
-
method: "POST",
|
|
1491
|
-
pathPrefix: `${path}/`
|
|
1492
|
-
});
|
|
1493
|
-
};
|
|
1494
|
-
|
|
1495
|
-
//#endregion
|
|
1496
|
-
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 };
|
|
1183
|
+
export { adapterArgsValidator, adapterConfig, adapterWhereValidator, checkUniqueFields, convex, createApi, createAuthRuntime, createClient, createDisabledAuthRuntime, createHandler, dbAdapter, defineAuth, deleteManyHandler, deleteOneHandler, findManyHandler, findOneHandler, getAuthUserId, getAuthUserIdentity, getGeneratedAuthDisabledReason, getHeaders, getSession, handlePagination, hasUniqueFields, httpAdapter, listOne, paginate, resolveGeneratedAuthDefinition, selectFields, updateManyHandler, updateOneHandler };
|