@confect/cli 9.0.0-next.0 → 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 +351 -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 +36 -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 +20 -24
  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 +272 -141
  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 +139 -48
  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 -344
  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 -313
  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 -589
  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 -477
  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, 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,22 +55,31 @@ const codegenHandler = Effect.gen(function* () {
43
55
  });
44
56
  const runCodegen = Effect.gen(function* () {
45
57
  yield* generateConfectGeneratedDirectory;
46
- yield* validateSchema;
47
- const leaves = yield* loadAndValidateLeafModules;
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);
66
+ const { leaves, groupSpecsByRelativePath } = yield* loadAndValidateLeafModules;
48
67
  yield* removeLegacyFiles;
68
+ yield* validateNoParentChildNameCollisions(leaves, groupSpecsByRelativePath);
49
69
  yield* generateAssembledSpecs(leaves);
50
- yield* validateImplModules(leaves);
51
- yield* generateGroupRegisteredFunctions(leaves);
52
- yield* removeObsoleteRegisteredFunctions(leaves);
53
70
  yield* Effect.all([
54
- generateApi,
71
+ removeGeneratedApi,
55
72
  generateRefs,
56
- generateNodeApi,
57
- generateServices
73
+ removeGeneratedNodeApi,
74
+ generateServices,
75
+ generateConvexSchema(tableModules)
58
76
  ], { concurrency: "unbounded" });
77
+ yield* validateImplModules(leaves);
78
+ yield* generateGroupRegisteredFunctions(leaves);
79
+ yield* removeObsoleteRegisteredFunctions(leaves);
59
80
  const [functionPaths] = yield* Effect.all([
60
81
  generateFunctionModules,
61
- generateSchema,
82
+ generateConvexSchemaReexport,
62
83
  logGenerated(generateHttp),
63
84
  logGenerated(generateCrons),
64
85
  logGenerated(generateAuthConfig)
@@ -80,20 +101,90 @@ const loadAndValidateLeafModules = Effect.gen(function* () {
80
101
  const path = yield* Path.Path;
81
102
  const confectDirectory = yield* ConfectDirectory.get;
82
103
  const specFiles = yield* discoverLeafSpecFiles;
83
- const leaves = yield* Effect.forEach(specFiles, (specRelativePath) => Effect.gen(function* () {
84
- const leaf = yield* toLeafModule(specRelativePath);
85
- yield* validateSpec(leaf);
104
+ const results = yield* Effect.forEach(specFiles, (specRelativePath) => Effect.gen(function* () {
105
+ const discovered = yield* toLeafModule(specRelativePath);
106
+ const groupSpec = yield* validateSpec(discovered);
107
+ const leaf = {
108
+ ...discovered,
109
+ runtime: Option.some(groupSpec.runtime)
110
+ };
86
111
  const implRelativePath = yield* implPathForSpec(specRelativePath);
87
112
  const implAbsolutePath = path.join(confectDirectory, implRelativePath);
88
113
  if (!(yield* fs.exists(implAbsolutePath))) return yield* new MissingImplFileError({
89
114
  specPath: specRelativePath,
90
115
  expectedImplPath: implRelativePath
91
116
  });
92
- return leaf;
117
+ return {
118
+ leaf,
119
+ groupSpec
120
+ };
93
121
  }));
94
122
  yield* validateOrphanImpls(specFiles);
95
- return leaves;
123
+ return {
124
+ leaves: Array.map(results, ({ leaf }) => leaf),
125
+ groupSpecsByRelativePath: new Map(Array.map(results, ({ leaf, groupSpec }) => [leaf.relativePath, groupSpec]))
126
+ };
127
+ });
128
+ /**
129
+ * Walk the assembly tree and fail with a {@link ParentChildNameCollisionError}
130
+ * when a parent leaf declares a function or subgroup whose name matches a
131
+ * sibling subdirectory spec's segment. Without this check the colliding
132
+ * descendant would overwrite the parent's entry in the assembled
133
+ * `GroupSpec.groups` map at runtime, surfacing as a confusing
134
+ * `Refs.make` error rather than a codegen-time diagnostic.
135
+ */
136
+ const validateNoParentChildNameCollisions = (leaves, groupSpecsByRelativePath) => Effect.gen(function* () {
137
+ const nodes = assemblyNodesFromLeaves(leaves);
138
+ yield* Effect.forEach(nodes, (n) => checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath));
139
+ });
140
+ const checkAssemblyNodeForCollisions = (node, groupSpecsByRelativePath) => Effect.gen(function* () {
141
+ yield* Option.match(node.importBinding, {
142
+ onNone: () => Effect.void,
143
+ onSome: (binding) => Effect.gen(function* () {
144
+ if (node.children.length === 0) return;
145
+ const parentRelativePath = bindingToRelativeSpecPath(binding.importPath);
146
+ const parentGroupSpec = groupSpecsByRelativePath.get(parentRelativePath);
147
+ if (parentGroupSpec === void 0) return;
148
+ yield* Effect.forEach(node.children, (child) => {
149
+ if (Object.prototype.hasOwnProperty.call(parentGroupSpec.functions, child.segment)) return Effect.fail(new ParentChildNameCollisionError({
150
+ parentSpecPath: parentRelativePath,
151
+ childSpecPath: childRepresentativeSpecPath(child),
152
+ collisionName: child.segment,
153
+ collisionKind: "function"
154
+ }));
155
+ if (Object.prototype.hasOwnProperty.call(parentGroupSpec.groups, child.segment)) return Effect.fail(new ParentChildNameCollisionError({
156
+ parentSpecPath: parentRelativePath,
157
+ childSpecPath: childRepresentativeSpecPath(child),
158
+ collisionName: child.segment,
159
+ collisionKind: "group"
160
+ }));
161
+ return Effect.void;
162
+ });
163
+ })
164
+ });
165
+ yield* Effect.forEach(node.children, (child) => checkAssemblyNodeForCollisions(child, groupSpecsByRelativePath));
96
166
  });
167
+ /**
168
+ * `LeafModule.specImportPath` is the import path used from inside the
169
+ * generated `_generated/spec.ts` (e.g. `"../notes.spec"`). Strip the
170
+ * `../` prefix and re-add the `.ts` extension to recover the leaf's
171
+ * confect-relative spec path used as the key in
172
+ * `groupSpecsByRelativePath`.
173
+ */
174
+ const bindingToRelativeSpecPath = (importPath) => {
175
+ return `${importPath.startsWith("../") ? importPath.slice(3) : importPath}.ts`;
176
+ };
177
+ /**
178
+ * A child assembly node may itself be a parent without a leaf (when the
179
+ * actual leaves live only in deeper subdirectories). In that case we
180
+ * surface the first descendant leaf as a representative path so the
181
+ * error message points at something the user actually wrote.
182
+ */
183
+ const childRepresentativeSpecPath = (node) => {
184
+ if (Option.isSome(node.importBinding)) return bindingToRelativeSpecPath(node.importBinding.value.importPath);
185
+ for (const child of node.children) return childRepresentativeSpecPath(child);
186
+ return node.segment;
187
+ };
97
188
  const validateOrphanImpls = (specFiles) => Effect.gen(function* () {
98
189
  const fs = yield* FileSystem.FileSystem;
99
190
  const path = yield* Path.Path;
@@ -114,7 +205,8 @@ const removeLegacyFiles = Effect.gen(function* () {
114
205
  const fs = yield* FileSystem.FileSystem;
115
206
  const path = yield* Path.Path;
116
207
  const confectDirectory = yield* ConfectDirectory.get;
117
- yield* Effect.forEach(LEGACY_PATHS, (relativePath) => Effect.gen(function* () {
208
+ const legacyPaths = yield* LEGACY_PATHS;
209
+ yield* Effect.forEach(legacyPaths, (relativePath) => Effect.gen(function* () {
118
210
  const absolutePath = path.join(confectDirectory, relativePath);
119
211
  if (yield* fs.exists(absolutePath)) {
120
212
  yield* removePathIfExists(absolutePath);
@@ -125,23 +217,10 @@ const removeLegacyFiles = Effect.gen(function* () {
125
217
  const generateAssembledSpecs = (leaves) => Effect.gen(function* () {
126
218
  const path = yield* Path.Path;
127
219
  const confectDirectory = yield* ConfectDirectory.get;
128
- const { convex, node } = partitionByRuntime(leaves);
129
- if (convex.length > 0) {
130
- const nodes = assemblyNodesFromLeaves(convex);
131
- const specContents = yield* assembledSpec({
132
- nodes,
133
- runtime: "Convex"
134
- });
135
- yield* writeFileStringAndLog(path.join(confectDirectory, GENERATED_SPEC_PATH), specContents);
136
- }
137
- if (node.length > 0) {
138
- const nodes = assemblyNodesFromLeaves(Array.map(node, toNodeRegistryLeaf));
139
- const nodeSpecContents = yield* assembledSpec({
140
- nodes,
141
- runtime: "Node"
142
- });
143
- yield* writeFileStringAndLog(path.join(confectDirectory, GENERATED_NODE_SPEC_PATH), nodeSpecContents);
144
- }
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);
145
224
  });
146
225
  const validateImplModules = (leaves) => Effect.forEach(leaves, validateImpl);
147
226
  const generateGroupRegisteredFunctions = (leaves) => Effect.gen(function* () {
@@ -154,15 +233,19 @@ const generateGroupRegisteredFunctions = (leaves) => Effect.gen(function* () {
154
233
  const fs = yield* FileSystem.FileSystem;
155
234
  if (!(yield* fs.exists(registryDir))) yield* fs.makeDirectory(registryDir, { recursive: true });
156
235
  const implRelativePath = yield* implPathForSpec(leaf.relativePath);
157
- const apiFileName = leaf.runtime === "Node" ? "nodeApi.ts" : "api.ts";
158
- 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)));
159
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
+ });
160
243
  yield* writeFileStringAndLog(registryPath, yield* registeredFunctionsForGroup({
161
- apiImportPath,
162
- groupPathDot: leaf.registryGroupPathDot,
244
+ schemaImportPath,
245
+ specImportPath,
163
246
  implImportPath,
164
247
  layerExportName: leaf.exportName,
165
- useNode: leaf.runtime === "Node"
248
+ useNode: runtime === "Node"
166
249
  }));
167
250
  }));
168
251
  });
@@ -190,130 +273,178 @@ const removeObsoleteRegisteredFunctions = (leaves) => Effect.gen(function* () {
190
273
  const getGeneratedSpecPath = Effect.gen(function* () {
191
274
  const path = yield* Path.Path;
192
275
  const confectDirectory = yield* ConfectDirectory.get;
193
- return path.join(confectDirectory, GENERATED_SPEC_PATH);
194
- });
195
- const getGeneratedNodeSpecPath = Effect.gen(function* () {
196
- const path = yield* Path.Path;
197
- const confectDirectory = yield* ConfectDirectory.get;
198
- return path.join(confectDirectory, GENERATED_NODE_SPEC_PATH);
276
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
277
+ return path.join(confectDirectory, generatedSpecPath);
199
278
  });
200
279
  const loadGeneratedSpec = Effect.gen(function* () {
201
280
  const specPath = yield* getGeneratedSpecPath;
202
281
  const { module: specModule } = yield* bundle(specPath);
203
282
  const spec = specModule.default;
204
- 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");
205
284
  return spec;
206
285
  });
207
- const loadGeneratedNodeSpec = Effect.gen(function* () {
208
- const fs = yield* FileSystem.FileSystem;
209
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
210
- if (!(yield* fs.exists(nodeSpecPath))) return Option.none();
211
- const { module: nodeSpecModule } = yield* bundle(nodeSpecPath);
212
- const nodeSpec = nodeSpecModule.default;
213
- if (!Spec.isNodeSpec(nodeSpec)) return yield* Effect.dieMessage("_generated/nodeSpec.ts does not export a valid Node Spec");
214
- return Option.some(nodeSpec);
215
- });
216
286
  const emptyFunctionPaths = FunctionPaths.make(HashSet.empty());
217
287
  const loadPreviousFunctionPaths = Effect.gen(function* () {
218
288
  const fs = yield* FileSystem.FileSystem;
219
289
  const specPath = yield* getGeneratedSpecPath;
220
290
  if (!(yield* fs.exists(specPath))) return emptyFunctionPaths;
221
291
  const specEither = yield* loadGeneratedSpec.pipe(Effect.either);
222
- return yield* Either.match(specEither, {
223
- onLeft: () => Effect.succeed(emptyFunctionPaths),
224
- onRight: (spec) => Effect.gen(function* () {
225
- const nodeSpecOption = yield* loadGeneratedNodeSpec;
226
- const mergedSpec = Option.match(nodeSpecOption, {
227
- onNone: () => spec,
228
- onSome: (nodeSpec) => Spec.merge(spec, nodeSpec)
229
- });
230
- return make(mergedSpec);
231
- })
292
+ return Either.match(specEither, {
293
+ onLeft: () => emptyFunctionPaths,
294
+ onRight: (spec) => make(spec)
232
295
  });
233
296
  });
234
- 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;
235
303
  const path = yield* Path.Path;
236
304
  const confectDirectory = yield* ConfectDirectory.get;
237
- const apiPath = path.join(confectDirectory, "_generated", "api.ts");
238
- const apiDir = path.dirname(apiPath);
239
- const schemaImportPath = yield* toModuleImportPath(path.relative(apiDir, path.join(confectDirectory, "schema.ts")));
240
- const specImportPath = yield* toModuleImportPath(path.relative(apiDir, path.join(confectDirectory, GENERATED_SPEC_PATH)));
241
- yield* writeFileStringAndLog(apiPath, yield* api({
242
- schemaImportPath,
243
- specImportPath
244
- }));
305
+ const filePath = path.join(confectDirectory, "_generated", fileName);
306
+ if (yield* fs.exists(filePath)) {
307
+ yield* removePathIfExists(filePath);
308
+ yield* logFileRemoved(filePath);
309
+ }
245
310
  });
246
- const generateNodeApi = Effect.gen(function* () {
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
+ });
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* () {
247
325
  const fs = yield* FileSystem.FileSystem;
248
326
  const path = yield* Path.Path;
249
327
  const confectDirectory = yield* ConfectDirectory.get;
250
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
251
- const nodeApiPath = path.join(confectDirectory, "_generated", "nodeApi.ts");
252
- if (!(yield* fs.exists(nodeSpecPath))) {
253
- if (yield* fs.exists(nodeApiPath)) {
254
- yield* removePathIfExists(nodeApiPath);
255
- yield* logFileRemoved(nodeApiPath);
256
- }
257
- return;
258
- }
259
- const nodeApiDir = path.dirname(nodeApiPath);
260
- const schemaImportPath = yield* toModuleImportPath(path.relative(nodeApiDir, path.join(confectDirectory, "schema.ts")));
261
- const nodeSpecImportPath = yield* toModuleImportPath(path.relative(nodeApiDir, nodeSpecPath));
262
- yield* writeFileStringAndLog(nodeApiPath, yield* nodeApi({
263
- schemaImportPath,
264
- nodeSpecImportPath
265
- }));
328
+ const legacyPath = path.join(confectDirectory, "schema.ts");
329
+ if (yield* fs.exists(legacyPath)) return yield* new LegacySchemaFileError({ schemaPath: "schema.ts" });
266
330
  });
267
- const generateFunctionModules = Effect.gen(function* () {
268
- const spec = yield* loadGeneratedSpec;
269
- const nodeSpecOption = yield* loadGeneratedNodeSpec;
270
- return yield* generateFunctions(Option.match(nodeSpecOption, {
271
- onNone: () => spec,
272
- 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
+ };
273
351
  }));
274
352
  });
275
- 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* () {
276
362
  const fs = yield* FileSystem.FileSystem;
277
363
  const path = yield* Path.Path;
278
364
  const confectDirectory = yield* ConfectDirectory.get;
279
- const confectSchemaPath = path.join(confectDirectory, "schema.ts");
280
- if (!(yield* fs.exists(confectSchemaPath))) return yield* new MissingSchemaFileError({ schemaPath: "schema.ts" });
281
- yield* bundle(confectSchemaPath).pipe(Effect.mapError((error) => fromBundlerError("schema.ts", error)), Effect.andThen(({ module: schemaModule }) => {
282
- const defaultExport = schemaModule.default;
283
- return DatabaseSchema.isDatabaseSchema(defaultExport) ? Effect.succeed(defaultExport) : Effect.fail(new SchemaInvalidDefaultExportError({ schemaPath: "schema.ts" }));
284
- }));
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
+ });
285
403
  });
286
- 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* () {
287
421
  const path = yield* Path.Path;
288
422
  const confectDirectory = yield* ConfectDirectory.get;
289
423
  const convexDirectory = yield* ConvexDirectory.get;
290
- const confectSchemaPath = path.join(confectDirectory, "schema.ts");
424
+ const generatedConvexSchemaRelativePath = yield* GENERATED_CONVEX_SCHEMA_PATH;
291
425
  const convexSchemaPath = path.join(convexDirectory, "schema.ts");
292
- const importPathWithoutExt = yield* removePathExtension(path.relative(path.dirname(convexSchemaPath), confectSchemaPath));
293
- 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 }));
294
429
  });
295
430
  const generateServices = Effect.gen(function* () {
296
431
  const path = yield* Path.Path;
297
432
  const confectDirectory = yield* ConfectDirectory.get;
298
433
  const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
299
434
  const servicesPath = path.join(confectGeneratedDirectory, "services.ts");
300
- 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)));
301
437
  yield* writeFileStringAndLog(servicesPath, yield* services({ schemaImportPath }));
302
438
  });
303
439
  const generateRefs = Effect.gen(function* () {
304
- const fs = yield* FileSystem.FileSystem;
305
440
  const path = yield* Path.Path;
306
441
  const confectDirectory = yield* ConfectDirectory.get;
307
442
  const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
308
443
  const refsPath = path.join(confectGeneratedDirectory, "refs.ts");
309
444
  const refsDir = path.dirname(refsPath);
310
- const specImportPath = yield* toModuleImportPath(path.relative(refsDir, path.join(confectDirectory, GENERATED_SPEC_PATH)));
311
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
312
- const nodeSpecImportPath = (yield* fs.exists(nodeSpecPath)) ? Option.some(yield* toModuleImportPath(path.relative(refsDir, nodeSpecPath))) : Option.none();
313
- yield* writeFileStringAndLog(refsPath, yield* refs({
314
- specImportPath,
315
- nodeSpecImportPath
316
- }));
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 }));
317
448
  });
318
449
  const logGenerated = (effect) => effect.pipe(Effect.tap(Option.match({
319
450
  onNone: () => Effect.void,