@gaganref/confect-cli 0.0.0-stage → 10.3.1-fork.1

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 (99) hide show
  1. package/CHANGELOG.md +1824 -0
  2. package/LICENSE +7 -0
  3. package/README.md +16 -2
  4. package/bin/confect.mjs +26 -0
  5. package/dist/Ansi.mjs +18 -0
  6. package/dist/Ansi.mjs.map +1 -0
  7. package/dist/BuildError.mjs +30 -0
  8. package/dist/BuildError.mjs.map +1 -0
  9. package/dist/Bundler.mjs +426 -0
  10. package/dist/Bundler.mjs.map +1 -0
  11. package/dist/CodeBlockWriter.mjs +55 -0
  12. package/dist/CodeBlockWriter.mjs.map +1 -0
  13. package/dist/CodegenError.mjs +194 -0
  14. package/dist/CodegenError.mjs.map +1 -0
  15. package/dist/ConfectDirectory.mjs +35 -0
  16. package/dist/ConfectDirectory.mjs.map +1 -0
  17. package/dist/ConvexConfig.mjs +198 -0
  18. package/dist/ConvexConfig.mjs.map +1 -0
  19. package/dist/ConvexDirectory.mjs +54 -0
  20. package/dist/ConvexDirectory.mjs.map +1 -0
  21. package/dist/Declaration.mjs +258 -0
  22. package/dist/Declaration.mjs.map +1 -0
  23. package/dist/DirectoryOptions.mjs +42 -0
  24. package/dist/DirectoryOptions.mjs.map +1 -0
  25. package/dist/DocName.mjs +25 -0
  26. package/dist/DocName.mjs.map +1 -0
  27. package/dist/EntryPoint.mjs +111 -0
  28. package/dist/EntryPoint.mjs.map +1 -0
  29. package/dist/Evaluation.mjs +117 -0
  30. package/dist/Evaluation.mjs.map +1 -0
  31. package/dist/EvaluationWorker.mjs +46 -0
  32. package/dist/EvaluationWorker.mjs.map +1 -0
  33. package/dist/FunctionModule.mjs +137 -0
  34. package/dist/FunctionModule.mjs.map +1 -0
  35. package/dist/FunctionPath.mjs +18 -0
  36. package/dist/FunctionPath.mjs.map +1 -0
  37. package/dist/FunctionPaths.mjs +45 -0
  38. package/dist/FunctionPaths.mjs.map +1 -0
  39. package/dist/GenerationSnapshot.mjs +183 -0
  40. package/dist/GenerationSnapshot.mjs.map +1 -0
  41. package/dist/GroupPath.mjs +51 -0
  42. package/dist/GroupPath.mjs.map +1 -0
  43. package/dist/GroupPaths.mjs +8 -0
  44. package/dist/GroupPaths.mjs.map +1 -0
  45. package/dist/ImplModulePath.mjs +8 -0
  46. package/dist/ImplModulePath.mjs.map +1 -0
  47. package/dist/LeafModule.mjs +285 -0
  48. package/dist/LeafModule.mjs.map +1 -0
  49. package/dist/ProjectRoot.mjs +38 -0
  50. package/dist/ProjectRoot.mjs.map +1 -0
  51. package/dist/RelativeModulePath.mjs +13 -0
  52. package/dist/RelativeModulePath.mjs.map +1 -0
  53. package/dist/Remove.mjs +24 -0
  54. package/dist/Remove.mjs.map +1 -0
  55. package/dist/RestoreWatcher.mjs +31 -0
  56. package/dist/RestoreWatcher.mjs.map +1 -0
  57. package/dist/Rollback.mjs +50 -0
  58. package/dist/Rollback.mjs.map +1 -0
  59. package/dist/SnapshotError.mjs +21 -0
  60. package/dist/SnapshotError.mjs.map +1 -0
  61. package/dist/SpecAssemblyNode.mjs +91 -0
  62. package/dist/SpecAssemblyNode.mjs.map +1 -0
  63. package/dist/SpecModulePath.mjs +10 -0
  64. package/dist/SpecModulePath.mjs.map +1 -0
  65. package/dist/SyncState.mjs +122 -0
  66. package/dist/SyncState.mjs.map +1 -0
  67. package/dist/TableModule.mjs +97 -0
  68. package/dist/TableModule.mjs.map +1 -0
  69. package/dist/TableModulePath.mjs +8 -0
  70. package/dist/TableModulePath.mjs.map +1 -0
  71. package/dist/WatchBuild.mjs +190 -0
  72. package/dist/WatchBuild.mjs.map +1 -0
  73. package/dist/WatchGraph.mjs +35 -0
  74. package/dist/WatchGraph.mjs.map +1 -0
  75. package/dist/WatchTopology.mjs +55 -0
  76. package/dist/WatchTopology.mjs.map +1 -0
  77. package/dist/Watcher.mjs +378 -0
  78. package/dist/Watcher.mjs.map +1 -0
  79. package/dist/cliApp.mjs +9 -0
  80. package/dist/cliApp.mjs.map +1 -0
  81. package/dist/confect/codegen.mjs +548 -0
  82. package/dist/confect/codegen.mjs.map +1 -0
  83. package/dist/confect/declaration.mjs +218 -0
  84. package/dist/confect/declaration.mjs.map +1 -0
  85. package/dist/confect/dev.mjs +182 -0
  86. package/dist/confect/dev.mjs.map +1 -0
  87. package/dist/confect.mjs +9 -0
  88. package/dist/confect.mjs.map +1 -0
  89. package/dist/index.mjs +19 -0
  90. package/dist/index.mjs.map +1 -0
  91. package/dist/log.mjs +107 -0
  92. package/dist/log.mjs.map +1 -0
  93. package/dist/package.mjs +6 -0
  94. package/dist/package.mjs.map +1 -0
  95. package/dist/templates.mjs +448 -0
  96. package/dist/templates.mjs.map +1 -0
  97. package/dist/utils.mjs +105 -0
  98. package/dist/utils.mjs.map +1 -0
  99. package/package.json +51 -3
@@ -0,0 +1,548 @@
1
+ import { fromBundlerError } from "../BuildError.mjs";
2
+ import { ProjectRoot } from "../ProjectRoot.mjs";
3
+ import { logFileAdded, logFileModified, logFileRemoved, logPending, logSuccess, logWarn } from "../log.mjs";
4
+ import { bundle } from "../Bundler.mjs";
5
+ import { CallbackPrefixError, ComponentNodeGroupError, ConflictingDocNameError, InvalidConvexConfigError, LegacySchemaFileError, tapAndLog } from "../CodegenError.mjs";
6
+ import { ConvexDirectory } from "../ConvexDirectory.mjs";
7
+ import { ConfectDirectory } from "../ConfectDirectory.mjs";
8
+ import { assemblyNodesFromLeaves } from "../SpecAssemblyNode.mjs";
9
+ import { assembledSpec, componentContract, components, convexSchema, docs, id, refs, registeredFunctionsForGroup, runtimeSchema, schema, services, tableWrapper } from "../templates.mjs";
10
+ import { generateAuthConfig, generateCrons, generateHttp, removePathIfExists, toModuleImportPath, touchConvexSchema, writeFileStringAndLog } from "../utils.mjs";
11
+ import { CONVEX_CONFIG_FILENAME, componentName, discoverInstalledComponents, resolveDefinition, typeImportPath } from "../ConvexConfig.mjs";
12
+ import { flags, layer } from "../DirectoryOptions.mjs";
13
+ import { fromTableName } from "../DocName.mjs";
14
+ import { FunctionPaths, make } from "../FunctionPaths.mjs";
15
+ import { diff, fromLoadedLeaves, update } from "../GenerationSnapshot.mjs";
16
+ import { applyFunctionPlan, generateFunctions } from "../FunctionModule.mjs";
17
+ import { implPathForSpec, loadAll, registeredFunctionsRelativePath, validateAllImpls, withImplFiles } from "../LeafModule.mjs";
18
+ import { TABLES_DIRNAME, discover, validate } from "../TableModule.mjs";
19
+ import { planDeclarations } from "../EntryPoint.mjs";
20
+ import { preserveComponentOutput } from "../Rollback.mjs";
21
+ import { syncDeclaration, syncDeclaredHttp } from "./declaration.mjs";
22
+ import * as Effect from "effect/Effect";
23
+ import * as Command from "effect/cli/Command";
24
+ import { App, Callback, Env, Spec } from "@confect/core";
25
+ import * as FileSystem from "effect/FileSystem";
26
+ import * as Path from "effect/Path";
27
+ import * as Array from "effect/Array";
28
+ import * as Result from "effect/Result";
29
+ import { pipe } from "effect/Function";
30
+ import * as HashSet from "effect/HashSet";
31
+ import * as Match from "effect/Match";
32
+ import * as Option from "effect/Option";
33
+ import * as Record from "effect/Record";
34
+ //#region src/confect/codegen.ts
35
+ const GENERATED_DIRNAME = "_generated";
36
+ const isComponent = Effect.map(ConvexDirectory.target, (target) => target.kind === "component");
37
+ const GENERATED_SPEC_PATH = Effect.map(Path.Path, (path) => path.join(GENERATED_DIRNAME, "spec.ts"));
38
+ const GENERATED_SCHEMA_PATH = Effect.map(Path.Path, (path) => path.join(GENERATED_DIRNAME, "schema.ts"));
39
+ const GENERATED_CONVEX_SCHEMA_PATH = Effect.map(Path.Path, (path) => path.join(GENERATED_DIRNAME, "convexSchema.ts"));
40
+ const GENERATED_ID_PATH = Effect.map(Path.Path, (path) => path.join(GENERATED_DIRNAME, "id.ts"));
41
+ const GENERATED_COMPONENTS_PATH = Effect.map(Path.Path, (path) => path.join(GENERATED_DIRNAME, "components.ts"));
42
+ const GENERATED_TABLES_DIRNAME = Effect.map(Path.Path, (path) => path.join(GENERATED_DIRNAME, "tables"));
43
+ const LEGACY_PATHS = Effect.gen(function* () {
44
+ const path = yield* Path.Path;
45
+ return [
46
+ "spec.ts",
47
+ "nodeSpec.ts",
48
+ "impl.ts",
49
+ "nodeImpl.ts",
50
+ path.join(GENERATED_DIRNAME, "registeredFunctions.ts"),
51
+ path.join(GENERATED_DIRNAME, "nodeRegisteredFunctions.ts"),
52
+ path.join(GENERATED_DIRNAME, "impl.ts"),
53
+ path.join(GENERATED_DIRNAME, "nodeImpl.ts"),
54
+ path.join(GENERATED_DIRNAME, "nodeSpec.ts")
55
+ ];
56
+ });
57
+ const codegen = Command.make("codegen", flags, () => Effect.gen(function* () {
58
+ yield* logPending("Performing initial sync…");
59
+ yield* codegenHandler.pipe(Effect.asVoid, Effect.tap(() => logSuccess("Generated files are up-to-date")), tapAndLog);
60
+ })).pipe(Command.provide(layer), Command.withDescription("Generate `confect/_generated` files and the contents of the `convex` directory (except `tsconfig.json`, and `convex.config.ts` unless `confect/app.ts` declares the backend)"));
61
+ const generateWithSnapshot = Effect.gen(function* () {
62
+ yield* validateComponentConfiguration;
63
+ yield* generateConfectGeneratedDirectory;
64
+ const declared = yield* syncDeclaration;
65
+ yield* rejectLegacySchemaFile;
66
+ const tableModules = yield* discover;
67
+ yield* warnIfNoTables(tableModules);
68
+ yield* generateIdConstructor(tableModules);
69
+ yield* generateComponents;
70
+ yield* validateCallbackPrefixes(declared);
71
+ yield* validate(tableModules);
72
+ yield* validateNoDocNameCollisions(tableModules);
73
+ yield* generateTableWrappers(tableModules);
74
+ yield* removeObsoleteTableWrappers(tableModules);
75
+ yield* generateRuntimeSchema(tableModules);
76
+ const leaves = yield* loadAll;
77
+ yield* rejectComponentNodeGroups(leaves);
78
+ yield* removeLegacyFiles;
79
+ const snapshot = yield* Effect.fromResult(fromLoadedLeaves(leaves));
80
+ yield* generateAssembledSpecs(leaves);
81
+ yield* Effect.all([
82
+ removeGeneratedApi,
83
+ generateRefs,
84
+ generateComponentContract(tableModules),
85
+ removeGeneratedNodeApi,
86
+ generateDocs(tableModules),
87
+ generateServices,
88
+ generateConvexSchema(tableModules)
89
+ ], { concurrency: "unbounded" });
90
+ yield* validateAllImpls(leaves);
91
+ yield* generateGroupRegisteredFunctions(leaves);
92
+ yield* removeObsoleteRegisteredFunctions(leaves);
93
+ const [functionPaths] = yield* Effect.all([
94
+ generateFunctions(snapshot),
95
+ generateConvexSchemaReexport,
96
+ logGenerated(Option.match(declared, {
97
+ onNone: () => generateHttp,
98
+ onSome: syncDeclaredHttp
99
+ })),
100
+ logGenerated(generateCrons),
101
+ Option.isNone(declared) ? logGenerated(generateAuthConfig) : Effect.succeedNone
102
+ ], { concurrency: "unbounded" });
103
+ if (Option.isSome(declared)) yield* generateTesting(declared.value);
104
+ return {
105
+ functionPaths,
106
+ snapshot
107
+ };
108
+ }).pipe(Effect.withSpan("Cli.codegen"));
109
+ const makeCodegenHandler = (duringRestore) => preserveComponentOutput(generateWithSnapshot, duringRestore).pipe(Effect.tap(() => touchConvexSchema), Effect.map(({ functionPaths }) => ({ functionPaths })));
110
+ const codegenHandler = makeCodegenHandler();
111
+ const updateDeclarations = Effect.fn("Cli.updateDeclarations")(function* (snapshot, declarations, affectedImpls) {
112
+ const path = yield* Path.Path;
113
+ const { paths, tables } = planDeclarations(declarations, affectedImpls, path);
114
+ yield* validate(tables);
115
+ return yield* withImplFiles(paths, (leaves) => Effect.gen(function* () {
116
+ yield* rejectComponentNodeGroups(leaves);
117
+ const next = yield* Effect.fromResult(update(snapshot, leaves));
118
+ yield* validateAllImpls(leaves);
119
+ yield* generateGroupRegisteredFunctions(leaves);
120
+ yield* applyFunctionPlan(diff(snapshot, next));
121
+ return next;
122
+ }));
123
+ });
124
+ const validateComponentGroupRuntime = (group, groupPath, specPath) => Result.gen(function* () {
125
+ if (group.runtime === "Node") return yield* Result.fail(new ComponentNodeGroupError({
126
+ specPath,
127
+ groupPath
128
+ }));
129
+ for (const [name, child] of Object.entries(group.groups)) yield* validateComponentGroupRuntime(child, `${groupPath}.${name}`, specPath);
130
+ });
131
+ const rejectComponentNodeGroups = Effect.fnUntraced(function* (leaves) {
132
+ if ((yield* ConvexDirectory.target).kind === "app") return;
133
+ yield* Effect.forEach(leaves, (leaf) => Effect.fromResult(validateComponentGroupRuntime(leaf.spec, leaf.groupPathDot, leaf.relativePath)));
134
+ });
135
+ const validateCallbackPrefixes = Effect.fnUntraced(function* (declared) {
136
+ if ((yield* ConvexDirectory.target).kind !== "component") return;
137
+ const fs = yield* FileSystem.FileSystem;
138
+ const path = yield* Path.Path;
139
+ const callbacksPath = path.join(yield* ConfectDirectory.get, "callbacks.ts");
140
+ if (!(yield* fs.exists(callbacksPath))) return;
141
+ const name = Option.isSome(declared) ? Env.name(App.env(declared.value)) : yield* componentName(path.join(yield* ConvexDirectory.get, CONVEX_CONFIG_FILENAME));
142
+ const { module } = yield* bundle(callbacksPath).pipe(Effect.mapError((error) => fromBundlerError("confect/callbacks.ts", error)));
143
+ for (const contract of Object.values(module)) if (Callback.isContract(contract) && !contract.id.startsWith(`${name}/`)) return yield* new CallbackPrefixError({
144
+ declarationPath: "confect/callbacks.ts",
145
+ contractId: contract.id,
146
+ componentName: name
147
+ });
148
+ });
149
+ const validateComponentConfiguration = Effect.gen(function* () {
150
+ return yield* Match.value((yield* ConvexDirectory.target).kind).pipe(Match.when("app", () => Effect.void), Match.when("component", () => Effect.gen(function* () {
151
+ const fs = yield* FileSystem.FileSystem;
152
+ const path = yield* Path.Path;
153
+ const confectDirectory = yield* ConfectDirectory.get;
154
+ const authPath = path.join(confectDirectory, "auth.ts");
155
+ if (yield* fs.exists(authPath)) return yield* new InvalidConvexConfigError({
156
+ configPath: authPath,
157
+ reason: "Components cannot configure application authentication. Pass identity explicitly through function arguments."
158
+ });
159
+ })), Match.exhaustive);
160
+ });
161
+ const generateComponentContract = Effect.fnUntraced(function* (tables) {
162
+ return yield* Match.value((yield* ConvexDirectory.target).kind).pipe(Match.when("app", () => Effect.void), Match.when("component", () => Effect.gen(function* () {
163
+ const path = yield* Path.Path;
164
+ const directory = yield* ConfectDirectory.get;
165
+ const contents = yield* componentContract({ tableNames: Array.map(tables, (table) => table.tableName) });
166
+ yield* writeFileStringAndLog(path.join(directory, GENERATED_DIRNAME, "component.ts"), contents);
167
+ })), Match.exhaustive);
168
+ });
169
+ const generateConfectGeneratedDirectory = Effect.gen(function* () {
170
+ const fs = yield* FileSystem.FileSystem;
171
+ const path = yield* Path.Path;
172
+ const confectDirectory = yield* ConfectDirectory.get;
173
+ if (!(yield* fs.exists(path.join(confectDirectory, "_generated")))) {
174
+ yield* fs.makeDirectory(path.join(confectDirectory, "_generated"), { recursive: true });
175
+ yield* logFileAdded(path.join(confectDirectory, "_generated") + "/");
176
+ }
177
+ });
178
+ const removeLegacyFiles = Effect.gen(function* () {
179
+ const fs = yield* FileSystem.FileSystem;
180
+ const path = yield* Path.Path;
181
+ const confectDirectory = yield* ConfectDirectory.get;
182
+ const legacyPaths = yield* LEGACY_PATHS;
183
+ yield* Effect.forEach(legacyPaths, Effect.fnUntraced(function* (relativePath) {
184
+ const absolutePath = path.join(confectDirectory, relativePath);
185
+ if (yield* fs.exists(absolutePath)) {
186
+ yield* removePathIfExists(absolutePath);
187
+ yield* logFileRemoved(absolutePath);
188
+ }
189
+ }));
190
+ });
191
+ const generateAssembledSpecs = Effect.fnUntraced(function* (leaves) {
192
+ const path = yield* Path.Path;
193
+ const confectDirectory = yield* ConfectDirectory.get;
194
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
195
+ const nodes = assemblyNodesFromLeaves(leaves);
196
+ const specContents = yield* assembledSpec({ nodes });
197
+ yield* writeFileStringAndLog(path.join(confectDirectory, generatedSpecPath), specContents);
198
+ });
199
+ const generateGroupRegisteredFunctions = Effect.fnUntraced(function* (leaves) {
200
+ const path = yield* Path.Path;
201
+ const confectDirectory = yield* ConfectDirectory.get;
202
+ yield* Effect.forEach(leaves, Effect.fnUntraced(function* (leaf) {
203
+ const registryRelativePath = yield* registeredFunctionsRelativePath(leaf);
204
+ const registryPath = path.join(confectDirectory, "_generated", registryRelativePath);
205
+ const registryDir = path.dirname(registryPath);
206
+ const fs = yield* FileSystem.FileSystem;
207
+ if (!(yield* fs.exists(registryDir))) yield* fs.makeDirectory(registryDir, { recursive: true });
208
+ const implRelativePath = yield* implPathForSpec(leaf.relativePath);
209
+ const schemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, "_generated", "schema.ts")));
210
+ const specImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, leaf.relativePath)));
211
+ const implImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, implRelativePath)));
212
+ const contents = yield* registeredFunctionsForGroup({
213
+ spec: leaf.spec,
214
+ schemaImportPath,
215
+ specImportPath,
216
+ implImportPath
217
+ });
218
+ yield* writeFileStringAndLog(registryPath, contents);
219
+ }));
220
+ });
221
+ const removeObsoleteRegisteredFunctions = Effect.fnUntraced(function* (leaves) {
222
+ const fs = yield* FileSystem.FileSystem;
223
+ const path = yield* Path.Path;
224
+ const confectDirectory = yield* ConfectDirectory.get;
225
+ const registryRoot = path.join(confectDirectory, "_generated", "registeredFunctions");
226
+ if (!(yield* fs.exists(registryRoot))) return;
227
+ const expected = new Set(yield* Effect.forEach(leaves, (leaf) => registeredFunctionsRelativePath(leaf)));
228
+ const existing = yield* fs.readDirectory(registryRoot, { recursive: true });
229
+ yield* Effect.forEach(existing, (relativePath) => {
230
+ if (path.extname(relativePath) !== ".ts") return Effect.void;
231
+ const normalized = path.join("registeredFunctions", relativePath);
232
+ if (!expected.has(normalized)) return Effect.gen(function* () {
233
+ const absolutePath = path.join(registryRoot, relativePath);
234
+ if (yield* fs.exists(absolutePath)) {
235
+ yield* removePathIfExists(absolutePath);
236
+ yield* logFileRemoved(absolutePath);
237
+ }
238
+ });
239
+ return Effect.void;
240
+ });
241
+ });
242
+ const getGeneratedSpecPath = Effect.gen(function* () {
243
+ const path = yield* Path.Path;
244
+ const confectDirectory = yield* ConfectDirectory.get;
245
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
246
+ return path.join(confectDirectory, generatedSpecPath);
247
+ });
248
+ const loadGeneratedSpec = Effect.gen(function* () {
249
+ const specPath = yield* getGeneratedSpecPath;
250
+ const { module: specModule } = yield* bundle(specPath);
251
+ const spec = specModule.default;
252
+ if (!Spec.isSpec(spec)) return yield* Effect.die(/* @__PURE__ */ new Error("_generated/spec.ts does not export a valid Spec"));
253
+ return spec;
254
+ });
255
+ const emptyFunctionPaths = FunctionPaths.make(HashSet.empty());
256
+ const loadPreviousFunctionPaths = Effect.gen(function* () {
257
+ const fs = yield* FileSystem.FileSystem;
258
+ const specPath = yield* getGeneratedSpecPath;
259
+ if (!(yield* fs.exists(specPath))) return emptyFunctionPaths;
260
+ const specResult = yield* loadGeneratedSpec.pipe(Effect.result);
261
+ return Result.match(specResult, {
262
+ onFailure: () => emptyFunctionPaths,
263
+ onSuccess: (spec) => make(spec)
264
+ });
265
+ });
266
+ /**
267
+ * Remove a now-obsolete `_generated/<name>.ts` if present (and log it), for
268
+ * projects upgrading from a version that still emitted it.
269
+ */
270
+ const removeObsoleteGeneratedFile = Effect.fnUntraced(function* (fileName) {
271
+ const fs = yield* FileSystem.FileSystem;
272
+ const path = yield* Path.Path;
273
+ const confectDirectory = yield* ConfectDirectory.get;
274
+ const filePath = path.join(confectDirectory, "_generated", fileName);
275
+ if (yield* fs.exists(filePath)) {
276
+ yield* removePathIfExists(filePath);
277
+ yield* logFileRemoved(filePath);
278
+ }
279
+ });
280
+ const removeGeneratedApi = removeObsoleteGeneratedFile("api.ts");
281
+ const removeGeneratedNodeApi = removeObsoleteGeneratedFile("nodeApi.ts");
282
+ /**
283
+ * The user-authored `confect/schema.ts` is no longer supported: codegen now
284
+ * owns both `_generated/schema.ts` (runtime) and `_generated/convexSchema.ts`
285
+ * (deploy), derived from a single scan of `confect/tables/*.ts`. Detect a stray
286
+ * file and fail with a clear migration message—leaving it in place would
287
+ * silently shadow the codegen-owned
288
+ * `_generated/schema.ts`/`_generated/convexSchema.ts`.
289
+ */
290
+ const rejectLegacySchemaFile = Effect.gen(function* () {
291
+ const fs = yield* FileSystem.FileSystem;
292
+ const path = yield* Path.Path;
293
+ const confectDirectory = yield* ConfectDirectory.get;
294
+ const legacyPath = path.join(confectDirectory, "schema.ts");
295
+ if (yield* fs.exists(legacyPath)) return yield* new LegacySchemaFileError({ schemaPath: "schema.ts" });
296
+ });
297
+ /**
298
+ * Surface a yellow `⚠` warning when codegen sees no tables—either the
299
+ * `confect/tables/` directory is missing or it contains no `.ts` files.
300
+ * Generation still succeeds (emitting an empty `DatabaseSchema` and
301
+ * `defineSchema({})`), since action-only/table-free Confect backends are
302
+ * legal—but the warning catches the much more common case of a typoed directory
303
+ * or files placed under the wrong root.
304
+ */
305
+ const warnIfNoTables = (tableModules) => tableModules.length === 0 ? logWarn(`No tables discovered in \`confect/${TABLES_DIRNAME}/\`. Generating an empty schema; add a \`Table.make(...)\` module under that directory unless this backend is intentionally tables-free.`) : Effect.void;
306
+ const tableModuleBindings = Effect.fnUntraced(function* (tableModules, generatedFilePath) {
307
+ const path = yield* Path.Path;
308
+ const confectDirectory = yield* ConfectDirectory.get;
309
+ const generatedDir = path.dirname(generatedFilePath);
310
+ const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
311
+ return yield* Effect.forEach(tableModules, Effect.fnUntraced(function* (tableModule) {
312
+ const wrapperAbsolutePath = path.join(confectDirectory, generatedTablesDirname, `${tableModule.tableName}.ts`);
313
+ return {
314
+ importPath: yield* toModuleImportPath(path.relative(generatedDir, wrapperAbsolutePath)),
315
+ tableName: tableModule.tableName
316
+ };
317
+ }));
318
+ });
319
+ const generateIdConstructor = Effect.fnUntraced(function* (tableModules) {
320
+ const path = yield* Path.Path;
321
+ const confectDirectory = yield* ConfectDirectory.get;
322
+ const generatedIdPath = yield* GENERATED_ID_PATH;
323
+ const idPath = path.join(confectDirectory, generatedIdPath);
324
+ const tableNames = Array.map(tableModules, (tableModule) => tableModule.tableName);
325
+ const contents = yield* Match.value(yield* ConvexDirectory.target).pipe(Match.when({ kind: "app" }, () => id({ tableNames })), Match.when({ kind: "component" }, ({ scope }) => id({
326
+ tableNames,
327
+ scope
328
+ })), Match.exhaustive);
329
+ yield* writeFileStringAndLog(idPath, contents);
330
+ });
331
+ const generateTableWrappers = Effect.fnUntraced(function* (tableModules) {
332
+ const fs = yield* FileSystem.FileSystem;
333
+ const path = yield* Path.Path;
334
+ const confectDirectory = yield* ConfectDirectory.get;
335
+ const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
336
+ const wrappersDir = path.join(confectDirectory, generatedTablesDirname);
337
+ if (!(yield* fs.exists(wrappersDir))) yield* fs.makeDirectory(wrappersDir, { recursive: true });
338
+ yield* Effect.forEach(tableModules, Effect.fnUntraced(function* (tableModule) {
339
+ const wrapperPath = path.join(confectDirectory, generatedTablesDirname, `${tableModule.tableName}.ts`);
340
+ const unnamedAbsolutePath = path.join(confectDirectory, tableModule.relativePath);
341
+ const unnamedImportPath = yield* toModuleImportPath(path.relative(path.dirname(wrapperPath), unnamedAbsolutePath));
342
+ const contents = yield* tableWrapper({
343
+ tableName: tableModule.tableName,
344
+ unnamedImportPath,
345
+ component: yield* isComponent
346
+ });
347
+ yield* writeFileStringAndLog(wrapperPath, contents);
348
+ }), { concurrency: "unbounded" });
349
+ });
350
+ /**
351
+ * Remove any stale `_generated/tables/*.ts` wrapper whose source table has been
352
+ * deleted or renamed. Mirrors `removeObsoleteRegisteredFunctions` for the
353
+ * wrapper directory.
354
+ */
355
+ const removeObsoleteTableWrappers = Effect.fnUntraced(function* (tableModules) {
356
+ const fs = yield* FileSystem.FileSystem;
357
+ const path = yield* Path.Path;
358
+ const confectDirectory = yield* ConfectDirectory.get;
359
+ const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
360
+ const wrappersDir = path.join(confectDirectory, generatedTablesDirname);
361
+ if (!(yield* fs.exists(wrappersDir))) return;
362
+ const expected = new Set(Array.map(tableModules, (tableModule) => `${tableModule.tableName}.ts`));
363
+ const existing = yield* fs.readDirectory(wrappersDir, { recursive: true });
364
+ yield* Effect.forEach(existing, (entry) => {
365
+ if (path.extname(entry) !== ".ts") return Effect.void;
366
+ if (!expected.has(entry)) return Effect.gen(function* () {
367
+ const absolutePath = path.join(wrappersDir, entry);
368
+ if (yield* fs.exists(absolutePath)) {
369
+ yield* removePathIfExists(absolutePath);
370
+ yield* logFileRemoved(absolutePath);
371
+ }
372
+ });
373
+ return Effect.void;
374
+ });
375
+ });
376
+ const generateRuntimeSchema = Effect.fnUntraced(function* (tableModules) {
377
+ const path = yield* Path.Path;
378
+ const confectDirectory = yield* ConfectDirectory.get;
379
+ const generatedSchemaPath = yield* GENERATED_SCHEMA_PATH;
380
+ const schemaPath = path.join(confectDirectory, generatedSchemaPath);
381
+ const bindings = yield* tableModuleBindings(tableModules, schemaPath);
382
+ const contents = yield* runtimeSchema({
383
+ tableModules: bindings,
384
+ component: yield* isComponent
385
+ });
386
+ yield* writeFileStringAndLog(schemaPath, contents);
387
+ });
388
+ const generateConvexSchema = Effect.fnUntraced(function* (tableModules) {
389
+ const path = yield* Path.Path;
390
+ const confectDirectory = yield* ConfectDirectory.get;
391
+ const generatedConvexSchemaPath = yield* GENERATED_CONVEX_SCHEMA_PATH;
392
+ const convexSchemaPath = path.join(confectDirectory, generatedConvexSchemaPath);
393
+ const bindings = yield* tableModuleBindings(tableModules, convexSchemaPath);
394
+ const contents = yield* convexSchema({ tableModules: bindings });
395
+ yield* writeFileStringAndLog(convexSchemaPath, contents);
396
+ });
397
+ const generateConvexSchemaReexport = Effect.gen(function* () {
398
+ const path = yield* Path.Path;
399
+ const confectDirectory = yield* ConfectDirectory.get;
400
+ const convexDirectory = yield* ConvexDirectory.get;
401
+ const generatedConvexSchemaRelativePath = yield* GENERATED_CONVEX_SCHEMA_PATH;
402
+ const convexSchemaPath = path.join(convexDirectory, "schema.ts");
403
+ const generatedConvexSchemaPath = path.join(confectDirectory, generatedConvexSchemaRelativePath);
404
+ const convexSchemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(convexSchemaPath), generatedConvexSchemaPath));
405
+ const schemaContents = yield* schema({ convexSchemaImportPath });
406
+ yield* writeFileStringAndLog(convexSchemaPath, schemaContents);
407
+ });
408
+ const generateServices = Effect.gen(function* () {
409
+ const path = yield* Path.Path;
410
+ const confectDirectory = yield* ConfectDirectory.get;
411
+ const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
412
+ const servicesPath = path.join(confectGeneratedDirectory, "services.ts");
413
+ const generatedSchemaPath = yield* GENERATED_SCHEMA_PATH;
414
+ const schemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(servicesPath), path.join(confectDirectory, generatedSchemaPath)));
415
+ const servicesContentsString = yield* services({
416
+ schemaImportPath,
417
+ component: yield* isComponent
418
+ });
419
+ yield* writeFileStringAndLog(servicesPath, servicesContentsString);
420
+ });
421
+ /**
422
+ * Two tables whose names fold to the same PascalCase document type name (e.g.
423
+ * `user_profiles` and `userProfiles` → `UserProfilesDoc`) would emit duplicate
424
+ * interfaces in `_generated/docs.ts`. Catch that up front with a clear error
425
+ * rather than letting `tsc` report a duplicate-identifier later.
426
+ */
427
+ const validateNoDocNameCollisions = Effect.fnUntraced(function* (tableModules) {
428
+ const collisions = pipe(tableModules, Array.groupBy((tableModule) => fromTableName(tableModule.tableName)), Record.toEntries, Array.filter(([, group]) => group.length > 1), Array.map(([docName, group]) => ({
429
+ docName,
430
+ tableNames: Array.map(group, (tableModule) => tableModule.tableName)
431
+ })));
432
+ if (Array.isReadonlyArrayNonEmpty(collisions)) return yield* new ConflictingDocNameError({ collisions });
433
+ });
434
+ const generateDocs = Effect.fnUntraced(function* (tableModules) {
435
+ const path = yield* Path.Path;
436
+ const confectDirectory = yield* ConfectDirectory.get;
437
+ const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
438
+ const docsPath = path.join(confectGeneratedDirectory, "docs.ts");
439
+ const generatedSchemaPath = yield* GENERATED_SCHEMA_PATH;
440
+ const schemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(docsPath), path.join(confectDirectory, generatedSchemaPath)));
441
+ const docsContentsString = yield* docs({
442
+ schemaImportPath,
443
+ tables: Array.map(tableModules, (tableModule) => ({
444
+ tableName: tableModule.tableName,
445
+ docName: fromTableName(tableModule.tableName)
446
+ }))
447
+ });
448
+ yield* writeFileStringAndLog(docsPath, docsContentsString);
449
+ });
450
+ const generateRefs = Effect.gen(function* () {
451
+ const path = yield* Path.Path;
452
+ const confectDirectory = yield* ConfectDirectory.get;
453
+ const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
454
+ const refsPath = path.join(confectGeneratedDirectory, "refs.ts");
455
+ const refsDir = path.dirname(refsPath);
456
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
457
+ const specImportPath = yield* toModuleImportPath(path.relative(refsDir, path.join(confectDirectory, generatedSpecPath)));
458
+ const refsContents = yield* refs({ specImportPath });
459
+ yield* writeFileStringAndLog(refsPath, refsContents);
460
+ });
461
+ /**
462
+ * Generate `confect/_generated/components.ts`—the typed registry of Convex
463
+ * components installed in `convex/convex.config.ts` (see
464
+ * {@link templates.components}). The file is emitted even when there is no
465
+ * `convex.config.ts` (as an empty registry) so the import surface stays stable.
466
+ * It must exist before loading tables and specs because they may import bound
467
+ * component IDs and schemas.
468
+ */
469
+ const generateComponents = Effect.gen(function* () {
470
+ const fs = yield* FileSystem.FileSystem;
471
+ const path = yield* Path.Path;
472
+ const confectDirectory = yield* ConfectDirectory.get;
473
+ const convexDirectory = yield* ConvexDirectory.get;
474
+ const projectRoot = yield* ProjectRoot.get;
475
+ const confectGeneratedDirectory = path.join(confectDirectory, GENERATED_DIRNAME);
476
+ const generatedComponentsPath = yield* GENERATED_COMPONENTS_PATH;
477
+ const componentsPath = path.join(confectDirectory, generatedComponentsPath);
478
+ const convexConfigPath = path.join(convexDirectory, CONVEX_CONFIG_FILENAME);
479
+ const installedComponents = (yield* fs.exists(convexConfigPath)) ? yield* discoverInstalledComponents(convexConfigPath, path.relative(projectRoot, convexConfigPath), (yield* ConvexDirectory.target).kind) : [];
480
+ const contents = yield* components({ components: Array.map(installedComponents, ({ name, componentDefinitionPath }) => ({
481
+ name,
482
+ typeImportPath: typeImportPath(path, componentDefinitionPath, confectGeneratedDirectory)
483
+ })) });
484
+ yield* writeFileStringAndLog(componentsPath, contents);
485
+ });
486
+ const logGenerated = (effect) => effect.pipe(Effect.tap(Option.match({
487
+ onNone: () => Effect.void,
488
+ onSome: ({ change, convexFilePath }) => Match.value(change).pipe(Match.when("Added", () => logFileAdded(convexFilePath)), Match.when("Modified", () => logFileModified(convexFilePath)), Match.when("Unchanged", () => Effect.void), Match.exhaustive)
489
+ })));
490
+ /**
491
+ * Test-only imports never enter the runtime schema or function wrappers.
492
+ */
493
+ const quote = (value) => JSON.stringify(value);
494
+ const generateTesting = Effect.fnUntraced(function* (app) {
495
+ const fs = yield* FileSystem.FileSystem;
496
+ const path = yield* Path.Path;
497
+ const confectDirectory = yield* ConfectDirectory.get;
498
+ const convexDirectory = yield* ConvexDirectory.get;
499
+ const generated = path.join(confectDirectory, "_generated");
500
+ const relative = (file) => {
501
+ const value = path.relative(generated, file).split(path.sep).join("/");
502
+ return value.startsWith(".") ? value : `./${value}`;
503
+ };
504
+ const imports = ["import schema from \"./schema\";", "import convexSchema from \"./convexSchema\";"];
505
+ const moduleMap = Effect.fnUntraced(function* (directory) {
506
+ return `{ "_generated/testing.ts": () => Promise.resolve({}),\n${(yield* fs.readDirectory(directory, { recursive: true })).filter((file) => /\.[cm]?[jt]s$/.test(file) && !file.endsWith(".d.ts") && !file.split(path.sep).includes("_generated") && !file.includes(".config.")).sort().map((file) => {
507
+ const specifier = relative(path.join(directory, file));
508
+ return `${quote(file.split(path.sep).join("/"))}: () => import(${quote(specifier.replace(/\.[cm]?[jt]s$/, ""))}),`;
509
+ }).join("\n")}\n}`;
510
+ });
511
+ const components = [];
512
+ const httpMounts = [];
513
+ const kits = /* @__PURE__ */ new Map();
514
+ const visit = Effect.fnUntraced(function* (directory, prefix, kind, parentHttpPrefix) {
515
+ const config = path.join(directory, CONVEX_CONFIG_FILENAME);
516
+ if (!(yield* fs.exists(config))) return;
517
+ const installed = yield* discoverInstalledComponents(config, relative(config), kind);
518
+ for (const component of installed) {
519
+ const name = prefix + component.name;
520
+ const resolved = path.isAbsolute(component.componentDefinitionPath) ? component.componentDefinitionPath : path.dirname(yield* resolveDefinition(`${component.componentDefinitionPath}/convex.config`, config, path).pipe(Effect.fromOption, Effect.mapError(() => new InvalidConvexConfigError({
521
+ configPath: relative(config),
522
+ reason: `Cannot resolve component ${component.componentDefinitionPath} for testing.`
523
+ }))));
524
+ const localPrefix = prefix === "" ? App.mounts(app).find((mount) => mount.name === component.name)?.httpPrefix : component.httpPrefix;
525
+ const httpPrefix = parentHttpPrefix === void 0 || localPrefix === void 0 ? void 0 : `${parentHttpPrefix.replace(/\/+$/, "")}${localPrefix}`.replace(/\/+$/, "") || "/";
526
+ if (httpPrefix !== void 0 && ((yield* fs.exists(path.join(resolved, "http.ts"))) || (yield* fs.exists(path.join(resolved, "http.js"))))) httpMounts.push(`{ componentPath: ${quote(name)}, httpPrefix: ${quote(httpPrefix)} }`);
527
+ let identifier = kits.get(resolved);
528
+ if (identifier === void 0) {
529
+ identifier = `component${kits.size}`;
530
+ kits.set(resolved, identifier);
531
+ imports.push(`import ${identifier}Schema from ${quote(relative(path.join(resolved, "schema")))};`);
532
+ imports.push(`const ${identifier} = { schema: ${identifier}Schema, modules: ${yield* moduleMap(resolved)} };`);
533
+ }
534
+ components.push(`${quote(name)}: ${identifier},`);
535
+ yield* visit(resolved, `${name}/`, "component", httpPrefix);
536
+ }
537
+ });
538
+ yield* visit(convexDirectory, "", (yield* ConvexDirectory.target).kind, "");
539
+ yield* writeFileStringAndLog(path.join(generated, "testing.ts"), [
540
+ "// Generated by `confect codegen`. Test-only; do not import in backend functions.",
541
+ ...imports,
542
+ `export default { httpMounts: [${httpMounts.join(",")}], schema, convexSchema, modules: ${yield* moduleMap(convexDirectory)}, components: {${components.join("\n")}} };`
543
+ ].join("\n"));
544
+ });
545
+ //#endregion
546
+ export { codegen, codegenHandler, generateComponents, generateWithSnapshot, loadPreviousFunctionPaths, makeCodegenHandler, updateDeclarations };
547
+
548
+ //# sourceMappingURL=codegen.mjs.map