@confect/cli 9.0.0-next.7 → 9.0.0-next.9

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 +60 -0
  2. package/dist/BuildError.mjs +9 -2
  3. package/dist/BuildError.mjs.map +1 -1
  4. package/dist/Bundler.mjs +5 -2
  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 +10 -15
  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 +17 -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 +6 -9
  27. package/dist/SpecAssemblyNode.mjs.map +1 -1
  28. package/dist/TableModule.mjs +6 -2
  29. package/dist/TableModule.mjs.map +1 -1
  30. package/dist/cliApp.mjs +1 -1
  31. package/dist/cliApp.mjs.map +1 -1
  32. package/dist/confect/codegen.mjs +36 -72
  33. package/dist/confect/codegen.mjs.map +1 -1
  34. package/dist/confect/dev.mjs +24 -7
  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 +7 -3
  41. package/dist/log.mjs.map +1 -1
  42. package/dist/package.mjs +1 -1
  43. package/dist/templates.mjs +8 -15
  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 +3 -3
  48. package/src/BuildError.ts +9 -2
  49. package/src/Bundler.ts +5 -2
  50. package/src/CodeBlockWriter.ts +1 -1
  51. package/src/CodegenError.ts +7 -38
  52. package/src/ConfectDirectory.ts +5 -2
  53. package/src/ConvexDirectory.ts +6 -2
  54. package/src/FunctionPath.ts +1 -1
  55. package/src/FunctionPaths.ts +5 -1
  56. package/src/GroupPath.ts +9 -11
  57. package/src/GroupPaths.ts +1 -1
  58. package/src/LeafModule.ts +24 -36
  59. package/src/ProjectRoot.ts +8 -3
  60. package/src/SpecAssemblyNode.ts +5 -14
  61. package/src/TableModule.ts +6 -2
  62. package/src/cliApp.ts +1 -1
  63. package/src/confect/codegen.ts +54 -108
  64. package/src/confect/dev.ts +24 -29
  65. package/src/confect.ts +2 -2
  66. package/src/index.ts +3 -2
  67. package/src/log.ts +7 -3
  68. package/src/templates.ts +11 -28
  69. package/src/utils.ts +47 -41
package/src/LeafModule.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  import { GroupSpec, Registry } from "@confect/core";
2
2
  import * as GroupImpl from "@confect/server/GroupImpl";
3
- import { FileSystem, Path } from "@effect/platform";
3
+ import * as FileSystem from "@effect/platform/FileSystem";
4
+ import * as Path from "@effect/platform/Path";
4
5
  import type { Context } from "effect";
5
- import { Array, Effect, Layer, Option, Ref, String } from "effect";
6
+ import * as Array from "effect/Array";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Layer from "effect/Layer";
9
+ import * as Option from "effect/Option";
10
+ import * as Ref from "effect/Ref";
11
+ import * as String from "effect/String";
6
12
  import { fromBundlerError } from "./BuildError";
7
13
  import * as Bundler from "./Bundler";
8
14
  import {
@@ -11,7 +17,6 @@ import {
11
17
  ImplMissingSpecImportError,
12
18
  ImplNotFinalizedError,
13
19
  SpecMissingDefaultGroupSpecError,
14
- SpecRuntimeMismatchError,
15
20
  } from "./CodegenError";
16
21
  import { ConfectDirectory } from "./ConfectDirectory";
17
22
  import { removePathExtension } from "./utils";
@@ -20,9 +25,15 @@ export interface LeafModule {
20
25
  readonly relativePath: string;
21
26
  readonly pathSegments: readonly [string, ...string[]];
22
27
  readonly groupPathDot: string;
23
- readonly registryGroupPathDot: string;
24
28
  readonly exportName: string;
25
- readonly runtime: "Convex" | "Node";
29
+ /**
30
+ * The runtime declared by the group's spec — `"Node"` for
31
+ * `GroupSpec.makeNode()`, `"Convex"` for `GroupSpec.make()`. `None` while the
32
+ * runtime is unknown: discovery (`toLeafModule`) works from the file path alone,
33
+ * which does not determine the runtime, so this is filled in once the spec has
34
+ * been bundled and validated (see `validateSpec`).
35
+ */
36
+ readonly runtime: Option.Option<"Convex" | "Node">;
26
37
  readonly specImportPath: string;
27
38
  }
28
39
 
@@ -98,24 +109,10 @@ export const specPathForImpl = (implRelativePath: string) =>
98
109
  export const implPathForSpec = (specRelativePath: string) =>
99
110
  swapModuleSuffix(specRelativePath, SPEC_SUFFIX, IMPL_SUFFIX);
100
111
 
101
- export const isNodeLeafModule = (relativePath: string) =>
102
- relativePath.startsWith("node/") || relativePath.startsWith("node\\");
103
-
104
- export const toNodeRegistryLeaf = (leaf: LeafModule): LeafModule => ({
105
- ...leaf,
106
- pathSegments: [leaf.exportName],
107
- groupPathDot: leaf.exportName,
108
- });
109
-
110
112
  export const registeredFunctionsRelativePath = (leaf: LeafModule) =>
111
113
  Effect.gen(function* () {
112
114
  const path = yield* Path.Path;
113
- return (
114
- path.join(
115
- "registeredFunctions",
116
- ...leaf.pathSegments.slice(leaf.runtime === "Node" ? 1 : 0),
117
- ) + ".ts"
118
- );
115
+ return path.join("registeredFunctions", ...leaf.pathSegments) + ".ts";
119
116
  });
120
117
 
121
118
  export const discoverLeafSpecFiles = Effect.gen(function* () {
@@ -171,15 +168,14 @@ export const toLeafModule = (specRelativePath: string) =>
171
168
  const { pathSegments, groupPathDot } =
172
169
  yield* groupPathFromRelativeModulePath(specRelativePath);
173
170
  const specImportPath = yield* specImportPathFromGenerated(specRelativePath);
174
- const runtime = isNodeLeafModule(specRelativePath) ? "Node" : "Convex";
175
171
 
176
172
  return {
177
173
  relativePath: specRelativePath,
178
174
  pathSegments,
179
175
  groupPathDot,
180
176
  exportName,
181
- runtime,
182
- registryGroupPathDot: runtime === "Node" ? exportName : groupPathDot,
177
+ // Unknown until the spec is bundled; see `LeafModule.runtime`.
178
+ runtime: Option.none(),
183
179
  specImportPath,
184
180
  } satisfies LeafModule;
185
181
  });
@@ -192,11 +188,11 @@ const absoluteModulePath = (relativePath: string) =>
192
188
  });
193
189
 
194
190
  /**
195
- * Validate that the leaf's spec file default-exports a `GroupSpec` whose
196
- * runtime matches the leaf's location (`Convex` for files outside
197
- * `confect/node/`, `Node` for files inside it). Returns the validated
198
- * `GroupSpec` so callers can avoid re-bundling for later inspection (e.g.
199
- * parent/child name-collision checks at codegen time).
191
+ * Validate that the leaf's spec file default-exports a `GroupSpec`. Returns the
192
+ * validated `GroupSpec` so callers can read its runtime and avoid re-bundling for
193
+ * later inspection (e.g. stamping `leaf.runtime` and parent/child name-collision
194
+ * checks at codegen time). The group's runtime (`Convex` vs `Node`) is whatever
195
+ * the spec declares it is not constrained by the file's location.
200
196
  */
201
197
  export const validateSpec = (leaf: LeafModule) =>
202
198
  Effect.gen(function* () {
@@ -213,14 +209,6 @@ export const validateSpec = (leaf: LeafModule) =>
213
209
  });
214
210
  }
215
211
 
216
- if (groupSpec.runtime !== leaf.runtime) {
217
- return yield* new SpecRuntimeMismatchError({
218
- specPath: leaf.relativePath,
219
- expectedRuntime: leaf.runtime,
220
- actualRuntime: groupSpec.runtime,
221
- });
222
- }
223
-
224
212
  return groupSpec;
225
213
  });
226
214
 
@@ -1,6 +1,11 @@
1
- import { FileSystem, Path } from "@effect/platform";
2
- import { NodeFileSystem } from "@effect/platform-node";
3
- import { Array, Effect, Option, Ref, Schema } from "effect";
1
+ import * as FileSystem from "@effect/platform/FileSystem";
2
+ import * as Path from "@effect/platform/Path";
3
+ import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
4
+ import * as Array from "effect/Array";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Option from "effect/Option";
7
+ import * as Ref from "effect/Ref";
8
+ import * as Schema from "effect/Schema";
4
9
 
5
10
  export class ProjectRoot extends Effect.Service<ProjectRoot>()(
6
11
  "@confect/cli/ProjectRoot",
@@ -1,5 +1,9 @@
1
1
  import type { LeafModule } from "./LeafModule";
2
- import { Array, Option, Order, pipe, Record } from "effect";
2
+ import { pipe } from "effect/Function";
3
+ import * as Array from "effect/Array";
4
+ import * as Option from "effect/Option";
5
+ import * as Order from "effect/Order";
6
+ import * as Record from "effect/Record";
3
7
 
4
8
  export interface SpecImportBinding {
5
9
  readonly importPath: string;
@@ -48,19 +52,6 @@ export const assemblyNodesFromLeaves = (
48
52
  leaves: ReadonlyArray<LeafModule>,
49
53
  ): ReadonlyArray<SpecAssemblyNode> => assemblyNodesAtDepth(leaves, 0);
50
54
 
51
- export const partitionByRuntime = (
52
- leaves: ReadonlyArray<LeafModule>,
53
- ): {
54
- readonly convex: ReadonlyArray<LeafModule>;
55
- readonly node: ReadonlyArray<LeafModule>;
56
- } => {
57
- const [node, convex] = Array.partition(
58
- leaves,
59
- (leaf) => leaf.runtime === "Convex",
60
- );
61
- return { convex, node };
62
- };
63
-
64
55
  const importBindingsForNode = (
65
56
  node: SpecAssemblyNode,
66
57
  ): ReadonlyArray<SpecImportBinding> =>
@@ -1,7 +1,11 @@
1
1
  import { Identifier } from "@confect/core";
2
2
  import * as Table from "@confect/server/Table";
3
- import { FileSystem, Path } from "@effect/platform";
4
- import { Array, Effect, Order, pipe } from "effect";
3
+ import * as FileSystem from "@effect/platform/FileSystem";
4
+ import * as Path from "@effect/platform/Path";
5
+ import { pipe } from "effect/Function";
6
+ import * as Array from "effect/Array";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Order from "effect/Order";
5
9
  import { fromBundlerError } from "./BuildError";
6
10
  import * as Bundler from "./Bundler";
7
11
  import {
package/src/cliApp.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Command } from "@effect/cli";
1
+ import * as Command from "@effect/cli/Command";
2
2
  import packageJson from "../package.json" with { type: "json" };
3
3
  import { confect } from "./confect";
4
4
 
@@ -1,7 +1,14 @@
1
1
  import { Spec, type GroupSpec } from "@confect/core";
2
- import { Command } from "@effect/cli";
3
- import { FileSystem, Path } from "@effect/platform";
4
- import { Array, Effect, Either, HashSet, Match, Option, Ref } from "effect";
2
+ import * as Command from "@effect/cli/Command";
3
+ import * as FileSystem from "@effect/platform/FileSystem";
4
+ import * as Path from "@effect/platform/Path";
5
+ import * as Array from "effect/Array";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Either from "effect/Either";
8
+ import * as HashSet from "effect/HashSet";
9
+ import * as Match from "effect/Match";
10
+ import * as Option from "effect/Option";
11
+ import * as Ref from "effect/Ref";
5
12
  import * as Bundler from "../Bundler";
6
13
  import * as CodegenError from "../CodegenError";
7
14
  import {
@@ -20,7 +27,6 @@ import {
20
27
  registeredFunctionsRelativePath,
21
28
  specPathForImpl,
22
29
  toLeafModule,
23
- toNodeRegistryLeaf,
24
30
  validateImpl,
25
31
  validateSpec,
26
32
  type LeafModule,
@@ -35,7 +41,6 @@ import {
35
41
  } from "../log";
36
42
  import {
37
43
  assemblyNodesFromLeaves,
38
- partitionByRuntime,
39
44
  type SpecAssemblyNode,
40
45
  } from "../SpecAssemblyNode";
41
46
  import * as TableModule from "../TableModule";
@@ -57,9 +62,6 @@ const GENERATED_DIRNAME = "_generated";
57
62
  const GENERATED_SPEC_PATH = Effect.andThen(Path.Path, (path) =>
58
63
  path.join(GENERATED_DIRNAME, "spec.ts"),
59
64
  );
60
- const GENERATED_NODE_SPEC_PATH = Effect.andThen(Path.Path, (path) =>
61
- path.join(GENERATED_DIRNAME, "nodeSpec.ts"),
62
- );
63
65
  const GENERATED_SCHEMA_PATH = Effect.andThen(Path.Path, (path) =>
64
66
  path.join(GENERATED_DIRNAME, "schema.ts"),
65
67
  );
@@ -85,6 +87,9 @@ const LEGACY_PATHS = Effect.gen(function* () {
85
87
  path.join(GENERATED_DIRNAME, "nodeRegisteredFunctions.ts"),
86
88
  path.join(GENERATED_DIRNAME, "impl.ts"),
87
89
  path.join(GENERATED_DIRNAME, "nodeImpl.ts"),
90
+ // `_generated/nodeSpec.ts` is not part of the generated output (all groups
91
+ // live in `_generated/spec.ts`); delete any copy left by an older version.
92
+ path.join(GENERATED_DIRNAME, "nodeSpec.ts"),
88
93
  ];
89
94
  });
90
95
 
@@ -191,8 +196,13 @@ const loadAndValidateLeafModules = Effect.gen(function* () {
191
196
 
192
197
  const results = yield* Effect.forEach(specFiles, (specRelativePath) =>
193
198
  Effect.gen(function* () {
194
- const leaf = yield* toLeafModule(specRelativePath);
195
- const groupSpec = yield* validateSpec(leaf);
199
+ const discovered = yield* toLeafModule(specRelativePath);
200
+ const groupSpec = yield* validateSpec(discovered);
201
+ // Fill in the runtime now that the spec is bundled; discovery left it `None`.
202
+ const leaf = {
203
+ ...discovered,
204
+ runtime: Option.some(groupSpec.runtime),
205
+ };
196
206
 
197
207
  const implRelativePath = yield* implPathForSpec(specRelativePath);
198
208
  const implAbsolutePath = path.join(confectDirectory, implRelativePath);
@@ -230,15 +240,11 @@ export const validateNoParentChildNameCollisions = (
230
240
  groupSpecsByRelativePath: ReadonlyMap<string, GroupSpec.AnyWithProps>,
231
241
  ) =>
232
242
  Effect.gen(function* () {
233
- const { convex, node } = partitionByRuntime(leaves);
234
- const convexNodes = assemblyNodesFromLeaves(convex);
235
- const nodeNodes = assemblyNodesFromLeaves(
236
- Array.map(node, toNodeRegistryLeaf),
237
- );
238
- yield* Effect.forEach(convexNodes, (n) =>
239
- checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath),
240
- );
241
- yield* Effect.forEach(nodeNodes, (n) =>
243
+ // Convex and Node groups share one namespace, so they assemble into a
244
+ // single tree. A Node group nested under a Convex parent (or vice versa) is
245
+ // caught here by the parent/child collision check.
246
+ const nodes = assemblyNodesFromLeaves(leaves);
247
+ yield* Effect.forEach(nodes, (n) =>
242
248
  checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath),
243
249
  );
244
250
  });
@@ -377,34 +383,17 @@ const generateAssembledSpecs = (leaves: ReadonlyArray<LeafModule>) =>
377
383
  const path = yield* Path.Path;
378
384
  const confectDirectory = yield* ConfectDirectory.get;
379
385
  const generatedSpecPath = yield* GENERATED_SPEC_PATH;
380
- const generatedNodeSpecPath = yield* GENERATED_NODE_SPEC_PATH;
381
- const { convex, node } = partitionByRuntime(leaves);
382
-
383
- if (convex.length > 0) {
384
- const nodes = assemblyNodesFromLeaves(convex);
385
- const specContents = yield* templates.assembledSpec({
386
- nodes,
387
- runtime: "Convex",
388
- });
389
- yield* writeFileStringAndLog(
390
- path.join(confectDirectory, generatedSpecPath),
391
- specContents,
392
- );
393
- }
394
386
 
395
- if (node.length > 0) {
396
- const nodes = assemblyNodesFromLeaves(
397
- Array.map(node, toNodeRegistryLeaf),
398
- );
399
- const nodeSpecContents = yield* templates.assembledSpec({
400
- nodes,
401
- runtime: "Node",
402
- });
403
- yield* writeFileStringAndLog(
404
- path.join(confectDirectory, generatedNodeSpecPath),
405
- nodeSpecContents,
406
- );
407
- }
387
+ // A single assembled spec holds every group regardless of runtime — a Node
388
+ // group's `makeNode()` lives in its imported leaf spec, so the assembled
389
+ // file is runtime-agnostic. Always emit it (even empty) so downstream
390
+ // readers (`loadGeneratedSpec`, `generateRefs`) always find a spec module.
391
+ const nodes = assemblyNodesFromLeaves(leaves);
392
+ const specContents = yield* templates.assembledSpec({ nodes });
393
+ yield* writeFileStringAndLog(
394
+ path.join(confectDirectory, generatedSpecPath),
395
+ specContents,
396
+ );
408
397
  });
409
398
 
410
399
  const validateImplModules = (leaves: ReadonlyArray<LeafModule>) =>
@@ -452,12 +441,23 @@ const generateGroupRegisteredFunctions = (leaves: ReadonlyArray<LeafModule>) =>
452
441
  ),
453
442
  );
454
443
 
444
+ // Every leaf reaching this point came through
445
+ // `loadAndValidateLeafModules`, which stamps the runtime from the
446
+ // validated spec — so `None` here means that invariant was broken.
447
+ const runtime = yield* Option.match(leaf.runtime, {
448
+ onNone: () =>
449
+ Effect.dieMessage(
450
+ `Runtime for '${leaf.relativePath}' was not resolved before registry generation.`,
451
+ ),
452
+ onSome: Effect.succeed,
453
+ });
454
+
455
455
  const contents = yield* templates.registeredFunctionsForGroup({
456
456
  schemaImportPath,
457
457
  specImportPath,
458
458
  implImportPath,
459
459
  layerExportName: leaf.exportName,
460
- useNode: leaf.runtime === "Node",
460
+ useNode: runtime === "Node",
461
461
  });
462
462
 
463
463
  yield* writeFileStringAndLog(registryPath, contents);
@@ -512,47 +512,20 @@ const getGeneratedSpecPath = Effect.gen(function* () {
512
512
  return path.join(confectDirectory, generatedSpecPath);
513
513
  });
514
514
 
515
- const getGeneratedNodeSpecPath = Effect.gen(function* () {
516
- const path = yield* Path.Path;
517
- const confectDirectory = yield* ConfectDirectory.get;
518
- const generatedNodeSpecPath = yield* GENERATED_NODE_SPEC_PATH;
519
- return path.join(confectDirectory, generatedNodeSpecPath);
520
- });
521
-
522
515
  const loadGeneratedSpec = Effect.gen(function* () {
523
516
  const specPath = yield* getGeneratedSpecPath;
524
517
  const { module: specModule } = yield* Bundler.bundle(specPath);
525
518
  const spec = specModule.default;
526
519
 
527
- if (!Spec.isConvexSpec(spec)) {
520
+ if (!Spec.isSpec(spec)) {
528
521
  return yield* Effect.dieMessage(
529
- "_generated/spec.ts does not export a valid Convex Spec",
522
+ "_generated/spec.ts does not export a valid Spec",
530
523
  );
531
524
  }
532
525
 
533
526
  return spec;
534
527
  });
535
528
 
536
- const loadGeneratedNodeSpec = Effect.gen(function* () {
537
- const fs = yield* FileSystem.FileSystem;
538
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
539
-
540
- if (!(yield* fs.exists(nodeSpecPath))) {
541
- return Option.none<Spec.AnyWithPropsWithRuntime<"Node">>();
542
- }
543
-
544
- const { module: nodeSpecModule } = yield* Bundler.bundle(nodeSpecPath);
545
- const nodeSpec = nodeSpecModule.default;
546
-
547
- if (!Spec.isNodeSpec(nodeSpec)) {
548
- return yield* Effect.dieMessage(
549
- "_generated/nodeSpec.ts does not export a valid Node Spec",
550
- );
551
- }
552
-
553
- return Option.some(nodeSpec);
554
- });
555
-
556
529
  const emptyFunctionPaths = FunctionPaths.FunctionPaths.make(HashSet.empty());
557
530
 
558
531
  export const loadPreviousFunctionPaths = Effect.gen(function* () {
@@ -565,17 +538,9 @@ export const loadPreviousFunctionPaths = Effect.gen(function* () {
565
538
 
566
539
  const specEither = yield* loadGeneratedSpec.pipe(Effect.either);
567
540
 
568
- return yield* Either.match(specEither, {
569
- onLeft: () => Effect.succeed(emptyFunctionPaths),
570
- onRight: (spec) =>
571
- Effect.gen(function* () {
572
- const nodeSpecOption = yield* loadGeneratedNodeSpec;
573
- const mergedSpec = Option.match(nodeSpecOption, {
574
- onNone: () => spec,
575
- onSome: (nodeSpec) => Spec.merge(spec, nodeSpec),
576
- });
577
- return FunctionPaths.make(mergedSpec);
578
- }),
541
+ return Either.match(specEither, {
542
+ onLeft: () => emptyFunctionPaths,
543
+ onRight: (spec) => FunctionPaths.make(spec),
579
544
  });
580
545
  });
581
546
 
@@ -606,14 +571,7 @@ const removeGeneratedNodeApi = removeObsoleteGeneratedFile("nodeApi.ts");
606
571
 
607
572
  const generateFunctionModules = Effect.gen(function* () {
608
573
  const spec = yield* loadGeneratedSpec;
609
- const nodeSpecOption = yield* loadGeneratedNodeSpec;
610
-
611
- const mergedSpec = Option.match(nodeSpecOption, {
612
- onNone: () => spec,
613
- onSome: (nodeSpec) => Spec.merge(spec, nodeSpec),
614
- });
615
-
616
- return yield* generateFunctions(mergedSpec);
574
+ return yield* generateFunctions(spec);
617
575
  });
618
576
 
619
577
  /**
@@ -855,7 +813,6 @@ const generateServices = Effect.gen(function* () {
855
813
  });
856
814
 
857
815
  const generateRefs = Effect.gen(function* () {
858
- const fs = yield* FileSystem.FileSystem;
859
816
  const path = yield* Path.Path;
860
817
  const confectDirectory = yield* ConfectDirectory.get;
861
818
 
@@ -868,18 +825,7 @@ const generateRefs = Effect.gen(function* () {
868
825
  path.relative(refsDir, path.join(confectDirectory, generatedSpecPath)),
869
826
  );
870
827
 
871
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
872
- const nodeSpecExists = yield* fs.exists(nodeSpecPath);
873
- const nodeSpecImportPath = nodeSpecExists
874
- ? Option.some(
875
- yield* toModuleImportPath(path.relative(refsDir, nodeSpecPath)),
876
- )
877
- : Option.none<string>();
878
-
879
- const refsContents = yield* templates.refs({
880
- specImportPath,
881
- nodeSpecImportPath,
882
- });
828
+ const refsContents = yield* templates.refs({ specImportPath });
883
829
 
884
830
  yield* writeFileStringAndLog(refsPath, refsContents);
885
831
  });
@@ -1,27 +1,27 @@
1
- import { Command } from "@effect/cli";
2
- import { FileSystem, Path } from "@effect/platform";
3
- import { Ansi, AnsiDoc } from "@effect/printer-ansi";
4
- import {
5
- Array,
6
- Chunk,
7
- Clock,
8
- Console,
9
- Duration,
10
- Effect,
11
- Equal,
12
- ExecutionStrategy,
13
- Exit,
14
- HashSet,
15
- Match,
16
- Option,
17
- Order,
18
- pipe,
19
- Queue,
20
- Ref,
21
- Scope,
22
- Stream,
23
- String,
24
- } from "effect";
1
+ import * as Command from "@effect/cli/Command";
2
+ import * as FileSystem from "@effect/platform/FileSystem";
3
+ import * as Path from "@effect/platform/Path";
4
+ import * as Ansi from "@effect/printer-ansi/Ansi";
5
+ import * as AnsiDoc from "@effect/printer-ansi/AnsiDoc";
6
+ import { pipe } from "effect/Function";
7
+ import * as Array from "effect/Array";
8
+ import * as Chunk from "effect/Chunk";
9
+ import * as Clock from "effect/Clock";
10
+ import * as Console from "effect/Console";
11
+ import * as Duration from "effect/Duration";
12
+ import * as Effect from "effect/Effect";
13
+ import * as Equal from "effect/Equal";
14
+ import * as ExecutionStrategy from "effect/ExecutionStrategy";
15
+ import * as Exit from "effect/Exit";
16
+ import * as HashSet from "effect/HashSet";
17
+ import * as Match from "effect/Match";
18
+ import * as Option from "effect/Option";
19
+ import * as Order from "effect/Order";
20
+ import * as Queue from "effect/Queue";
21
+ import * as Ref from "effect/Ref";
22
+ import * as Scope from "effect/Scope";
23
+ import * as Stream from "effect/Stream";
24
+ import * as String from "effect/String";
25
25
  import {
26
26
  externalPlugin,
27
27
  loadTsConfig,
@@ -54,9 +54,6 @@ const GENERATED_DIRNAME = "_generated";
54
54
  const GENERATED_SPEC_PATH = Effect.andThen(Path.Path, (path) =>
55
55
  path.join(GENERATED_DIRNAME, "spec.ts"),
56
56
  );
57
- const GENERATED_NODE_SPEC_PATH = Effect.andThen(Path.Path, (path) =>
58
- path.join(GENERATED_DIRNAME, "nodeSpec.ts"),
59
- );
60
57
 
61
58
  // Quiescence window: the sync loop waits this long for further signals
62
59
  // after each batch. One user edit fires `onEnd` on every esbuild
@@ -421,11 +418,9 @@ const discoverEntryPoints = Effect.gen(function* () {
421
418
  });
422
419
 
423
420
  const generatedSpecPath = yield* GENERATED_SPEC_PATH;
424
- const generatedNodeSpecPath = yield* GENERATED_NODE_SPEC_PATH;
425
421
 
426
422
  const fixedEntryOptions = yield* Effect.all([
427
423
  tryEntry(generatedSpecPath, "specDirty"),
428
- tryEntry(generatedNodeSpecPath, "specDirty"),
429
424
  // `confect/schema.ts` is no longer user-authored; the runtime
430
425
  // `DatabaseSchema` lives at `_generated/schema.ts` (codegen-written,
431
426
  // so not an entry point — wiring it through esbuild would form a
package/src/confect.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Command } from "@effect/cli";
2
- import { Layer } from "effect";
1
+ import * as Command from "@effect/cli/Command";
2
+ import * as Layer from "effect/Layer";
3
3
  import { codegen } from "./confect/codegen";
4
4
  import { dev } from "./confect/dev";
5
5
  import { ConfectDirectory } from "./ConfectDirectory";
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { NodeContext, NodeRuntime } from "@effect/platform-node";
4
- import { Effect } from "effect";
3
+ import * as NodeContext from "@effect/platform-node/NodeContext";
4
+ import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
5
+ import * as Effect from "effect/Effect";
5
6
  import { cliApp } from "./cliApp";
6
7
 
7
8
  // Track if we received SIGINT so we can re-raise it after cleanup.
package/src/log.ts CHANGED
@@ -1,6 +1,10 @@
1
- import { Path } from "@effect/platform";
2
- import { Ansi, AnsiDoc } from "@effect/printer-ansi";
3
- import { Console, Effect, pipe, String } from "effect";
1
+ import * as Path from "@effect/platform/Path";
2
+ import * as Ansi from "@effect/printer-ansi/Ansi";
3
+ import * as AnsiDoc from "@effect/printer-ansi/AnsiDoc";
4
+ import { pipe } from "effect/Function";
5
+ import * as Console from "effect/Console";
6
+ import * as Effect from "effect/Effect";
7
+ import * as String from "effect/String";
4
8
  import type * as FunctionPath from "./FunctionPath";
5
9
  import * as GroupPath from "./GroupPath";
6
10
  import { ProjectRoot } from "./ProjectRoot";
package/src/templates.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { Array, Effect, Option } from "effect";
1
+ import * as Array from "effect/Array";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Option from "effect/Option";
2
4
  import { CodeBlockWriter } from "./CodeBlockWriter";
3
5
  import {
4
6
  collectImportBindings,
@@ -263,30 +265,14 @@ export const authConfig = ({ authImportPath }: { authImportPath: string }) =>
263
265
  return yield* cbw.toString();
264
266
  });
265
267
 
266
- export const refs = ({
267
- specImportPath,
268
- nodeSpecImportPath,
269
- }: {
270
- specImportPath: string;
271
- nodeSpecImportPath: Option.Option<string>;
272
- }) =>
268
+ export const refs = ({ specImportPath }: { specImportPath: string }) =>
273
269
  Effect.gen(function* () {
274
270
  const cbw = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
275
271
 
276
272
  yield* cbw.writeLine(`import { Refs } from "@confect/core";`);
277
273
  yield* cbw.writeLine(`import spec from "${specImportPath}";`);
278
- yield* Option.match(nodeSpecImportPath, {
279
- onNone: () => Effect.void,
280
- onSome: (nodeSpecImportPath_) =>
281
- cbw.writeLine(`import nodeSpec from "${nodeSpecImportPath_}";`),
282
- });
283
274
  yield* cbw.blankLine();
284
- yield* cbw.writeLine(
285
- Option.match(nodeSpecImportPath, {
286
- onSome: () => `export default Refs.make(spec, nodeSpec);`,
287
- onNone: () => `export default Refs.make(spec);`,
288
- }),
289
- );
275
+ yield* cbw.writeLine(`export default Refs.make(spec);`);
290
276
 
291
277
  return yield* cbw.toString();
292
278
  });
@@ -588,10 +574,8 @@ const writeRootAddAt = (
588
574
 
589
575
  export const assembledSpec = ({
590
576
  nodes,
591
- runtime,
592
577
  }: {
593
578
  nodes: ReadonlyArray<SpecAssemblyNode>;
594
- runtime: "Convex" | "Node";
595
579
  }) =>
596
580
  Effect.gen(function* () {
597
581
  const cbw = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
@@ -615,14 +599,13 @@ export const assembledSpec = ({
615
599
 
616
600
  yield* cbw.blankLine();
617
601
 
618
- const specFactory =
619
- runtime === "Convex" ? "Spec.make()" : "Spec.makeNode()";
620
- const groupFactory =
621
- runtime === "Convex" ? "GroupSpec.makeAt" : "GroupSpec.makeNodeAt";
622
-
623
- yield* cbw.write(`export default ${specFactory}`);
602
+ // The assembled spec is runtime-agnostic: a Node group's `makeNode()` is
603
+ // already baked into its imported leaf spec, so the root is always
604
+ // `Spec.make()` and binding-less container groups always use
605
+ // `GroupSpec.makeAt` (containers register no functions and carry no runtime).
606
+ yield* cbw.write(`export default Spec.make()`);
624
607
  yield* Effect.forEach(nodes, (node) =>
625
- writeRootAddAt(cbw, node, groupFactory),
608
+ writeRootAddAt(cbw, node, "GroupSpec.makeAt"),
626
609
  );
627
610
  yield* cbw.write(";");
628
611
  yield* cbw.newLine();