@confect/cli 9.0.0-next.8 → 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.
@@ -27,7 +27,6 @@ import {
27
27
  registeredFunctionsRelativePath,
28
28
  specPathForImpl,
29
29
  toLeafModule,
30
- toNodeRegistryLeaf,
31
30
  validateImpl,
32
31
  validateSpec,
33
32
  type LeafModule,
@@ -42,7 +41,6 @@ import {
42
41
  } from "../log";
43
42
  import {
44
43
  assemblyNodesFromLeaves,
45
- partitionByRuntime,
46
44
  type SpecAssemblyNode,
47
45
  } from "../SpecAssemblyNode";
48
46
  import * as TableModule from "../TableModule";
@@ -64,9 +62,6 @@ const GENERATED_DIRNAME = "_generated";
64
62
  const GENERATED_SPEC_PATH = Effect.andThen(Path.Path, (path) =>
65
63
  path.join(GENERATED_DIRNAME, "spec.ts"),
66
64
  );
67
- const GENERATED_NODE_SPEC_PATH = Effect.andThen(Path.Path, (path) =>
68
- path.join(GENERATED_DIRNAME, "nodeSpec.ts"),
69
- );
70
65
  const GENERATED_SCHEMA_PATH = Effect.andThen(Path.Path, (path) =>
71
66
  path.join(GENERATED_DIRNAME, "schema.ts"),
72
67
  );
@@ -92,6 +87,9 @@ const LEGACY_PATHS = Effect.gen(function* () {
92
87
  path.join(GENERATED_DIRNAME, "nodeRegisteredFunctions.ts"),
93
88
  path.join(GENERATED_DIRNAME, "impl.ts"),
94
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"),
95
93
  ];
96
94
  });
97
95
 
@@ -198,8 +196,13 @@ const loadAndValidateLeafModules = Effect.gen(function* () {
198
196
 
199
197
  const results = yield* Effect.forEach(specFiles, (specRelativePath) =>
200
198
  Effect.gen(function* () {
201
- const leaf = yield* toLeafModule(specRelativePath);
202
- 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
+ };
203
206
 
204
207
  const implRelativePath = yield* implPathForSpec(specRelativePath);
205
208
  const implAbsolutePath = path.join(confectDirectory, implRelativePath);
@@ -237,15 +240,11 @@ export const validateNoParentChildNameCollisions = (
237
240
  groupSpecsByRelativePath: ReadonlyMap<string, GroupSpec.AnyWithProps>,
238
241
  ) =>
239
242
  Effect.gen(function* () {
240
- const { convex, node } = partitionByRuntime(leaves);
241
- const convexNodes = assemblyNodesFromLeaves(convex);
242
- const nodeNodes = assemblyNodesFromLeaves(
243
- Array.map(node, toNodeRegistryLeaf),
244
- );
245
- yield* Effect.forEach(convexNodes, (n) =>
246
- checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath),
247
- );
248
- 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) =>
249
248
  checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath),
250
249
  );
251
250
  });
@@ -384,34 +383,17 @@ const generateAssembledSpecs = (leaves: ReadonlyArray<LeafModule>) =>
384
383
  const path = yield* Path.Path;
385
384
  const confectDirectory = yield* ConfectDirectory.get;
386
385
  const generatedSpecPath = yield* GENERATED_SPEC_PATH;
387
- const generatedNodeSpecPath = yield* GENERATED_NODE_SPEC_PATH;
388
- const { convex, node } = partitionByRuntime(leaves);
389
-
390
- if (convex.length > 0) {
391
- const nodes = assemblyNodesFromLeaves(convex);
392
- const specContents = yield* templates.assembledSpec({
393
- nodes,
394
- runtime: "Convex",
395
- });
396
- yield* writeFileStringAndLog(
397
- path.join(confectDirectory, generatedSpecPath),
398
- specContents,
399
- );
400
- }
401
386
 
402
- if (node.length > 0) {
403
- const nodes = assemblyNodesFromLeaves(
404
- Array.map(node, toNodeRegistryLeaf),
405
- );
406
- const nodeSpecContents = yield* templates.assembledSpec({
407
- nodes,
408
- runtime: "Node",
409
- });
410
- yield* writeFileStringAndLog(
411
- path.join(confectDirectory, generatedNodeSpecPath),
412
- nodeSpecContents,
413
- );
414
- }
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
+ );
415
397
  });
416
398
 
417
399
  const validateImplModules = (leaves: ReadonlyArray<LeafModule>) =>
@@ -459,12 +441,23 @@ const generateGroupRegisteredFunctions = (leaves: ReadonlyArray<LeafModule>) =>
459
441
  ),
460
442
  );
461
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
+
462
455
  const contents = yield* templates.registeredFunctionsForGroup({
463
456
  schemaImportPath,
464
457
  specImportPath,
465
458
  implImportPath,
466
459
  layerExportName: leaf.exportName,
467
- useNode: leaf.runtime === "Node",
460
+ useNode: runtime === "Node",
468
461
  });
469
462
 
470
463
  yield* writeFileStringAndLog(registryPath, contents);
@@ -519,47 +512,20 @@ const getGeneratedSpecPath = Effect.gen(function* () {
519
512
  return path.join(confectDirectory, generatedSpecPath);
520
513
  });
521
514
 
522
- const getGeneratedNodeSpecPath = Effect.gen(function* () {
523
- const path = yield* Path.Path;
524
- const confectDirectory = yield* ConfectDirectory.get;
525
- const generatedNodeSpecPath = yield* GENERATED_NODE_SPEC_PATH;
526
- return path.join(confectDirectory, generatedNodeSpecPath);
527
- });
528
-
529
515
  const loadGeneratedSpec = Effect.gen(function* () {
530
516
  const specPath = yield* getGeneratedSpecPath;
531
517
  const { module: specModule } = yield* Bundler.bundle(specPath);
532
518
  const spec = specModule.default;
533
519
 
534
- if (!Spec.isConvexSpec(spec)) {
520
+ if (!Spec.isSpec(spec)) {
535
521
  return yield* Effect.dieMessage(
536
- "_generated/spec.ts does not export a valid Convex Spec",
522
+ "_generated/spec.ts does not export a valid Spec",
537
523
  );
538
524
  }
539
525
 
540
526
  return spec;
541
527
  });
542
528
 
543
- const loadGeneratedNodeSpec = Effect.gen(function* () {
544
- const fs = yield* FileSystem.FileSystem;
545
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
546
-
547
- if (!(yield* fs.exists(nodeSpecPath))) {
548
- return Option.none<Spec.AnyWithPropsWithRuntime<"Node">>();
549
- }
550
-
551
- const { module: nodeSpecModule } = yield* Bundler.bundle(nodeSpecPath);
552
- const nodeSpec = nodeSpecModule.default;
553
-
554
- if (!Spec.isNodeSpec(nodeSpec)) {
555
- return yield* Effect.dieMessage(
556
- "_generated/nodeSpec.ts does not export a valid Node Spec",
557
- );
558
- }
559
-
560
- return Option.some(nodeSpec);
561
- });
562
-
563
529
  const emptyFunctionPaths = FunctionPaths.FunctionPaths.make(HashSet.empty());
564
530
 
565
531
  export const loadPreviousFunctionPaths = Effect.gen(function* () {
@@ -572,17 +538,9 @@ export const loadPreviousFunctionPaths = Effect.gen(function* () {
572
538
 
573
539
  const specEither = yield* loadGeneratedSpec.pipe(Effect.either);
574
540
 
575
- return yield* Either.match(specEither, {
576
- onLeft: () => Effect.succeed(emptyFunctionPaths),
577
- onRight: (spec) =>
578
- Effect.gen(function* () {
579
- const nodeSpecOption = yield* loadGeneratedNodeSpec;
580
- const mergedSpec = Option.match(nodeSpecOption, {
581
- onNone: () => spec,
582
- onSome: (nodeSpec) => Spec.merge(spec, nodeSpec),
583
- });
584
- return FunctionPaths.make(mergedSpec);
585
- }),
541
+ return Either.match(specEither, {
542
+ onLeft: () => emptyFunctionPaths,
543
+ onRight: (spec) => FunctionPaths.make(spec),
586
544
  });
587
545
  });
588
546
 
@@ -613,14 +571,7 @@ const removeGeneratedNodeApi = removeObsoleteGeneratedFile("nodeApi.ts");
613
571
 
614
572
  const generateFunctionModules = Effect.gen(function* () {
615
573
  const spec = yield* loadGeneratedSpec;
616
- const nodeSpecOption = yield* loadGeneratedNodeSpec;
617
-
618
- const mergedSpec = Option.match(nodeSpecOption, {
619
- onNone: () => spec,
620
- onSome: (nodeSpec) => Spec.merge(spec, nodeSpec),
621
- });
622
-
623
- return yield* generateFunctions(mergedSpec);
574
+ return yield* generateFunctions(spec);
624
575
  });
625
576
 
626
577
  /**
@@ -862,7 +813,6 @@ const generateServices = Effect.gen(function* () {
862
813
  });
863
814
 
864
815
  const generateRefs = Effect.gen(function* () {
865
- const fs = yield* FileSystem.FileSystem;
866
816
  const path = yield* Path.Path;
867
817
  const confectDirectory = yield* ConfectDirectory.get;
868
818
 
@@ -875,18 +825,7 @@ const generateRefs = Effect.gen(function* () {
875
825
  path.relative(refsDir, path.join(confectDirectory, generatedSpecPath)),
876
826
  );
877
827
 
878
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
879
- const nodeSpecExists = yield* fs.exists(nodeSpecPath);
880
- const nodeSpecImportPath = nodeSpecExists
881
- ? Option.some(
882
- yield* toModuleImportPath(path.relative(refsDir, nodeSpecPath)),
883
- )
884
- : Option.none<string>();
885
-
886
- const refsContents = yield* templates.refs({
887
- specImportPath,
888
- nodeSpecImportPath,
889
- });
828
+ const refsContents = yield* templates.refs({ specImportPath });
890
829
 
891
830
  yield* writeFileStringAndLog(refsPath, refsContents);
892
831
  });
@@ -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/templates.ts CHANGED
@@ -265,30 +265,14 @@ export const authConfig = ({ authImportPath }: { authImportPath: string }) =>
265
265
  return yield* cbw.toString();
266
266
  });
267
267
 
268
- export const refs = ({
269
- specImportPath,
270
- nodeSpecImportPath,
271
- }: {
272
- specImportPath: string;
273
- nodeSpecImportPath: Option.Option<string>;
274
- }) =>
268
+ export const refs = ({ specImportPath }: { specImportPath: string }) =>
275
269
  Effect.gen(function* () {
276
270
  const cbw = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
277
271
 
278
272
  yield* cbw.writeLine(`import { Refs } from "@confect/core";`);
279
273
  yield* cbw.writeLine(`import spec from "${specImportPath}";`);
280
- yield* Option.match(nodeSpecImportPath, {
281
- onNone: () => Effect.void,
282
- onSome: (nodeSpecImportPath_) =>
283
- cbw.writeLine(`import nodeSpec from "${nodeSpecImportPath_}";`),
284
- });
285
274
  yield* cbw.blankLine();
286
- yield* cbw.writeLine(
287
- Option.match(nodeSpecImportPath, {
288
- onSome: () => `export default Refs.make(spec, nodeSpec);`,
289
- onNone: () => `export default Refs.make(spec);`,
290
- }),
291
- );
275
+ yield* cbw.writeLine(`export default Refs.make(spec);`);
292
276
 
293
277
  return yield* cbw.toString();
294
278
  });
@@ -590,10 +574,8 @@ const writeRootAddAt = (
590
574
 
591
575
  export const assembledSpec = ({
592
576
  nodes,
593
- runtime,
594
577
  }: {
595
578
  nodes: ReadonlyArray<SpecAssemblyNode>;
596
- runtime: "Convex" | "Node";
597
579
  }) =>
598
580
  Effect.gen(function* () {
599
581
  const cbw = new CodeBlockWriter({ indentNumberOfSpaces: 2 });
@@ -617,14 +599,13 @@ export const assembledSpec = ({
617
599
 
618
600
  yield* cbw.blankLine();
619
601
 
620
- const specFactory =
621
- runtime === "Convex" ? "Spec.make()" : "Spec.makeNode()";
622
- const groupFactory =
623
- runtime === "Convex" ? "GroupSpec.makeAt" : "GroupSpec.makeNodeAt";
624
-
625
- 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()`);
626
607
  yield* Effect.forEach(nodes, (node) =>
627
- writeRootAddAt(cbw, node, groupFactory),
608
+ writeRootAddAt(cbw, node, "GroupSpec.makeAt"),
628
609
  );
629
610
  yield* cbw.write(";");
630
611
  yield* cbw.newLine();
package/src/utils.ts CHANGED
@@ -196,6 +196,35 @@ export const generateGroupModule = ({
196
196
  return "Unchanged" as const;
197
197
  });
198
198
 
199
+ /**
200
+ * Compute the module import specifier (relative to `modulePath`) for a group's
201
+ * registry file under `confect/_generated/registeredFunctions/`. The registry
202
+ * path mirrors the group's path one-to-one (see `registeredFunctionsRelativePath`
203
+ * in `LeafModule.ts`) for both Convex and Node groups. Centralizing this here
204
+ * keeps the "overlapping" and "new" group branches of `generateFunctions` from
205
+ * drifting apart.
206
+ */
207
+ const registeredFunctionsImportPathForGroup = (
208
+ groupPath: GroupPath.GroupPath,
209
+ modulePath: string,
210
+ ) =>
211
+ Effect.gen(function* () {
212
+ const path = yield* Path.Path;
213
+ const confectDirectory = yield* ConfectDirectory.get;
214
+
215
+ const registeredFunctionsPath =
216
+ path.join(
217
+ confectDirectory,
218
+ "_generated",
219
+ "registeredFunctions",
220
+ ...groupPath.pathSegments,
221
+ ) + ".ts";
222
+
223
+ return yield* toModuleImportPath(
224
+ path.relative(path.dirname(modulePath), registeredFunctionsPath),
225
+ );
226
+ });
227
+
199
228
  const logGroupPaths = <R>(
200
229
  groupPaths: GroupPaths.GroupPaths,
201
230
  logFn: (fullPath: string) => Effect.Effect<void, never, R>,
@@ -216,7 +245,6 @@ export const generateFunctions = (spec: Spec.AnyWithProps) =>
216
245
  Effect.gen(function* () {
217
246
  const path = yield* Path.Path;
218
247
  const convexDirectory = yield* ConvexDirectory.get;
219
- const confectDirectory = yield* ConfectDirectory.get;
220
248
 
221
249
  const groupPathsFromFs = yield* getGroupPathsFromFs;
222
250
  const functionPaths = FunctionPaths.make(spec);
@@ -241,25 +269,13 @@ export const generateFunctions = (spec: Spec.AnyWithProps) =>
241
269
  );
242
270
  const relativeModulePath = yield* GroupPath.modulePath(groupPath);
243
271
  const modulePath = path.join(convexDirectory, relativeModulePath);
244
- const registrySegments =
245
- groupPath.pathSegments[0] === "node"
246
- ? groupPath.pathSegments.slice(1)
247
- : groupPath.pathSegments;
248
- const registeredFunctionsPath =
249
- path.join(
250
- confectDirectory,
251
- "_generated",
252
- "registeredFunctions",
253
- ...registrySegments,
254
- ) + ".ts";
255
- const registeredFunctionsImportPath = yield* toModuleImportPath(
256
- path.relative(path.dirname(modulePath), registeredFunctionsPath),
257
- );
272
+ const registeredFunctionsImportPath =
273
+ yield* registeredFunctionsImportPathForGroup(groupPath, modulePath);
258
274
  const result = yield* generateGroupModule({
259
275
  groupPath,
260
276
  functionNames,
261
277
  registeredFunctionsImportPath,
262
- useNode: groupPath.pathSegments[0] === "node",
278
+ useNode: group.runtime === "Node",
263
279
  });
264
280
  if (result === "Modified") {
265
281
  yield* logFileModified(modulePath);
@@ -342,7 +358,6 @@ export const writeGroups = (
342
358
  Effect.gen(function* () {
343
359
  const path = yield* Path.Path;
344
360
  const convexDirectory = yield* ConvexDirectory.get;
345
- const confectDirectory = yield* ConfectDirectory.get;
346
361
  const group = yield* GroupPath.getGroupSpec(spec, groupPath);
347
362
 
348
363
  const functionNames = pipe(
@@ -359,23 +374,15 @@ export const writeGroups = (
359
374
 
360
375
  const relativeModulePath = yield* GroupPath.modulePath(groupPath);
361
376
  const modulePath = path.join(convexDirectory, relativeModulePath);
362
- const registeredFunctionsPath =
363
- path.join(
364
- confectDirectory,
365
- "_generated",
366
- "registeredFunctions",
367
- ...groupPath.pathSegments,
368
- ) + ".ts";
369
- const registeredFunctionsImportPath = yield* toModuleImportPath(
370
- path.relative(path.dirname(modulePath), registeredFunctionsPath),
371
- );
377
+ const registeredFunctionsImportPath =
378
+ yield* registeredFunctionsImportPathForGroup(groupPath, modulePath);
372
379
 
373
380
  yield* Effect.logDebug(`Generating group ${groupPath}...`);
374
381
  yield* generateGroupModule({
375
382
  groupPath,
376
383
  functionNames,
377
384
  registeredFunctionsImportPath,
378
- useNode: groupPath.pathSegments[0] === "node",
385
+ useNode: group.runtime === "Node",
379
386
  });
380
387
  yield* Effect.logDebug(`Group ${groupPath} generated`);
381
388
  }),