@confect/cli 9.0.0-next.1 → 9.0.0-next.10

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 (69) hide show
  1. package/CHANGELOG.md +337 -1
  2. package/dist/BuildError.mjs +9 -2
  3. package/dist/BuildError.mjs.map +1 -1
  4. package/dist/Bundler.mjs +67 -57
  5. package/dist/Bundler.mjs.map +1 -1
  6. package/dist/CodeBlockWriter.mjs +1 -1
  7. package/dist/CodeBlockWriter.mjs.map +1 -1
  8. package/dist/CodegenError.mjs +29 -21
  9. package/dist/CodegenError.mjs.map +1 -1
  10. package/dist/ConfectDirectory.mjs +5 -2
  11. package/dist/ConfectDirectory.mjs.map +1 -1
  12. package/dist/ConvexDirectory.mjs +6 -2
  13. package/dist/ConvexDirectory.mjs.map +1 -1
  14. package/dist/FunctionPath.mjs +1 -1
  15. package/dist/FunctionPath.mjs.map +1 -1
  16. package/dist/FunctionPaths.mjs +5 -1
  17. package/dist/FunctionPaths.mjs.map +1 -1
  18. package/dist/GroupPath.mjs +9 -2
  19. package/dist/GroupPath.mjs.map +1 -1
  20. package/dist/GroupPaths.mjs +1 -1
  21. package/dist/GroupPaths.mjs.map +1 -1
  22. package/dist/LeafModule.mjs +19 -26
  23. package/dist/LeafModule.mjs.map +1 -1
  24. package/dist/ProjectRoot.mjs +8 -3
  25. package/dist/ProjectRoot.mjs.map +1 -1
  26. package/dist/SpecAssemblyNode.mjs +9 -11
  27. package/dist/SpecAssemblyNode.mjs.map +1 -1
  28. package/dist/TableModule.mjs +94 -0
  29. package/dist/TableModule.mjs.map +1 -0
  30. package/dist/cliApp.mjs +1 -1
  31. package/dist/cliApp.mjs.map +1 -1
  32. package/dist/confect/codegen.mjs +203 -142
  33. package/dist/confect/codegen.mjs.map +1 -1
  34. package/dist/confect/dev.mjs +36 -17
  35. package/dist/confect/dev.mjs.map +1 -1
  36. package/dist/confect.mjs +2 -2
  37. package/dist/confect.mjs.map +1 -1
  38. package/dist/index.mjs +3 -2
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/log.mjs +9 -4
  41. package/dist/log.mjs.map +1 -1
  42. package/dist/package.mjs +1 -1
  43. package/dist/templates.mjs +132 -45
  44. package/dist/templates.mjs.map +1 -1
  45. package/dist/utils.mjs +42 -22
  46. package/dist/utils.mjs.map +1 -1
  47. package/package.json +33 -50
  48. package/dist/index.d.mts +0 -1
  49. package/src/BuildError.ts +0 -210
  50. package/src/Bundler.ts +0 -144
  51. package/src/CodeBlockWriter.ts +0 -65
  52. package/src/CodegenError.ts +0 -376
  53. package/src/ConfectDirectory.ts +0 -42
  54. package/src/ConvexDirectory.ts +0 -68
  55. package/src/FunctionPath.ts +0 -27
  56. package/src/FunctionPaths.ts +0 -103
  57. package/src/GroupPath.ts +0 -118
  58. package/src/GroupPaths.ts +0 -7
  59. package/src/LeafModule.ts +0 -317
  60. package/src/ProjectRoot.ts +0 -50
  61. package/src/SpecAssemblyNode.ts +0 -82
  62. package/src/cliApp.ts +0 -8
  63. package/src/confect/codegen.ts +0 -710
  64. package/src/confect/dev.ts +0 -749
  65. package/src/confect.ts +0 -19
  66. package/src/index.ts +0 -22
  67. package/src/log.ts +0 -104
  68. package/src/templates.ts +0 -482
  69. package/src/utils.ts +0 -429
@@ -1,35 +1,47 @@
1
- import { logFileAdded, logFileModified, logFileRemoved, logPending, logSuccess } from "../log.mjs";
2
- import { fromBundlerError } from "../BuildError.mjs";
3
- import { MissingImplFileError, MissingSchemaFileError, MissingSpecFileError, ParentChildNameCollisionError, SchemaInvalidDefaultExportError, tapAndLog } from "../CodegenError.mjs";
1
+ import { logFileAdded, logFileModified, logFileRemoved, logPending, logSuccess, logWarn } from "../log.mjs";
2
+ import { bundle } from "../Bundler.mjs";
3
+ import { LegacySchemaFileError, MissingImplFileError, MissingSpecFileError, ParentChildNameCollisionError, tapAndLog } from "../CodegenError.mjs";
4
4
  import { ConvexDirectory } from "../ConvexDirectory.mjs";
5
5
  import { ConfectDirectory } from "../ConfectDirectory.mjs";
6
6
  import { FunctionPaths, make } from "../FunctionPaths.mjs";
7
- import { bundle } from "../Bundler.mjs";
8
- import { assemblyNodesFromLeaves, partitionByRuntime } from "../SpecAssemblyNode.mjs";
9
- import { api, assembledSpec, nodeApi, refs, registeredFunctionsForGroup, schema, services } from "../templates.mjs";
10
- import { WriteTracker, generateAuthConfig, generateCrons, generateFunctions, generateHttp, removePathExtension, removePathIfExists, toModuleImportPath, touchConvexSchema, writeFileStringAndLog } from "../utils.mjs";
11
- import { discoverLeafImplFiles, discoverLeafSpecFiles, implPathForSpec, registeredFunctionsRelativePath, specPathForImpl, toLeafModule, toNodeRegistryLeaf, validateImpl, validateSpec } from "../LeafModule.mjs";
12
- import { Array, Effect, Either, HashSet, Match, Option, Ref } from "effect";
13
- import { Command } from "@effect/cli";
7
+ import { assemblyNodesFromLeaves } from "../SpecAssemblyNode.mjs";
8
+ import { assembledSpec, convexSchema, id, refs, registeredFunctionsForGroup, runtimeSchema, schema, services, tableWrapper } from "../templates.mjs";
9
+ import { WriteTracker, generateAuthConfig, generateCrons, generateFunctions, generateHttp, removePathIfExists, toModuleImportPath, touchConvexSchema, writeFileStringAndLog } from "../utils.mjs";
10
+ import { discoverLeafImplFiles, discoverLeafSpecFiles, implPathForSpec, registeredFunctionsRelativePath, specPathForImpl, toLeafModule, validateImpl, validateSpec } from "../LeafModule.mjs";
11
+ import { TABLES_DIRNAME, discover, validate } from "../TableModule.mjs";
12
+ import * as Effect from "effect/Effect";
13
+ import * as Command from "@effect/cli/Command";
14
14
  import { Spec } from "@confect/core";
15
- import * as DatabaseSchema from "@confect/server/DatabaseSchema";
16
- import { FileSystem, Path } from "@effect/platform";
15
+ import * as FileSystem from "@effect/platform/FileSystem";
16
+ import * as Path from "@effect/platform/Path";
17
+ import * as Array from "effect/Array";
18
+ import * as Either from "effect/Either";
19
+ import * as HashSet from "effect/HashSet";
20
+ import * as Match from "effect/Match";
21
+ import * as Option from "effect/Option";
22
+ import * as Ref from "effect/Ref";
17
23
 
18
24
  //#region src/confect/codegen.ts
19
- const GENERATED_SPEC_PATH = "_generated/spec.ts";
20
- const GENERATED_NODE_SPEC_PATH = "_generated/nodeSpec.ts";
21
- const LEGACY_PATHS = [
22
- "spec.ts",
23
- "nodeSpec.ts",
24
- "impl.ts",
25
- "nodeImpl.ts",
26
- "notesAndRandom.impl.ts",
27
- "groups.impl.ts",
28
- "_generated/registeredFunctions.ts",
29
- "_generated/nodeRegisteredFunctions.ts",
30
- "_generated/impl.ts",
31
- "_generated/nodeImpl.ts"
32
- ];
25
+ const GENERATED_DIRNAME = "_generated";
26
+ const GENERATED_SPEC_PATH = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "spec.ts"));
27
+ const GENERATED_SCHEMA_PATH = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "schema.ts"));
28
+ const GENERATED_CONVEX_SCHEMA_PATH = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "convexSchema.ts"));
29
+ const GENERATED_ID_PATH = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "id.ts"));
30
+ const GENERATED_TABLES_DIRNAME = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "tables"));
31
+ const LEGACY_PATHS = Effect.gen(function* () {
32
+ const path = yield* Path.Path;
33
+ return [
34
+ "spec.ts",
35
+ "nodeSpec.ts",
36
+ "impl.ts",
37
+ "nodeImpl.ts",
38
+ path.join(GENERATED_DIRNAME, "registeredFunctions.ts"),
39
+ path.join(GENERATED_DIRNAME, "nodeRegisteredFunctions.ts"),
40
+ path.join(GENERATED_DIRNAME, "impl.ts"),
41
+ path.join(GENERATED_DIRNAME, "nodeImpl.ts"),
42
+ path.join(GENERATED_DIRNAME, "nodeSpec.ts")
43
+ ];
44
+ });
33
45
  const codegen = Command.make("codegen", {}, () => Effect.gen(function* () {
34
46
  yield* logPending("Performing initial sync…");
35
47
  yield* codegenHandler.pipe(Effect.asVoid, Effect.tap(() => logSuccess("Generated files are up-to-date")), tapAndLog);
@@ -43,23 +55,31 @@ const codegenHandler = Effect.gen(function* () {
43
55
  });
44
56
  const runCodegen = Effect.gen(function* () {
45
57
  yield* generateConfectGeneratedDirectory;
46
- yield* validateSchema;
58
+ yield* rejectLegacySchemaFile;
59
+ const tableModules = yield* discover;
60
+ yield* warnIfNoTables(tableModules);
61
+ yield* generateIdConstructor(tableModules);
62
+ yield* validate(tableModules);
63
+ yield* generateTableWrappers(tableModules);
64
+ yield* removeObsoleteTableWrappers(tableModules);
65
+ yield* generateRuntimeSchema(tableModules);
47
66
  const { leaves, groupSpecsByRelativePath } = yield* loadAndValidateLeafModules;
48
67
  yield* removeLegacyFiles;
49
68
  yield* validateNoParentChildNameCollisions(leaves, groupSpecsByRelativePath);
50
69
  yield* generateAssembledSpecs(leaves);
51
- yield* validateImplModules(leaves);
52
- yield* generateGroupRegisteredFunctions(leaves);
53
- yield* removeObsoleteRegisteredFunctions(leaves);
54
70
  yield* Effect.all([
55
- generateApi,
71
+ removeGeneratedApi,
56
72
  generateRefs,
57
- generateNodeApi,
58
- generateServices
73
+ removeGeneratedNodeApi,
74
+ generateServices,
75
+ generateConvexSchema(tableModules)
59
76
  ], { concurrency: "unbounded" });
77
+ yield* validateImplModules(leaves);
78
+ yield* generateGroupRegisteredFunctions(leaves);
79
+ yield* removeObsoleteRegisteredFunctions(leaves);
60
80
  const [functionPaths] = yield* Effect.all([
61
81
  generateFunctionModules,
62
- generateSchema,
82
+ generateConvexSchemaReexport,
63
83
  logGenerated(generateHttp),
64
84
  logGenerated(generateCrons),
65
85
  logGenerated(generateAuthConfig)
@@ -82,8 +102,12 @@ const loadAndValidateLeafModules = Effect.gen(function* () {
82
102
  const confectDirectory = yield* ConfectDirectory.get;
83
103
  const specFiles = yield* discoverLeafSpecFiles;
84
104
  const results = yield* Effect.forEach(specFiles, (specRelativePath) => Effect.gen(function* () {
85
- const leaf = yield* toLeafModule(specRelativePath);
86
- const groupSpec = yield* validateSpec(leaf);
105
+ const discovered = yield* toLeafModule(specRelativePath);
106
+ const groupSpec = yield* validateSpec(discovered);
107
+ const leaf = {
108
+ ...discovered,
109
+ runtime: Option.some(groupSpec.runtime)
110
+ };
87
111
  const implRelativePath = yield* implPathForSpec(specRelativePath);
88
112
  const implAbsolutePath = path.join(confectDirectory, implRelativePath);
89
113
  if (!(yield* fs.exists(implAbsolutePath))) return yield* new MissingImplFileError({
@@ -110,11 +134,8 @@ const loadAndValidateLeafModules = Effect.gen(function* () {
110
134
  * `Refs.make` error rather than a codegen-time diagnostic.
111
135
  */
112
136
  const validateNoParentChildNameCollisions = (leaves, groupSpecsByRelativePath) => Effect.gen(function* () {
113
- const { convex, node } = partitionByRuntime(leaves);
114
- const convexNodes = assemblyNodesFromLeaves(convex);
115
- const nodeNodes = assemblyNodesFromLeaves(Array.map(node, toNodeRegistryLeaf));
116
- yield* Effect.forEach(convexNodes, (n) => checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath));
117
- yield* Effect.forEach(nodeNodes, (n) => checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath));
137
+ const nodes = assemblyNodesFromLeaves(leaves);
138
+ yield* Effect.forEach(nodes, (n) => checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath));
118
139
  });
119
140
  const checkAssemblyNodeForCollisions = (node, groupSpecsByRelativePath) => Effect.gen(function* () {
120
141
  yield* Option.match(node.importBinding, {
@@ -184,7 +205,8 @@ const removeLegacyFiles = Effect.gen(function* () {
184
205
  const fs = yield* FileSystem.FileSystem;
185
206
  const path = yield* Path.Path;
186
207
  const confectDirectory = yield* ConfectDirectory.get;
187
- yield* Effect.forEach(LEGACY_PATHS, (relativePath) => Effect.gen(function* () {
208
+ const legacyPaths = yield* LEGACY_PATHS;
209
+ yield* Effect.forEach(legacyPaths, (relativePath) => Effect.gen(function* () {
188
210
  const absolutePath = path.join(confectDirectory, relativePath);
189
211
  if (yield* fs.exists(absolutePath)) {
190
212
  yield* removePathIfExists(absolutePath);
@@ -195,23 +217,10 @@ const removeLegacyFiles = Effect.gen(function* () {
195
217
  const generateAssembledSpecs = (leaves) => Effect.gen(function* () {
196
218
  const path = yield* Path.Path;
197
219
  const confectDirectory = yield* ConfectDirectory.get;
198
- const { convex, node } = partitionByRuntime(leaves);
199
- if (convex.length > 0) {
200
- const nodes = assemblyNodesFromLeaves(convex);
201
- const specContents = yield* assembledSpec({
202
- nodes,
203
- runtime: "Convex"
204
- });
205
- yield* writeFileStringAndLog(path.join(confectDirectory, GENERATED_SPEC_PATH), specContents);
206
- }
207
- if (node.length > 0) {
208
- const nodes = assemblyNodesFromLeaves(Array.map(node, toNodeRegistryLeaf));
209
- const nodeSpecContents = yield* assembledSpec({
210
- nodes,
211
- runtime: "Node"
212
- });
213
- yield* writeFileStringAndLog(path.join(confectDirectory, GENERATED_NODE_SPEC_PATH), nodeSpecContents);
214
- }
220
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
221
+ const nodes = assemblyNodesFromLeaves(leaves);
222
+ const specContents = yield* assembledSpec({ nodes });
223
+ yield* writeFileStringAndLog(path.join(confectDirectory, generatedSpecPath), specContents);
215
224
  });
216
225
  const validateImplModules = (leaves) => Effect.forEach(leaves, validateImpl);
217
226
  const generateGroupRegisteredFunctions = (leaves) => Effect.gen(function* () {
@@ -224,15 +233,19 @@ const generateGroupRegisteredFunctions = (leaves) => Effect.gen(function* () {
224
233
  const fs = yield* FileSystem.FileSystem;
225
234
  if (!(yield* fs.exists(registryDir))) yield* fs.makeDirectory(registryDir, { recursive: true });
226
235
  const implRelativePath = yield* implPathForSpec(leaf.relativePath);
227
- const apiFileName = leaf.runtime === "Node" ? "nodeApi.ts" : "api.ts";
228
- const apiImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, "_generated", apiFileName)));
236
+ const schemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, "_generated", "schema.ts")));
237
+ const specImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, leaf.relativePath)));
229
238
  const implImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, implRelativePath)));
239
+ const runtime = yield* Option.match(leaf.runtime, {
240
+ onNone: () => Effect.dieMessage(`Runtime for '${leaf.relativePath}' was not resolved before registry generation.`),
241
+ onSome: Effect.succeed
242
+ });
230
243
  yield* writeFileStringAndLog(registryPath, yield* registeredFunctionsForGroup({
231
- apiImportPath,
232
- groupPathDot: leaf.registryGroupPathDot,
244
+ schemaImportPath,
245
+ specImportPath,
233
246
  implImportPath,
234
247
  layerExportName: leaf.exportName,
235
- useNode: leaf.runtime === "Node"
248
+ useNode: runtime === "Node"
236
249
  }));
237
250
  }));
238
251
  });
@@ -260,130 +273,178 @@ const removeObsoleteRegisteredFunctions = (leaves) => Effect.gen(function* () {
260
273
  const getGeneratedSpecPath = Effect.gen(function* () {
261
274
  const path = yield* Path.Path;
262
275
  const confectDirectory = yield* ConfectDirectory.get;
263
- return path.join(confectDirectory, GENERATED_SPEC_PATH);
264
- });
265
- const getGeneratedNodeSpecPath = Effect.gen(function* () {
266
- const path = yield* Path.Path;
267
- const confectDirectory = yield* ConfectDirectory.get;
268
- return path.join(confectDirectory, GENERATED_NODE_SPEC_PATH);
276
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
277
+ return path.join(confectDirectory, generatedSpecPath);
269
278
  });
270
279
  const loadGeneratedSpec = Effect.gen(function* () {
271
280
  const specPath = yield* getGeneratedSpecPath;
272
281
  const { module: specModule } = yield* bundle(specPath);
273
282
  const spec = specModule.default;
274
- if (!Spec.isConvexSpec(spec)) return yield* Effect.dieMessage("_generated/spec.ts does not export a valid Convex Spec");
283
+ if (!Spec.isSpec(spec)) return yield* Effect.dieMessage("_generated/spec.ts does not export a valid Spec");
275
284
  return spec;
276
285
  });
277
- const loadGeneratedNodeSpec = Effect.gen(function* () {
278
- const fs = yield* FileSystem.FileSystem;
279
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
280
- if (!(yield* fs.exists(nodeSpecPath))) return Option.none();
281
- const { module: nodeSpecModule } = yield* bundle(nodeSpecPath);
282
- const nodeSpec = nodeSpecModule.default;
283
- if (!Spec.isNodeSpec(nodeSpec)) return yield* Effect.dieMessage("_generated/nodeSpec.ts does not export a valid Node Spec");
284
- return Option.some(nodeSpec);
285
- });
286
286
  const emptyFunctionPaths = FunctionPaths.make(HashSet.empty());
287
287
  const loadPreviousFunctionPaths = Effect.gen(function* () {
288
288
  const fs = yield* FileSystem.FileSystem;
289
289
  const specPath = yield* getGeneratedSpecPath;
290
290
  if (!(yield* fs.exists(specPath))) return emptyFunctionPaths;
291
291
  const specEither = yield* loadGeneratedSpec.pipe(Effect.either);
292
- return yield* Either.match(specEither, {
293
- onLeft: () => Effect.succeed(emptyFunctionPaths),
294
- onRight: (spec) => Effect.gen(function* () {
295
- const nodeSpecOption = yield* loadGeneratedNodeSpec;
296
- const mergedSpec = Option.match(nodeSpecOption, {
297
- onNone: () => spec,
298
- onSome: (nodeSpec) => Spec.merge(spec, nodeSpec)
299
- });
300
- return make(mergedSpec);
301
- })
292
+ return Either.match(specEither, {
293
+ onLeft: () => emptyFunctionPaths,
294
+ onRight: (spec) => make(spec)
302
295
  });
303
296
  });
304
- const generateApi = Effect.gen(function* () {
297
+ /**
298
+ * Remove a now-obsolete `_generated/<name>.ts` if present (and log it), for
299
+ * projects upgrading from a version that still emitted it.
300
+ */
301
+ const removeObsoleteGeneratedFile = (fileName) => Effect.gen(function* () {
302
+ const fs = yield* FileSystem.FileSystem;
305
303
  const path = yield* Path.Path;
306
304
  const confectDirectory = yield* ConfectDirectory.get;
307
- const apiPath = path.join(confectDirectory, "_generated", "api.ts");
308
- const apiDir = path.dirname(apiPath);
309
- const schemaImportPath = yield* toModuleImportPath(path.relative(apiDir, path.join(confectDirectory, "schema.ts")));
310
- const specImportPath = yield* toModuleImportPath(path.relative(apiDir, path.join(confectDirectory, GENERATED_SPEC_PATH)));
311
- yield* writeFileStringAndLog(apiPath, yield* api({
312
- schemaImportPath,
313
- specImportPath
314
- }));
305
+ const filePath = path.join(confectDirectory, "_generated", fileName);
306
+ if (yield* fs.exists(filePath)) {
307
+ yield* removePathIfExists(filePath);
308
+ yield* logFileRemoved(filePath);
309
+ }
310
+ });
311
+ const removeGeneratedApi = removeObsoleteGeneratedFile("api.ts");
312
+ const removeGeneratedNodeApi = removeObsoleteGeneratedFile("nodeApi.ts");
313
+ const generateFunctionModules = Effect.gen(function* () {
314
+ return yield* generateFunctions(yield* loadGeneratedSpec);
315
315
  });
316
- const generateNodeApi = Effect.gen(function* () {
316
+ /**
317
+ * The user-authored `confect/schema.ts` is no longer supported: codegen now
318
+ * owns both `_generated/schema.ts` (runtime) and `_generated/convexSchema.ts`
319
+ * (deploy), derived from a single scan of `confect/tables/*.ts`. Detect a
320
+ * stray file and fail with a clear migration message — leaving it in place
321
+ * would silently shadow the codegen-owned `_generated/schema.ts` /
322
+ * `_generated/convexSchema.ts`.
323
+ */
324
+ const rejectLegacySchemaFile = Effect.gen(function* () {
317
325
  const fs = yield* FileSystem.FileSystem;
318
326
  const path = yield* Path.Path;
319
327
  const confectDirectory = yield* ConfectDirectory.get;
320
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
321
- const nodeApiPath = path.join(confectDirectory, "_generated", "nodeApi.ts");
322
- if (!(yield* fs.exists(nodeSpecPath))) {
323
- if (yield* fs.exists(nodeApiPath)) {
324
- yield* removePathIfExists(nodeApiPath);
325
- yield* logFileRemoved(nodeApiPath);
326
- }
327
- return;
328
- }
329
- const nodeApiDir = path.dirname(nodeApiPath);
330
- const schemaImportPath = yield* toModuleImportPath(path.relative(nodeApiDir, path.join(confectDirectory, "schema.ts")));
331
- const nodeSpecImportPath = yield* toModuleImportPath(path.relative(nodeApiDir, nodeSpecPath));
332
- yield* writeFileStringAndLog(nodeApiPath, yield* nodeApi({
333
- schemaImportPath,
334
- nodeSpecImportPath
335
- }));
328
+ const legacyPath = path.join(confectDirectory, "schema.ts");
329
+ if (yield* fs.exists(legacyPath)) return yield* new LegacySchemaFileError({ schemaPath: "schema.ts" });
336
330
  });
337
- const generateFunctionModules = Effect.gen(function* () {
338
- const spec = yield* loadGeneratedSpec;
339
- const nodeSpecOption = yield* loadGeneratedNodeSpec;
340
- return yield* generateFunctions(Option.match(nodeSpecOption, {
341
- onNone: () => spec,
342
- onSome: (nodeSpec) => Spec.merge(spec, nodeSpec)
331
+ /**
332
+ * Surface a yellow `⚠` warning when codegen sees no tables — either the
333
+ * `confect/tables/` directory is missing or it contains no `.ts` files.
334
+ * Generation still succeeds (emitting an empty `DatabaseSchema` and
335
+ * `defineSchema({})`), since action-only / table-free Confect backends
336
+ * are legal but the warning catches the much more common case of a
337
+ * typoed directory or files placed under the wrong root.
338
+ */
339
+ 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;
340
+ const tableModuleBindings = (tableModules, generatedFilePath) => Effect.gen(function* () {
341
+ const path = yield* Path.Path;
342
+ const confectDirectory = yield* ConfectDirectory.get;
343
+ const generatedDir = path.dirname(generatedFilePath);
344
+ const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
345
+ return yield* Effect.forEach(tableModules, (tm) => Effect.gen(function* () {
346
+ const wrapperAbsolutePath = path.join(confectDirectory, generatedTablesDirname, `${tm.tableName}.ts`);
347
+ return {
348
+ importPath: yield* toModuleImportPath(path.relative(generatedDir, wrapperAbsolutePath)),
349
+ tableName: tm.tableName
350
+ };
343
351
  }));
344
352
  });
345
- const validateSchema = Effect.gen(function* () {
353
+ const generateIdConstructor = (tableModules) => Effect.gen(function* () {
354
+ const path = yield* Path.Path;
355
+ const confectDirectory = yield* ConfectDirectory.get;
356
+ const generatedIdPath = yield* GENERATED_ID_PATH;
357
+ const idPath = path.join(confectDirectory, generatedIdPath);
358
+ const tableNames = tableModules.map((tm) => tm.tableName);
359
+ yield* writeFileStringAndLog(idPath, yield* id({ tableNames }));
360
+ });
361
+ const generateTableWrappers = (tableModules) => Effect.gen(function* () {
346
362
  const fs = yield* FileSystem.FileSystem;
347
363
  const path = yield* Path.Path;
348
364
  const confectDirectory = yield* ConfectDirectory.get;
349
- const confectSchemaPath = path.join(confectDirectory, "schema.ts");
350
- if (!(yield* fs.exists(confectSchemaPath))) return yield* new MissingSchemaFileError({ schemaPath: "schema.ts" });
351
- yield* bundle(confectSchemaPath).pipe(Effect.mapError((error) => fromBundlerError("schema.ts", error)), Effect.andThen(({ module: schemaModule }) => {
352
- const defaultExport = schemaModule.default;
353
- return DatabaseSchema.isDatabaseSchema(defaultExport) ? Effect.succeed(defaultExport) : Effect.fail(new SchemaInvalidDefaultExportError({ schemaPath: "schema.ts" }));
354
- }));
365
+ const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
366
+ const wrappersDir = path.join(confectDirectory, generatedTablesDirname);
367
+ if (!(yield* fs.exists(wrappersDir))) yield* fs.makeDirectory(wrappersDir, { recursive: true });
368
+ yield* Effect.forEach(tableModules, (tm) => Effect.gen(function* () {
369
+ const wrapperPath = path.join(confectDirectory, generatedTablesDirname, `${tm.tableName}.ts`);
370
+ const unnamedAbsolutePath = path.join(confectDirectory, tm.relativePath);
371
+ const unnamedImportPath = yield* toModuleImportPath(path.relative(path.dirname(wrapperPath), unnamedAbsolutePath));
372
+ yield* writeFileStringAndLog(wrapperPath, yield* tableWrapper({
373
+ tableName: tm.tableName,
374
+ unnamedImportPath
375
+ }));
376
+ }), { concurrency: "unbounded" });
377
+ });
378
+ /**
379
+ * Remove any stale `_generated/tables/*.ts` wrapper whose source table
380
+ * has been deleted or renamed. Mirrors `removeObsoleteRegisteredFunctions`
381
+ * for the wrapper directory.
382
+ */
383
+ const removeObsoleteTableWrappers = (tableModules) => Effect.gen(function* () {
384
+ const fs = yield* FileSystem.FileSystem;
385
+ const path = yield* Path.Path;
386
+ const confectDirectory = yield* ConfectDirectory.get;
387
+ const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
388
+ const wrappersDir = path.join(confectDirectory, generatedTablesDirname);
389
+ if (!(yield* fs.exists(wrappersDir))) return;
390
+ const expected = new Set(tableModules.map((tm) => `${tm.tableName}.ts`));
391
+ const existing = yield* fs.readDirectory(wrappersDir, { recursive: true });
392
+ yield* Effect.forEach(existing, (entry) => {
393
+ if (path.extname(entry) !== ".ts") return Effect.void;
394
+ if (!expected.has(entry)) return Effect.gen(function* () {
395
+ const absolutePath = path.join(wrappersDir, entry);
396
+ if (yield* fs.exists(absolutePath)) {
397
+ yield* removePathIfExists(absolutePath);
398
+ yield* logFileRemoved(absolutePath);
399
+ }
400
+ });
401
+ return Effect.void;
402
+ });
355
403
  });
356
- const generateSchema = Effect.gen(function* () {
404
+ const generateRuntimeSchema = (tableModules) => Effect.gen(function* () {
405
+ const path = yield* Path.Path;
406
+ const confectDirectory = yield* ConfectDirectory.get;
407
+ const generatedSchemaPath = yield* GENERATED_SCHEMA_PATH;
408
+ const schemaPath = path.join(confectDirectory, generatedSchemaPath);
409
+ const bindings = yield* tableModuleBindings(tableModules, schemaPath);
410
+ yield* writeFileStringAndLog(schemaPath, yield* runtimeSchema({ tableModules: bindings }));
411
+ });
412
+ const generateConvexSchema = (tableModules) => Effect.gen(function* () {
413
+ const path = yield* Path.Path;
414
+ const confectDirectory = yield* ConfectDirectory.get;
415
+ const generatedConvexSchemaPath = yield* GENERATED_CONVEX_SCHEMA_PATH;
416
+ const convexSchemaPath = path.join(confectDirectory, generatedConvexSchemaPath);
417
+ const bindings = yield* tableModuleBindings(tableModules, convexSchemaPath);
418
+ yield* writeFileStringAndLog(convexSchemaPath, yield* convexSchema({ tableModules: bindings }));
419
+ });
420
+ const generateConvexSchemaReexport = Effect.gen(function* () {
357
421
  const path = yield* Path.Path;
358
422
  const confectDirectory = yield* ConfectDirectory.get;
359
423
  const convexDirectory = yield* ConvexDirectory.get;
360
- const confectSchemaPath = path.join(confectDirectory, "schema.ts");
424
+ const generatedConvexSchemaRelativePath = yield* GENERATED_CONVEX_SCHEMA_PATH;
361
425
  const convexSchemaPath = path.join(convexDirectory, "schema.ts");
362
- const importPathWithoutExt = yield* removePathExtension(path.relative(path.dirname(convexSchemaPath), confectSchemaPath));
363
- yield* writeFileStringAndLog(convexSchemaPath, yield* schema({ schemaImportPath: importPathWithoutExt }));
426
+ const generatedConvexSchemaPath = path.join(confectDirectory, generatedConvexSchemaRelativePath);
427
+ const convexSchemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(convexSchemaPath), generatedConvexSchemaPath));
428
+ yield* writeFileStringAndLog(convexSchemaPath, yield* schema({ convexSchemaImportPath }));
364
429
  });
365
430
  const generateServices = Effect.gen(function* () {
366
431
  const path = yield* Path.Path;
367
432
  const confectDirectory = yield* ConfectDirectory.get;
368
433
  const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
369
434
  const servicesPath = path.join(confectGeneratedDirectory, "services.ts");
370
- const schemaImportPath = path.relative(path.dirname(servicesPath), path.join(confectDirectory, "schema"));
435
+ const generatedSchemaPath = yield* GENERATED_SCHEMA_PATH;
436
+ const schemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(servicesPath), path.join(confectDirectory, generatedSchemaPath)));
371
437
  yield* writeFileStringAndLog(servicesPath, yield* services({ schemaImportPath }));
372
438
  });
373
439
  const generateRefs = Effect.gen(function* () {
374
- const fs = yield* FileSystem.FileSystem;
375
440
  const path = yield* Path.Path;
376
441
  const confectDirectory = yield* ConfectDirectory.get;
377
442
  const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
378
443
  const refsPath = path.join(confectGeneratedDirectory, "refs.ts");
379
444
  const refsDir = path.dirname(refsPath);
380
- const specImportPath = yield* toModuleImportPath(path.relative(refsDir, path.join(confectDirectory, GENERATED_SPEC_PATH)));
381
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
382
- const nodeSpecImportPath = (yield* fs.exists(nodeSpecPath)) ? Option.some(yield* toModuleImportPath(path.relative(refsDir, nodeSpecPath))) : Option.none();
383
- yield* writeFileStringAndLog(refsPath, yield* refs({
384
- specImportPath,
385
- nodeSpecImportPath
386
- }));
445
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
446
+ const specImportPath = yield* toModuleImportPath(path.relative(refsDir, path.join(confectDirectory, generatedSpecPath)));
447
+ yield* writeFileStringAndLog(refsPath, yield* refs({ specImportPath }));
387
448
  });
388
449
  const logGenerated = (effect) => effect.pipe(Effect.tap(Option.match({
389
450
  onNone: () => Effect.void,