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.
Files changed (47) hide show
  1. package/dist/aggregate/index.d.ts +388 -0
  2. package/dist/aggregate/index.js +37 -0
  3. package/dist/{auth-client → auth/client}/index.js +1 -1
  4. package/dist/auth/http/index.d.ts +63 -0
  5. package/dist/auth/http/index.js +429 -0
  6. package/dist/auth/index.d.ts +19001 -185
  7. package/dist/auth/index.js +373 -686
  8. package/dist/{auth-nextjs → auth/nextjs}/index.d.ts +3 -4
  9. package/dist/{auth-nextjs → auth/nextjs}/index.js +3 -5
  10. package/dist/{caller-factory-B1FvYSKr.js → caller-factory-Dmgv8MLS.js} +15 -12
  11. package/dist/cli.mjs +2601 -13
  12. package/dist/codegen-Cz1idI3-.mjs +969 -0
  13. package/dist/{create-schema-orm-DplxTtYj.js → create-schema-orm-69VF4CFV.js} +4 -3
  14. package/dist/crpc/index.d.ts +2 -2
  15. package/dist/crpc/index.js +3 -3
  16. package/dist/{http-types-BRLY10NX.d.ts → http-types-BCf2wCgp.d.ts} +25 -25
  17. package/dist/meta-utils-DDVYp9Xf.js +117 -0
  18. package/dist/orm/index.d.ts +4 -3012
  19. package/dist/orm/index.js +9631 -2
  20. package/dist/{index-BQkhP2ny.d.ts → procedure-caller-CcjtUFvL.d.ts} +211 -74
  21. package/dist/query-context-BDSis9rT.js +1518 -0
  22. package/dist/query-context-DGExXZIV.d.ts +42 -0
  23. package/dist/react/index.d.ts +31 -35
  24. package/dist/react/index.js +145 -58
  25. package/dist/rsc/index.d.ts +4 -7
  26. package/dist/rsc/index.js +14 -10
  27. package/dist/runtime-B9xQFY8W.js +2280 -0
  28. package/dist/server/index.d.ts +3 -4
  29. package/dist/server/index.js +384 -10
  30. package/dist/{types-o-5rYcTr.d.ts → types-CIBGEYXq.d.ts} +4 -3
  31. package/dist/types-DgwvxKbT.d.ts +4 -0
  32. package/dist/watcher.mjs +8 -8
  33. package/dist/where-clause-compiler-CRP-i1Qa.d.ts +3463 -0
  34. package/package.json +14 -10
  35. package/dist/codegen-DkpPBVPn.mjs +0 -189
  36. package/dist/context-utils-DSuX99Da.d.ts +0 -17
  37. package/dist/meta-utils-DCpLSBWB.js +0 -41
  38. package/dist/orm-CleikBIV.js +0 -8820
  39. /package/dist/{auth-client → auth/client}/index.d.ts +0 -0
  40. /package/dist/{auth-config → auth/config}/index.d.ts +0 -0
  41. /package/dist/{auth-config → auth/config}/index.js +0 -0
  42. /package/dist/{create-schema-DhWXOhnU.js → create-schema-BdZOL6ns.js} +0 -0
  43. /package/dist/{customFunctions-C1okqCzL.js → customFunctions-CZnCwoR3.js} +0 -0
  44. /package/dist/{error-BZUhlhYz.js → error-Be4OcwwD.js} +0 -0
  45. /package/dist/{query-options-BL1Q0X7q.js → query-options-B0c1b6pZ.js} +0 -0
  46. /package/dist/{transformer-CTNSPjwp.js → transformer-Dh0w2py0.js} +0 -0
  47. /package/dist/{types-jftzhhuc.d.ts → types-DwGkkq2s.d.ts} +0 -0
@@ -0,0 +1,969 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { createJiti } from "jiti";
5
+
6
+ //#region src/shared/meta-utils.ts
7
+ /** Files to exclude from meta generation */
8
+ const EXCLUDED_FILES = new Set([
9
+ "schema.ts",
10
+ "auth.ts",
11
+ "generated.ts",
12
+ "convex.config.ts",
13
+ "auth.config.ts"
14
+ ]);
15
+ /**
16
+ * Check if a file path should be included in meta generation.
17
+ * Filters out private files/directories (prefixed with _) and config files.
18
+ */
19
+ function isValidConvexFile(file) {
20
+ if (file.endsWith(".runtime.ts")) return false;
21
+ if (file.startsWith("generated/")) return false;
22
+ if (file.startsWith("_") || file.includes("/_")) return false;
23
+ const basename = file.split("/").pop() ?? "";
24
+ if (EXCLUDED_FILES.has(basename)) return false;
25
+ return true;
26
+ }
27
+
28
+ //#endregion
29
+ //#region src/cli/codegen.ts
30
+ /** Valid JS identifier pattern for object keys */
31
+ const VALID_IDENTIFIER_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
32
+ /** Valid JS identifier start pattern */
33
+ const IDENTIFIER_START_RE = /^[a-zA-Z_$]/;
34
+ /** Pattern to strip .ts extension */
35
+ const TS_EXTENSION_RE = /\.ts$/;
36
+ /** Pattern to detect default exports in auth contract files. */
37
+ const DEFAULT_EXPORT_RE = /\bexport\s+default\b/;
38
+ const RUNTIME_CALLER_RESERVED_EXPORTS = new Set(["actions", "schedule"]);
39
+ const CODEGEN_SCOPES = new Set([
40
+ "all",
41
+ "auth",
42
+ "orm"
43
+ ]);
44
+ function normalizeCodegenScope(scope) {
45
+ const normalized = scope ?? "all";
46
+ if (CODEGEN_SCOPES.has(normalized)) return normalized;
47
+ throw new Error(`Invalid codegen scope "${normalized}". Expected one of: all, auth, orm.`);
48
+ }
49
+ function shouldGenerateApi(scope) {
50
+ return scope === "all";
51
+ }
52
+ function shouldGenerateAuth(scope) {
53
+ return scope !== "orm";
54
+ }
55
+ function getGenerationLabel(generateApi, generateAuth) {
56
+ if (generateApi && generateAuth) return "all";
57
+ if (!generateApi && generateAuth) return "auth";
58
+ if (!generateApi && !generateAuth) return "orm";
59
+ return "api";
60
+ }
61
+ function resolveGenerationMode(options) {
62
+ const hasApiToggle = typeof options?.api === "boolean";
63
+ const hasAuthToggle = typeof options?.auth === "boolean";
64
+ if (hasApiToggle || hasAuthToggle) {
65
+ const generateApi = options?.api ?? true;
66
+ const generateAuth = options?.auth ?? true;
67
+ return {
68
+ generateApi,
69
+ generateAuth,
70
+ modeLabel: getGenerationLabel(generateApi, generateAuth)
71
+ };
72
+ }
73
+ const scope = normalizeCodegenScope(options?.scope);
74
+ return {
75
+ generateApi: shouldGenerateApi(scope),
76
+ generateAuth: shouldGenerateAuth(scope),
77
+ modeLabel: scope
78
+ };
79
+ }
80
+ const AUTH_RUNTIME_PROCEDURES = [
81
+ {
82
+ exportName: "create",
83
+ internal: true,
84
+ type: "mutation"
85
+ },
86
+ {
87
+ exportName: "deleteMany",
88
+ internal: true,
89
+ type: "mutation"
90
+ },
91
+ {
92
+ exportName: "deleteOne",
93
+ internal: true,
94
+ type: "mutation"
95
+ },
96
+ {
97
+ exportName: "findMany",
98
+ internal: true,
99
+ type: "query"
100
+ },
101
+ {
102
+ exportName: "findOne",
103
+ internal: true,
104
+ type: "query"
105
+ },
106
+ {
107
+ exportName: "getLatestJwks",
108
+ internal: true,
109
+ type: "action"
110
+ },
111
+ {
112
+ exportName: "rotateKeys",
113
+ internal: true,
114
+ type: "action"
115
+ },
116
+ {
117
+ exportName: "updateMany",
118
+ internal: true,
119
+ type: "mutation"
120
+ },
121
+ {
122
+ exportName: "updateOne",
123
+ internal: true,
124
+ type: "mutation"
125
+ }
126
+ ];
127
+ const GENERATED_SERVER_RUNTIME_PROCEDURES = [
128
+ {
129
+ exportName: "scheduledMutationBatch",
130
+ internal: true,
131
+ type: "mutation"
132
+ },
133
+ {
134
+ exportName: "scheduledDelete",
135
+ internal: true,
136
+ type: "mutation"
137
+ },
138
+ {
139
+ exportName: "aggregateBackfill",
140
+ internal: true,
141
+ type: "mutation"
142
+ },
143
+ {
144
+ exportName: "aggregateBackfillChunk",
145
+ internal: true,
146
+ type: "mutation"
147
+ },
148
+ {
149
+ exportName: "aggregateBackfillStatus",
150
+ internal: true,
151
+ type: "mutation"
152
+ },
153
+ {
154
+ exportName: "resetChunk",
155
+ internal: true,
156
+ type: "mutation"
157
+ },
158
+ {
159
+ exportName: "reset",
160
+ internal: true,
161
+ type: "action"
162
+ }
163
+ ];
164
+ function listFilesRecursive(cwd, relDir = "") {
165
+ const absDir = path.join(cwd, relDir);
166
+ const entries = fs.readdirSync(absDir, { withFileTypes: true });
167
+ const files = [];
168
+ for (const entry of entries) {
169
+ const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
170
+ if (entry.isDirectory()) {
171
+ files.push(...listFilesRecursive(cwd, relPath));
172
+ continue;
173
+ }
174
+ if (entry.isFile()) files.push(relPath);
175
+ }
176
+ return files;
177
+ }
178
+ function ensureRelativeImportPath(value) {
179
+ if (value.startsWith(".") || value.startsWith("/")) return value;
180
+ return `./${value}`;
181
+ }
182
+ function normalizeImportPath(value) {
183
+ return value.replace(/\\/g, "/");
184
+ }
185
+ function formatKey(key) {
186
+ return VALID_IDENTIFIER_RE.test(key) ? key : `'${key}'`;
187
+ }
188
+ function toPascalCaseToken(token) {
189
+ if (token.length === 0) return "";
190
+ return `${token[0]?.toUpperCase() ?? ""}${token.slice(1)}`;
191
+ }
192
+ function getModuleRuntimeExportBase(moduleName) {
193
+ const base = moduleName.split("/").filter(Boolean).flatMap((segment) => segment.split(/[^a-zA-Z0-9]+/g).filter(Boolean).map((token) => toPascalCaseToken(token))).join("");
194
+ if (base.length === 0) return "Module";
195
+ return IDENTIFIER_START_RE.test(base) ? base : `M${base}`;
196
+ }
197
+ function getModuleRuntimeExportNames(moduleName) {
198
+ if (moduleName === "generated/server") return {
199
+ callerExportName: "createServerCaller",
200
+ handlerExportName: "createServerHandler"
201
+ };
202
+ const base = getModuleRuntimeExportBase(moduleName);
203
+ return {
204
+ callerExportName: `create${base}Caller`,
205
+ handlerExportName: `create${base}Handler`
206
+ };
207
+ }
208
+ function getAccessPath(base, segments) {
209
+ return segments.reduce((acc, segment) => `${acc}[${JSON.stringify(segment)}]`, base);
210
+ }
211
+ function getModuleImportPath(outputFile, functionsDir, moduleName) {
212
+ const moduleFile = path.join(functionsDir, moduleName);
213
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), moduleFile)));
214
+ }
215
+ function getRuntimeApiImportPath(outputFile, functionsDir) {
216
+ const runtimeApiFile = path.join(functionsDir, "_generated", "api.js");
217
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), runtimeApiFile)));
218
+ }
219
+ function getSchemaImportPath(outputFile, functionsDir) {
220
+ const schemaFile = path.join(functionsDir, "schema");
221
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), schemaFile)));
222
+ }
223
+ function getServerTypesImportPath(outputFile, functionsDir) {
224
+ const serverTypesFile = path.join(functionsDir, "_generated", "server");
225
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), serverTypesFile)));
226
+ }
227
+ function getDataModelImportPath(outputFile, functionsDir) {
228
+ const dataModelFile = path.join(functionsDir, "_generated", "dataModel");
229
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), dataModelFile)));
230
+ }
231
+ function getHttpImportPath(outputFile, functionsDir) {
232
+ const httpFile = path.join(functionsDir, "http");
233
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), httpFile)));
234
+ }
235
+ const GENERATED_DIR = "generated";
236
+ function getGeneratedServerOutputFile(functionsDir) {
237
+ return path.join(functionsDir, GENERATED_DIR, "server.ts");
238
+ }
239
+ function getGeneratedAuthOutputFile(functionsDir) {
240
+ return path.join(functionsDir, GENERATED_DIR, "auth.ts");
241
+ }
242
+ function getLegacyGeneratedOutputFile(functionsDir) {
243
+ return path.join(functionsDir, "generated.ts");
244
+ }
245
+ function getModuleNameFromOutputFile(outputFile, functionsDir) {
246
+ return normalizeImportPath(path.relative(functionsDir, outputFile)).replace(TS_EXTENSION_RE, "");
247
+ }
248
+ function getGeneratedServerImportPath(outputFile, functionsDir) {
249
+ const generatedServerFile = getGeneratedServerOutputFile(functionsDir);
250
+ return ensureRelativeImportPath(normalizeImportPath(path.relative(path.dirname(outputFile), generatedServerFile)).replace(TS_EXTENSION_RE, ""));
251
+ }
252
+ function getGeneratedRuntimeOutputFile(functionsDir, moduleName) {
253
+ const runtimeModuleName = moduleName.startsWith(`${GENERATED_DIR}/`) ? moduleName.slice(10) : moduleName;
254
+ return path.join(functionsDir, GENERATED_DIR, `${runtimeModuleName}.runtime.ts`);
255
+ }
256
+ function emitGeneratedServerPlaceholderFile() {
257
+ return `// biome-ignore-all format: generated
258
+ // This file is auto-generated by better-convex
259
+ // Do not edit manually. Run \`better-convex codegen\` to regenerate.
260
+
261
+ import { initCRPC as baseInitCRPC } from 'better-convex/server';
262
+
263
+ export type QueryCtx = unknown;
264
+ export type MutationCtx = unknown;
265
+ export type ActionCtx = unknown;
266
+ export type GenericCtx = QueryCtx | MutationCtx | ActionCtx;
267
+ export type OrmCtx<Ctx = QueryCtx> = Ctx;
268
+
269
+ export const orm = {} as Record<string, unknown>;
270
+ export const scheduledMutationBatch = undefined as unknown;
271
+ export const scheduledDelete = undefined as unknown;
272
+ export const initCRPC = baseInitCRPC;
273
+
274
+ export function withOrm<Ctx>(ctx: Ctx): Ctx {
275
+ return ctx;
276
+ }
277
+ `;
278
+ }
279
+ function emitGeneratedAuthPlaceholderFile() {
280
+ return `// biome-ignore-all format: generated
281
+ // This file is auto-generated by better-convex
282
+ // Do not edit manually. Run \`better-convex codegen\` to regenerate.
283
+
284
+ export function defineAuth<TDefinition>(definition: TDefinition): TDefinition {
285
+ return definition;
286
+ }
287
+
288
+ export const authEnabled = false;
289
+ export const authClient = {} as Record<string, unknown>;
290
+ export const getAuth = () => ({} as Record<string, unknown>);
291
+ export const auth = {} as Record<string, unknown>;
292
+ `;
293
+ }
294
+ function ensureGeneratedSupportPlaceholders(functionsDir, options) {
295
+ const serverOutputFile = getGeneratedServerOutputFile(functionsDir);
296
+ const authOutputFile = getGeneratedAuthOutputFile(functionsDir);
297
+ const generatedDir = path.dirname(serverOutputFile);
298
+ fs.mkdirSync(generatedDir, { recursive: true });
299
+ const includeAuth = options?.includeAuth ?? true;
300
+ if (!fs.existsSync(serverOutputFile)) fs.writeFileSync(serverOutputFile, emitGeneratedServerPlaceholderFile());
301
+ if (includeAuth && !fs.existsSync(authOutputFile)) fs.writeFileSync(authOutputFile, emitGeneratedAuthPlaceholderFile());
302
+ }
303
+ function emitGeneratedRuntimePlaceholderFile(moduleName) {
304
+ const { callerExportName, handlerExportName } = getModuleRuntimeExportNames(moduleName);
305
+ return `// biome-ignore-all format: generated
306
+ // This file is auto-generated by better-convex
307
+ // Do not edit manually. Run \`better-convex codegen\` to regenerate.
308
+
309
+ export function ${callerExportName}(_ctx: unknown) {
310
+ throw new Error('[better-convex] Runtime caller is not generated yet. Run better-convex codegen.');
311
+ }
312
+
313
+ export function ${handlerExportName}(_ctx: unknown) {
314
+ throw new Error('[better-convex] Runtime handler is not generated yet. Run better-convex codegen.');
315
+ }
316
+ `;
317
+ }
318
+ function ensureGeneratedRuntimePlaceholders(functionsDir, moduleNames) {
319
+ const createdPlaceholderFiles = [];
320
+ for (const moduleName of moduleNames) {
321
+ const runtimeOutputFile = getGeneratedRuntimeOutputFile(functionsDir, moduleName);
322
+ if (fs.existsSync(runtimeOutputFile)) continue;
323
+ fs.mkdirSync(path.dirname(runtimeOutputFile), { recursive: true });
324
+ fs.writeFileSync(runtimeOutputFile, emitGeneratedRuntimePlaceholderFile(moduleName));
325
+ createdPlaceholderFiles.push(runtimeOutputFile);
326
+ }
327
+ return createdPlaceholderFiles;
328
+ }
329
+ function listGeneratedRuntimeFiles(functionsDir) {
330
+ const generatedDir = path.join(functionsDir, "generated");
331
+ if (!fs.existsSync(generatedDir)) return [];
332
+ return listFilesRecursive(generatedDir).filter((file) => file.endsWith(".runtime.ts")).map((file) => path.join(generatedDir, file));
333
+ }
334
+ function emitGeneratedServerFile(outputFile, functionsDir, hasRelationsExport, hasTriggersExport) {
335
+ const asSingleQuotedImport = (importPath) => `'${importPath.replaceAll("'", "\\'")}'`;
336
+ const serverTypesImportPath = getServerTypesImportPath(outputFile, functionsDir);
337
+ const dataModelImportPath = getDataModelImportPath(outputFile, functionsDir);
338
+ const runtimeApiImportPath = getRuntimeApiImportPath(outputFile, functionsDir);
339
+ const schemaImportPath = getSchemaImportPath(outputFile, functionsDir);
340
+ const serverTypesImportLiteral = asSingleQuotedImport(serverTypesImportPath);
341
+ const dataModelImportLiteral = asSingleQuotedImport(dataModelImportPath);
342
+ const runtimeApiImportLiteral = asSingleQuotedImport(runtimeApiImportPath);
343
+ const schemaImportLiteral = asSingleQuotedImport(schemaImportPath);
344
+ if (!hasRelationsExport) return `// biome-ignore-all format: generated
345
+ // This file is auto-generated by better-convex
346
+ // Do not edit manually. Run \`better-convex codegen\` to regenerate.
347
+
348
+ import { initCRPC as baseInitCRPC } from 'better-convex/server';
349
+ import type { DataModel } from ${dataModelImportLiteral};
350
+ import type {
351
+ ActionCtx as ServerActionCtx,
352
+ MutationCtx as ServerMutationCtx,
353
+ QueryCtx as ServerQueryCtx,
354
+ } from ${serverTypesImportLiteral};
355
+
356
+ export type QueryCtx = ServerQueryCtx;
357
+ export type MutationCtx = ServerMutationCtx;
358
+ export type ActionCtx = ServerActionCtx;
359
+ export type GenericCtx = QueryCtx | MutationCtx | ActionCtx;
360
+ export const initCRPC = baseInitCRPC.dataModel<DataModel>();
361
+ `;
362
+ const ormFunctionsAccessor = getAccessPath("(internal as unknown as Record<string, any>)", getModuleNameFromOutputFile(outputFile, functionsDir).split("/").filter(Boolean));
363
+ return `// biome-ignore-all format: generated
364
+ // This file is auto-generated by better-convex
365
+ // Do not edit manually. Run \`better-convex codegen\` to regenerate.
366
+
367
+ import { createOrm, type GenericOrmCtx, type OrmFunctions } from 'better-convex/orm';
368
+ import { initCRPC as baseInitCRPC } from 'better-convex/server';
369
+ import { internal } from ${runtimeApiImportLiteral};
370
+ import type { DataModel } from ${dataModelImportLiteral};
371
+ import type {
372
+ ActionCtx as ServerActionCtx,
373
+ MutationCtx as ServerMutationCtx,
374
+ QueryCtx as ServerQueryCtx,
375
+ } from ${serverTypesImportLiteral};
376
+ import { internalMutation } from ${serverTypesImportLiteral};
377
+ import schema, { ${hasTriggersExport ? "relations, triggers" : "relations"} } from ${schemaImportLiteral};
378
+
379
+ const ormFunctions = ${ormFunctionsAccessor} as OrmFunctions;
380
+
381
+ export const orm = createOrm({
382
+ schema: relations,
383
+ ${hasTriggersExport ? " triggers,\n" : ""} ormFunctions,
384
+ internalMutation,
385
+ });
386
+
387
+ export type OrmCtx<Ctx extends ServerQueryCtx | ServerMutationCtx = ServerQueryCtx> = GenericOrmCtx<Ctx, typeof relations>;
388
+ export type QueryCtx = OrmCtx<ServerQueryCtx>;
389
+ export type MutationCtx = OrmCtx<ServerMutationCtx>;
390
+ export type ActionCtx = ServerActionCtx;
391
+ export type GenericCtx = QueryCtx | MutationCtx | ActionCtx;
392
+
393
+ export function withOrm<Ctx extends ServerQueryCtx | ServerMutationCtx>(ctx: Ctx) {
394
+ return orm.with(ctx) as OrmCtx<Ctx>;
395
+ }
396
+
397
+ export const initCRPC = baseInitCRPC.dataModel<DataModel>().context({
398
+ query: (ctx) => withOrm(ctx),
399
+ mutation: (ctx) => withOrm(ctx),
400
+ });
401
+
402
+ export const {
403
+ scheduledMutationBatch,
404
+ scheduledDelete,
405
+ aggregateBackfill,
406
+ aggregateBackfillChunk,
407
+ aggregateBackfillStatus,
408
+ resetChunk,
409
+ reset,
410
+ } = orm.api();
411
+ `;
412
+ }
413
+ function emitGeneratedAuthFile(outputFile, functionsDir, hasRelationsExport, authContract) {
414
+ const asSingleQuotedImport = (importPath) => `'${importPath.replaceAll("'", "\\'")}'`;
415
+ const runtimeApiImportPath = getRuntimeApiImportPath(outputFile, functionsDir);
416
+ const dataModelImportPath = getDataModelImportPath(outputFile, functionsDir);
417
+ const schemaImportPath = getSchemaImportPath(outputFile, functionsDir);
418
+ const serverImportPath = getGeneratedServerImportPath(outputFile, functionsDir);
419
+ const moduleNamespace = getModuleNameFromOutputFile(outputFile, functionsDir);
420
+ const authDefinitionImportPath = getModuleImportPath(outputFile, functionsDir, "auth");
421
+ const runtimeApiImportLiteral = asSingleQuotedImport(runtimeApiImportPath);
422
+ const dataModelImportLiteral = asSingleQuotedImport(dataModelImportPath);
423
+ const schemaImportLiteral = asSingleQuotedImport(schemaImportPath);
424
+ const serverImportLiteral = asSingleQuotedImport(serverImportPath);
425
+ const authDefinitionImportLiteral = asSingleQuotedImport(authDefinitionImportPath);
426
+ const hasAuthFile = authContract.hasAuthFile;
427
+ const hasAuthDefaultExport = authContract.hasAuthDefaultExport;
428
+ const disabledAuthReasonKind = hasAuthFile ? hasAuthDefaultExport ? "default_export_unavailable" : "missing_default_export" : "missing_auth_file";
429
+ const authRuntimeImports = `import {
430
+ ${[
431
+ "type BetterAuthOptionsWithoutDatabase",
432
+ "defineAuth as baseDefineAuth",
433
+ "createAuthRuntime",
434
+ "type GenericAuthDefinition",
435
+ "getGeneratedAuthDisabledReason",
436
+ hasAuthDefaultExport ? "resolveGeneratedAuthDefinition" : "createDisabledAuthRuntime"
437
+ ].join(",\n ")},
438
+ } from 'better-convex/auth';`;
439
+ const authDefinitionImport = hasAuthDefaultExport ? `import * as authDefinitionModule from ${authDefinitionImportLiteral};` : "";
440
+ return `// biome-ignore-all format: generated
441
+ // This file is auto-generated by better-convex
442
+ // Do not edit manually. Run \`better-convex codegen\` to regenerate.
443
+
444
+ ${authRuntimeImports}
445
+ ${hasAuthDefaultExport ? `import { internal } from ${runtimeApiImportLiteral};` : ""}
446
+ import type { DataModel } from ${dataModelImportLiteral};
447
+ import type { GenericCtx, MutationCtx } from ${serverImportLiteral};
448
+ ${hasRelationsExport && hasAuthDefaultExport ? `import { withOrm } from ${serverImportLiteral};` : ""}
449
+ import schema from ${schemaImportLiteral};
450
+ ${authDefinitionImport}
451
+
452
+ export function defineAuth<
453
+ AuthOptions extends BetterAuthOptionsWithoutDatabase = BetterAuthOptionsWithoutDatabase,
454
+ >(
455
+ definition: GenericAuthDefinition<GenericCtx, DataModel, typeof schema, AuthOptions>
456
+ ) {
457
+ return baseDefineAuth(definition);
458
+ }
459
+
460
+ ${hasAuthDefaultExport ? `type AuthDefinitionFromFile = Extract<
461
+ typeof authDefinitionModule extends { default: infer T } ? T : never,
462
+ (...args: unknown[]) => unknown
463
+ >;
464
+
465
+ const authDefinition = ((ctx: GenericCtx) =>
466
+ resolveGeneratedAuthDefinition<AuthDefinitionFromFile>(
467
+ authDefinitionModule,
468
+ getGeneratedAuthDisabledReason(${JSON.stringify(disabledAuthReasonKind)})
469
+ )(ctx)) as AuthDefinitionFromFile;
470
+ ` : ""}
471
+ const authRuntime = ${hasAuthDefaultExport ? `createAuthRuntime<
472
+ DataModel,
473
+ typeof schema,
474
+ MutationCtx,
475
+ GenericCtx,
476
+ ReturnType<AuthDefinitionFromFile>
477
+ >({
478
+ internal,
479
+ moduleName: ${JSON.stringify(moduleNamespace)},
480
+ schema,
481
+ auth: authDefinition,${hasRelationsExport ? "\n context: withOrm," : ""}
482
+ })` : `createDisabledAuthRuntime<DataModel, typeof schema, MutationCtx, GenericCtx>({
483
+ reason: getGeneratedAuthDisabledReason(${JSON.stringify(disabledAuthReasonKind)}),
484
+ })`};
485
+
486
+ export const {
487
+ authEnabled,
488
+ authClient,
489
+ getAuth,
490
+ auth,
491
+ create,
492
+ deleteMany,
493
+ deleteOne,
494
+ findMany,
495
+ findOne,
496
+ updateMany,
497
+ updateOne,
498
+ getLatestJwks,
499
+ rotateKeys,
500
+ } = authRuntime;
501
+ `;
502
+ }
503
+ function emitGeneratedModuleRuntimeFile(outputFile, functionsDir, moduleName, procedureEntries) {
504
+ const { callerExportName, handlerExportName } = getModuleRuntimeExportNames(moduleName);
505
+ const runtimeApiImportPath = getRuntimeApiImportPath(outputFile, functionsDir);
506
+ const generatedServerImportPath = getGeneratedServerImportPath(outputFile, functionsDir);
507
+ const { callerEntries, handlerEntries } = partitionRuntimeEntriesForEmission(procedureEntries);
508
+ const callerRegistryLines = emitProcedureRegistryEntries(callerEntries, outputFile, functionsDir, moduleName);
509
+ const callerRegistryBody = callerRegistryLines.length > 0 ? `\n${callerRegistryLines.join("\n")}\n` : "\n";
510
+ const hasHandlerRegistry = handlerEntries.length > 0;
511
+ const handlerRegistryLines = hasHandlerRegistry ? emitProcedureRegistryEntries(handlerEntries, outputFile, functionsDir, moduleName) : [];
512
+ const handlerRegistryBody = handlerRegistryLines.length > 0 ? `\n${handlerRegistryLines.join("\n")}\n` : "\n";
513
+ const allEntriesAreCrpc = callerEntries.length > 0 && callerEntries.length === handlerEntries.length;
514
+ const callerRegistryIdentifier = allEntriesAreCrpc ? "procedureRegistry" : "callerRegistry";
515
+ const handlerRegistryIdentifier = allEntriesAreCrpc ? "procedureRegistry" : "handlerRegistry";
516
+ const callerRegistryDeclaration = allEntriesAreCrpc ? `const procedureRegistry = {${callerRegistryBody}} as const;` : `const callerRegistry = {${callerRegistryBody}} as const;`;
517
+ return `// biome-ignore-all format: generated
518
+ // This file is auto-generated by better-convex
519
+ // Do not edit manually. Run \`better-convex codegen\` to regenerate.
520
+
521
+ import {
522
+ createGenericCallerFactory,${hasHandlerRegistry ? "\n createGenericHandlerFactory," : ""}
523
+ typedProcedureResolver,
524
+ type ProcedureActionCallerFromRegistry,
525
+ type ProcedureCallerFromRegistry,
526
+ type ProcedureScheduleCallerFromRegistry,
527
+ } from 'better-convex/server';
528
+ import { api, internal } from '${runtimeApiImportPath}';
529
+ import type { ActionCtx, MutationCtx, QueryCtx } from '${generatedServerImportPath}';
530
+
531
+ ${callerRegistryDeclaration}${hasHandlerRegistry && !allEntriesAreCrpc ? `\nconst handlerRegistry = {${handlerRegistryBody}} as const;\n` : ""}
532
+
533
+ type ProcedureCallerContext = QueryCtx | MutationCtx | ActionCtx;
534
+ type GeneratedProcedureCaller<
535
+ TCtx extends ProcedureCallerContext = ProcedureCallerContext,
536
+ > = TCtx extends MutationCtx
537
+ ? ProcedureCallerFromRegistry<typeof ${callerRegistryIdentifier}, 'mutation'> & {
538
+ schedule: ProcedureScheduleCallerFromRegistry<typeof ${callerRegistryIdentifier}>;
539
+ }
540
+ : TCtx extends ActionCtx
541
+ ? ProcedureCallerFromRegistry<typeof ${callerRegistryIdentifier}, 'mutation'> & {
542
+ actions: ProcedureActionCallerFromRegistry<typeof ${callerRegistryIdentifier}>;
543
+ schedule: ProcedureScheduleCallerFromRegistry<typeof ${callerRegistryIdentifier}>;
544
+ }
545
+ : ProcedureCallerFromRegistry<typeof ${callerRegistryIdentifier}, 'query'>;
546
+ ${hasHandlerRegistry ? `
547
+ type ProcedureHandlerContext = QueryCtx | MutationCtx;
548
+ type GeneratedProcedureHandler<
549
+ TCtx extends ProcedureHandlerContext = ProcedureHandlerContext,
550
+ > = TCtx extends MutationCtx
551
+ ? ProcedureCallerFromRegistry<typeof ${handlerRegistryIdentifier}, 'mutation'>
552
+ : ProcedureCallerFromRegistry<typeof ${handlerRegistryIdentifier}, 'query'>;
553
+ ` : ""}
554
+
555
+ const createCallerFromRegistry = createGenericCallerFactory<
556
+ QueryCtx,
557
+ MutationCtx,
558
+ typeof ${callerRegistryIdentifier},
559
+ ActionCtx
560
+ >(${callerRegistryIdentifier});${hasHandlerRegistry ? `
561
+ const createHandlerFromRegistry = createGenericHandlerFactory<
562
+ QueryCtx,
563
+ MutationCtx,
564
+ typeof ${handlerRegistryIdentifier}
565
+ >(${handlerRegistryIdentifier});
566
+ ` : ""}
567
+
568
+ export function ${callerExportName}<TCtx extends ProcedureCallerContext>(
569
+ ctx: TCtx
570
+ ): GeneratedProcedureCaller<TCtx> {
571
+ return createCallerFromRegistry(ctx) as GeneratedProcedureCaller<TCtx>;
572
+ }
573
+ ${hasHandlerRegistry ? `
574
+ export function ${handlerExportName}<TCtx extends ProcedureHandlerContext>(
575
+ ctx: TCtx
576
+ ): GeneratedProcedureHandler<TCtx> {
577
+ return createHandlerFromRegistry(ctx) as GeneratedProcedureHandler<TCtx>;
578
+ }
579
+ ` : ""}
580
+ `;
581
+ }
582
+ function hasNamedExport(filePath, exportName) {
583
+ if (!fs.existsSync(filePath)) return false;
584
+ const source = fs.readFileSync(filePath, "utf-8");
585
+ if (new RegExp(`\\bexport\\s+(?:const|let|var|function|class|type|interface)\\s+${exportName}\\b`).test(source)) return true;
586
+ for (const match of source.matchAll(/\bexport\s*{([^}]*)}/g)) {
587
+ const exportList = match[1] ?? "";
588
+ if (new RegExp(`\\b${exportName}\\b`).test(exportList)) return true;
589
+ }
590
+ return false;
591
+ }
592
+ function hasDefaultExport(filePath) {
593
+ if (!fs.existsSync(filePath)) return false;
594
+ const source = fs.readFileSync(filePath, "utf-8");
595
+ return DEFAULT_EXPORT_RE.test(source);
596
+ }
597
+ function createApiTree(meta) {
598
+ const root = {
599
+ children: {},
600
+ functions: []
601
+ };
602
+ for (const [moduleName, fns] of Object.entries(meta)) {
603
+ const pathSegments = moduleName.split("/").filter(Boolean);
604
+ let node = root;
605
+ for (const segment of pathSegments) {
606
+ node.children[segment] ??= {
607
+ children: {},
608
+ functions: []
609
+ };
610
+ node = node.children[segment];
611
+ }
612
+ for (const fnName of Object.keys(fns).sort()) {
613
+ const fnMeta = fns[fnName] ?? {};
614
+ const type = fnMeta.type;
615
+ const fnType = type === "query" || type === "mutation" || type === "action" ? type : "query";
616
+ node.functions.push({
617
+ fnName,
618
+ fnType,
619
+ moduleName,
620
+ fnMeta: {
621
+ ...fnMeta,
622
+ type: fnType
623
+ }
624
+ });
625
+ }
626
+ }
627
+ return root;
628
+ }
629
+ function formatMetaValue(value) {
630
+ if (typeof value === "string") return JSON.stringify(value);
631
+ if (typeof value === "boolean" || typeof value === "number") return String(value);
632
+ return null;
633
+ }
634
+ function emitFnMetaLiteral(fnMeta) {
635
+ const metaProps = [];
636
+ for (const [key, value] of Object.entries(fnMeta).sort(([a], [b]) => a.localeCompare(b))) {
637
+ if (value === void 0) continue;
638
+ const formatted = formatMetaValue(value);
639
+ if (formatted !== null) metaProps.push(`${key}: ${formatted}`);
640
+ }
641
+ return `{ ${metaProps.join(", ")} }`;
642
+ }
643
+ function emitHttpRoutes(dedupedRoutes, indentLevel) {
644
+ const indent = " ".repeat(indentLevel);
645
+ const lines = [];
646
+ for (const [routeKey, route] of Object.entries(dedupedRoutes).sort(([a], [b]) => a.localeCompare(b))) lines.push(`${indent}${formatKey(routeKey)}: { path: ${JSON.stringify(route.path)}, method: ${JSON.stringify(route.method)} },`);
647
+ return lines;
648
+ }
649
+ function emitApiObject(tree, pathSegments, outputFile, functionsDir, indentLevel, dedupedRoutes, hasHttpRouterExport) {
650
+ const indent = " ".repeat(indentLevel);
651
+ const lines = [];
652
+ const childKeys = Object.keys(tree.children).sort((a, b) => a.localeCompare(b));
653
+ const functionEntries = [...tree.functions].sort((a, b) => a.fnName.localeCompare(b.fnName));
654
+ const childSet = new Set(childKeys);
655
+ for (const entry of functionEntries) if (childSet.has(entry.fnName)) throw new Error(`Codegen conflict at ${pathSegments.join("/")}: export "${entry.fnName}" conflicts with directory of same name.`);
656
+ const mergedKeys = [...childKeys, ...functionEntries.map((entry) => entry.fnName)].sort((a, b) => a.localeCompare(b));
657
+ for (const key of mergedKeys) {
658
+ if (childSet.has(key)) {
659
+ const childNode = tree.children[key];
660
+ lines.push(`${indent}${formatKey(key)}: {`);
661
+ lines.push(...emitApiObject(childNode, [...pathSegments, key], outputFile, functionsDir, indentLevel + 1, dedupedRoutes, hasHttpRouterExport));
662
+ lines.push(`${indent}},`);
663
+ continue;
664
+ }
665
+ const fnEntry = functionEntries.find((entry) => entry.fnName === key);
666
+ if (!fnEntry) continue;
667
+ const runtimeAccess = getAccessPath("convexApi", [...pathSegments, key]);
668
+ const moduleImportPath = getModuleImportPath(outputFile, functionsDir, fnEntry.moduleName);
669
+ const fnMetaLiteral = emitFnMetaLiteral(fnEntry.fnMeta);
670
+ lines.push(`${indent}${formatKey(key)}: createApiLeaf<${JSON.stringify(fnEntry.fnType)}, typeof import(${JSON.stringify(moduleImportPath)}).${key}>(${runtimeAccess}, ${fnMetaLiteral}),`);
671
+ }
672
+ if (pathSegments.length === 0) {
673
+ if (hasHttpRouterExport) lines.push(`${indent}http: undefined as unknown as typeof httpRouter,`);
674
+ lines.push(`${indent}_http: {`);
675
+ lines.push(...emitHttpRoutes(dedupedRoutes, indentLevel + 1));
676
+ lines.push(`${indent}},`);
677
+ }
678
+ return lines;
679
+ }
680
+ function emitProcedureRegistryEntries(entries, outputFile, functionsDir, moduleName) {
681
+ return entries.map((entry) => {
682
+ const pathKey = entry.moduleName === moduleName ? entry.exportName : [...entry.moduleName.split("/"), entry.exportName].join(".");
683
+ const moduleImportPath = getModuleImportPath(outputFile, functionsDir, entry.moduleName);
684
+ const functionRefAccess = getAccessPath(entry.internal ? "internal" : "api", [...entry.moduleName.split("/"), entry.exportName]);
685
+ const resolver = `(require(${JSON.stringify(moduleImportPath)}) as Record<string, unknown>)[${JSON.stringify(entry.exportName)}]`;
686
+ return ` ${JSON.stringify(pathKey)}: [${JSON.stringify(entry.type)}, typedProcedureResolver(${functionRefAccess}, () => ${resolver})],`;
687
+ }).sort((a, b) => a.localeCompare(b));
688
+ }
689
+ function buildAuthRuntimeProcedureEntries(moduleName) {
690
+ return AUTH_RUNTIME_PROCEDURES.map((entry) => ({
691
+ ...entry,
692
+ moduleName,
693
+ kind: "crpc"
694
+ }));
695
+ }
696
+ function buildGeneratedServerRuntimeProcedureEntries(moduleName) {
697
+ return GENERATED_SERVER_RUNTIME_PROCEDURES.map((entry) => ({
698
+ ...entry,
699
+ moduleName,
700
+ kind: "dispatch"
701
+ }));
702
+ }
703
+ function partitionRuntimeEntriesForEmission(entries) {
704
+ return {
705
+ callerEntries: entries,
706
+ handlerEntries: entries.filter((entry) => entry.kind === "crpc")
707
+ };
708
+ }
709
+ function dedupeProcedureEntries(entries) {
710
+ const seen = /* @__PURE__ */ new Set();
711
+ const deduped = [];
712
+ for (const entry of entries) {
713
+ const key = `${entry.moduleName}.${entry.exportName}`;
714
+ if (seen.has(key)) continue;
715
+ seen.add(key);
716
+ deduped.push(entry);
717
+ }
718
+ return deduped;
719
+ }
720
+ function getConvexConfig(outputDir) {
721
+ const convexConfigPath = path.join(process.cwd(), "convex.json");
722
+ const functionsDir = (fs.existsSync(convexConfigPath) ? JSON.parse(fs.readFileSync(convexConfigPath, "utf-8")) : {}).functions || "convex";
723
+ return {
724
+ functionsDir: path.join(process.cwd(), functionsDir),
725
+ outputFile: path.join(process.cwd(), outputDir || "convex/shared", "api.ts")
726
+ };
727
+ }
728
+ /**
729
+ * Check if a value is a CRPCHttpRouter (has _def.router === true)
730
+ */
731
+ function isCRPCHttpRouter(value) {
732
+ return typeof value === "object" && value !== null && "_def" in value && value._def?.router === true;
733
+ }
734
+ /**
735
+ * Import a module using jiti and extract cRPC metadata from exports.
736
+ */
737
+ async function parseModuleRuntime(filePath, jitiInstance) {
738
+ const result = {};
739
+ const httpRoutes = {};
740
+ const procedures = [];
741
+ const isHttp = filePath.endsWith("http.ts");
742
+ const module = await jitiInstance.import(filePath);
743
+ if (!module || typeof module !== "object") {
744
+ if (isHttp) console.error(" http.ts: module is empty or not an object");
745
+ return {
746
+ meta: null,
747
+ httpRoutes: {},
748
+ procedures: []
749
+ };
750
+ }
751
+ for (const [name, value] of Object.entries(module)) {
752
+ if (name.startsWith("_")) continue;
753
+ const meta = value?._crpcMeta;
754
+ if (meta?.type) {
755
+ procedures.push({
756
+ exportName: name,
757
+ internal: Boolean(meta.internal),
758
+ type: meta.type
759
+ });
760
+ if (meta.internal) continue;
761
+ const fnMeta = { type: meta.type };
762
+ if (meta.auth) fnMeta.auth = meta.auth;
763
+ for (const [key, val] of Object.entries(meta)) if (key !== "type" && key !== "internal" && val !== void 0) fnMeta[key] = val;
764
+ result[name] = fnMeta;
765
+ }
766
+ const httpRoute = value?._crpcHttpRoute;
767
+ if (httpRoute?.path && httpRoute?.method) httpRoutes[name] = {
768
+ path: httpRoute.path,
769
+ method: httpRoute.method
770
+ };
771
+ if (isCRPCHttpRouter(value)) for (const [procPath, procedure] of Object.entries(value._def.procedures)) {
772
+ const route = procedure._crpcHttpRoute;
773
+ if (route?.path && route?.method) httpRoutes[procPath] = {
774
+ path: route.path,
775
+ method: route.method
776
+ };
777
+ }
778
+ }
779
+ return {
780
+ meta: Object.keys(result).length > 0 ? result : null,
781
+ httpRoutes,
782
+ procedures
783
+ };
784
+ }
785
+ async function generateMeta(outputDir, options) {
786
+ const startTime = Date.now();
787
+ const { functionsDir, outputFile } = getConvexConfig(outputDir);
788
+ const serverOutputFile = getGeneratedServerOutputFile(functionsDir);
789
+ const authOutputFile = getGeneratedAuthOutputFile(functionsDir);
790
+ const generatedAuthModuleName = getModuleNameFromOutputFile(authOutputFile, functionsDir);
791
+ const debug = options?.debug ?? false;
792
+ const silent = options?.silent ?? false;
793
+ const { generateApi, generateAuth, modeLabel } = resolveGenerationMode(options);
794
+ if (debug) if (generateApi) console.info("🔍 Scanning Convex functions for cRPC metadata...\n");
795
+ else console.info(`🔍 Running better-convex codegen (mode=${modeLabel})...\n`);
796
+ const meta = {};
797
+ const allHttpRoutes = {};
798
+ const procedureEntries = [];
799
+ let createdRuntimePlaceholders = [];
800
+ const runtimeFilesPreservedFromParseFailures = /* @__PURE__ */ new Set();
801
+ let totalFunctions = 0;
802
+ const authFilePath = path.join(functionsDir, "auth.ts");
803
+ const hasAuthFile = fs.existsSync(authFilePath);
804
+ const hasAuthDefaultExport = hasDefaultExport(authFilePath);
805
+ const authContract = {
806
+ hasAuthFile,
807
+ hasAuthDefaultExport
808
+ };
809
+ const hasRelationsExport = hasNamedExport(path.join(functionsDir, "schema.ts"), "relations");
810
+ const hasTriggersExport = hasNamedExport(path.join(functionsDir, "schema.ts"), "triggers");
811
+ if (hasTriggersExport && !hasRelationsExport) throw new Error("Codegen error: schema.ts exports 'triggers' but is missing 'relations'. Export `relations` and define triggers via `defineTriggers(relations, { ... })`.");
812
+ ensureGeneratedSupportPlaceholders(functionsDir, { includeAuth: generateAuth });
813
+ if (generateApi) {
814
+ const jitiInstance = createJiti(process.cwd(), {
815
+ interopDefault: true,
816
+ moduleCache: false
817
+ });
818
+ const files = listFilesRecursive(functionsDir).filter((file) => file.endsWith(".ts") && isValidConvexFile(file));
819
+ createdRuntimePlaceholders = ensureGeneratedRuntimePlaceholders(functionsDir, [...new Set([
820
+ ...files.map((file) => file.replace(TS_EXTENSION_RE, "")),
821
+ ...hasRelationsExport ? ["generated/server"] : [],
822
+ ...generateAuth ? [generatedAuthModuleName] : []
823
+ ])]);
824
+ for (const file of files) {
825
+ const filePath = path.join(functionsDir, file);
826
+ const moduleName = file.replace(TS_EXTENSION_RE, "");
827
+ try {
828
+ const { meta: moduleMeta, httpRoutes, procedures } = await parseModuleRuntime(filePath, jitiInstance);
829
+ if (moduleMeta) {
830
+ meta[moduleName] = moduleMeta;
831
+ const fnCount = Object.keys(moduleMeta).length;
832
+ totalFunctions += fnCount;
833
+ if (debug) console.info(` ✓ ${moduleName}: ${fnCount} functions`);
834
+ }
835
+ if (Object.keys(httpRoutes).length > 0 && debug) console.info(` ✓ ${moduleName}: ${Object.keys(httpRoutes).length} HTTP routes`);
836
+ Object.assign(allHttpRoutes, httpRoutes);
837
+ for (const procedure of procedures) procedureEntries.push({
838
+ moduleName,
839
+ exportName: procedure.exportName,
840
+ internal: procedure.internal,
841
+ type: procedure.type,
842
+ kind: "crpc"
843
+ });
844
+ } catch (error) {
845
+ runtimeFilesPreservedFromParseFailures.add(getGeneratedRuntimeOutputFile(functionsDir, moduleName));
846
+ if (debug || file === "http.ts") console.error(` ⚠ Failed to parse ${file}:`, error);
847
+ }
848
+ }
849
+ }
850
+ if (generateApi) {
851
+ const routesByPath = /* @__PURE__ */ new Map();
852
+ for (const [key, route] of Object.entries(allHttpRoutes)) {
853
+ const pathKey = `${route.path}:${route.method}`;
854
+ const existing = routesByPath.get(pathKey) || [];
855
+ existing.push({
856
+ key,
857
+ route
858
+ });
859
+ routesByPath.set(pathKey, existing);
860
+ }
861
+ const dedupedRoutes = {};
862
+ for (const entries of routesByPath.values()) {
863
+ const best = entries.reduce((a, b) => a.key.length >= b.key.length ? a : b);
864
+ dedupedRoutes[best.key] = best.route;
865
+ }
866
+ const runtimeApiImportPath = getRuntimeApiImportPath(outputFile, functionsDir);
867
+ const schemaImportPath = getSchemaImportPath(outputFile, functionsDir);
868
+ const httpImportPath = getHttpImportPath(outputFile, functionsDir);
869
+ const hasTablesExport = hasNamedExport(path.join(functionsDir, "schema.ts"), "tables");
870
+ const hasHttpRouterExport = hasNamedExport(path.join(functionsDir, "http.ts"), "httpRouter");
871
+ const apiTree = createApiTree(meta);
872
+ if (Object.hasOwn(apiTree.children, "http") || apiTree.functions.some((entry) => entry.fnName === "http")) throw new Error("Codegen conflict: root \"http\" namespace is reserved for generated HTTP router types. Rename your Convex module/function.");
873
+ const apiObjectLines = emitApiObject(apiTree, [], outputFile, functionsDir, 1, dedupedRoutes, hasHttpRouterExport);
874
+ const apiObjectBody = apiObjectLines.length > 0 ? `\n${apiObjectLines.join("\n")}\n` : "\n";
875
+ const serverTypeImports = "import type { inferApiInputs, inferApiOutputs } from \"better-convex/server\";";
876
+ const ormTypeImports = [hasTablesExport ? "InferInsertModel" : null, hasTablesExport ? "InferSelectModel" : null].filter((item) => !!item);
877
+ const optionalImports = [
878
+ ormTypeImports.length > 0 ? `import type { ${ormTypeImports.join(", ")} } from "better-convex/orm";` : null,
879
+ hasHttpRouterExport ? `import type { httpRouter } from ${JSON.stringify(httpImportPath)};` : null,
880
+ hasTablesExport ? `import type { tables } from ${JSON.stringify(schemaImportPath)};` : null
881
+ ].filter((line) => !!line).join("\n");
882
+ const apiTypeLine = "export type Api = typeof api;";
883
+ const optionalTypeExports = [hasTablesExport ? `
884
+ export type TableName = keyof typeof tables;
885
+ export type Select<T extends TableName> = InferSelectModel<(typeof tables)[T]>;
886
+ export type Insert<T extends TableName> = InferInsertModel<(typeof tables)[T]>;` : null].filter((entry) => !!entry).join("\n");
887
+ const output = `// biome-ignore-all format: generated
888
+ // This file is auto-generated by better-convex
889
+ // Do not edit manually. Run \`better-convex codegen\` to regenerate.
890
+
891
+ import { createApiLeaf } from "better-convex/server";
892
+ ${serverTypeImports}
893
+ import { api as convexApi } from ${JSON.stringify(runtimeApiImportPath)};
894
+ ${optionalImports ? `\n${optionalImports}` : ""}
895
+
896
+ export const api = {${apiObjectBody}} as const;
897
+
898
+ ${apiTypeLine}
899
+ export type ApiInputs = inferApiInputs<Api>;
900
+ export type ApiOutputs = inferApiOutputs<Api>;
901
+ ${optionalTypeExports}
902
+ `;
903
+ const outputDirname = path.dirname(outputFile);
904
+ if (!fs.existsSync(outputDirname)) fs.mkdirSync(outputDirname, { recursive: true });
905
+ fs.writeFileSync(outputFile, output);
906
+ } else fs.rmSync(outputFile, { force: true });
907
+ const serverOutput = emitGeneratedServerFile(serverOutputFile, functionsDir, hasRelationsExport, hasTriggersExport);
908
+ const generatedOutputDirname = path.dirname(serverOutputFile);
909
+ if (!fs.existsSync(generatedOutputDirname)) fs.mkdirSync(generatedOutputDirname, { recursive: true });
910
+ fs.writeFileSync(serverOutputFile, serverOutput);
911
+ if (generateAuth) {
912
+ const authOutput = emitGeneratedAuthFile(authOutputFile, functionsDir, hasRelationsExport, authContract);
913
+ fs.writeFileSync(authOutputFile, authOutput);
914
+ } else fs.rmSync(authOutputFile, { force: true });
915
+ fs.rmSync(getLegacyGeneratedOutputFile(functionsDir), { force: true });
916
+ const mergedProcedureEntries = dedupeProcedureEntries([
917
+ ...hasRelationsExport ? buildGeneratedServerRuntimeProcedureEntries("generated/server") : [],
918
+ ...generateApi ? procedureEntries : [],
919
+ ...generateAuth && hasAuthDefaultExport ? buildAuthRuntimeProcedureEntries(generatedAuthModuleName) : []
920
+ ]);
921
+ const runtimeProcedureEntriesByModule = /* @__PURE__ */ new Map();
922
+ for (const entry of mergedProcedureEntries) {
923
+ if (RUNTIME_CALLER_RESERVED_EXPORTS.has(entry.exportName)) throw new Error(`Codegen conflict: "${entry.moduleName}.${entry.exportName}" uses reserved runtime caller namespace "${entry.exportName}". Rename the procedure export.`);
924
+ const existingEntries = runtimeProcedureEntriesByModule.get(entry.moduleName);
925
+ if (existingEntries) {
926
+ existingEntries.push(entry);
927
+ continue;
928
+ }
929
+ runtimeProcedureEntriesByModule.set(entry.moduleName, [entry]);
930
+ }
931
+ const runtimeOutputFiles = [];
932
+ for (const [moduleName, moduleEntries] of [...runtimeProcedureEntriesByModule].sort(([moduleA], [moduleB]) => moduleA.localeCompare(moduleB))) {
933
+ const runtimeOutputFile = getGeneratedRuntimeOutputFile(functionsDir, moduleName);
934
+ const runtimeOutput = emitGeneratedModuleRuntimeFile(runtimeOutputFile, functionsDir, moduleName, moduleEntries);
935
+ fs.mkdirSync(path.dirname(runtimeOutputFile), { recursive: true });
936
+ fs.writeFileSync(runtimeOutputFile, runtimeOutput);
937
+ runtimeOutputFiles.push(runtimeOutputFile);
938
+ }
939
+ const runtimeOutputFileSet = new Set(runtimeOutputFiles);
940
+ const existingRuntimeFiles = listGeneratedRuntimeFiles(functionsDir);
941
+ for (const existingRuntimeFile of existingRuntimeFiles) {
942
+ if (runtimeOutputFileSet.has(existingRuntimeFile) || runtimeFilesPreservedFromParseFailures.has(existingRuntimeFile)) continue;
943
+ fs.rmSync(existingRuntimeFile, { force: true });
944
+ }
945
+ for (const createdRuntimePlaceholder of createdRuntimePlaceholders) {
946
+ if (runtimeOutputFileSet.has(createdRuntimePlaceholder) || runtimeFilesPreservedFromParseFailures.has(createdRuntimePlaceholder)) continue;
947
+ fs.rmSync(createdRuntimePlaceholder, { force: true });
948
+ }
949
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(2);
950
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
951
+ hour12: false,
952
+ hour: "2-digit",
953
+ minute: "2-digit",
954
+ second: "2-digit"
955
+ });
956
+ if (!silent) if (debug) {
957
+ if (generateApi) console.info(`\n✅ Generated ${outputFile}`);
958
+ else console.info(`\n🧹 Removed ${outputFile}`);
959
+ console.info(`✅ Generated ${serverOutputFile}`);
960
+ if (generateAuth) console.info(`✅ Generated ${authOutputFile}`);
961
+ else console.info(`🧹 Removed ${authOutputFile}`);
962
+ for (const runtimeOutputFile of runtimeOutputFiles) console.info(`✅ Generated ${runtimeOutputFile}`);
963
+ if (generateApi) console.info(` ${Object.keys(meta).length} modules, ${totalFunctions} functions`);
964
+ else console.info(" cRPC scan skipped for scoped generation");
965
+ } else console.info(`✔ ${time} Convex api ready! (${elapsed}s)`);
966
+ }
967
+
968
+ //#endregion
969
+ export { getConvexConfig as n, generateMeta as t };