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

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.
@@ -1,27 +1,18 @@
1
- import { type GroupSpec, Spec } from "@confect/core";
2
- import * as DatabaseSchema from "@confect/server/DatabaseSchema";
1
+ import { Spec, type GroupSpec } from "@confect/core";
3
2
  import { Command } from "@effect/cli";
4
3
  import { FileSystem, Path } from "@effect/platform";
5
4
  import { Array, Effect, Either, HashSet, Match, Option, Ref } from "effect";
6
- import { fromBundlerError } from "../BuildError";
5
+ import * as Bundler from "../Bundler";
7
6
  import * as CodegenError from "../CodegenError";
8
7
  import {
8
+ LegacySchemaFileError,
9
9
  MissingImplFileError,
10
- MissingSchemaFileError,
11
10
  MissingSpecFileError,
12
11
  ParentChildNameCollisionError,
13
- SchemaInvalidDefaultExportError,
14
12
  } from "../CodegenError";
15
13
  import { ConfectDirectory } from "../ConfectDirectory";
16
14
  import { ConvexDirectory } from "../ConvexDirectory";
17
15
  import * as FunctionPaths from "../FunctionPaths";
18
- import {
19
- logFileAdded,
20
- logFileModified,
21
- logFileRemoved,
22
- logPending,
23
- logSuccess,
24
- } from "../log";
25
16
  import {
26
17
  discoverLeafImplFiles,
27
18
  discoverLeafSpecFiles,
@@ -34,19 +25,26 @@ import {
34
25
  validateSpec,
35
26
  type LeafModule,
36
27
  } from "../LeafModule";
28
+ import {
29
+ logFileAdded,
30
+ logFileModified,
31
+ logFileRemoved,
32
+ logPending,
33
+ logSuccess,
34
+ logWarn,
35
+ } from "../log";
37
36
  import {
38
37
  assemblyNodesFromLeaves,
39
38
  partitionByRuntime,
40
39
  type SpecAssemblyNode,
41
40
  } from "../SpecAssemblyNode";
41
+ import * as TableModule from "../TableModule";
42
42
  import * as templates from "../templates";
43
- import * as Bundler from "../Bundler";
44
43
  import {
45
44
  generateAuthConfig,
46
45
  generateCrons,
47
46
  generateFunctions,
48
47
  generateHttp,
49
- removePathExtension,
50
48
  removePathIfExists,
51
49
  toModuleImportPath,
52
50
  touchConvexSchema,
@@ -54,21 +52,41 @@ import {
54
52
  WriteTracker,
55
53
  } from "../utils";
56
54
 
57
- const GENERATED_SPEC_PATH = "_generated/spec.ts";
58
- const GENERATED_NODE_SPEC_PATH = "_generated/nodeSpec.ts";
59
-
60
- const LEGACY_PATHS = [
61
- "spec.ts",
62
- "nodeSpec.ts",
63
- "impl.ts",
64
- "nodeImpl.ts",
65
- "notesAndRandom.impl.ts",
66
- "groups.impl.ts",
67
- "_generated/registeredFunctions.ts",
68
- "_generated/nodeRegisteredFunctions.ts",
69
- "_generated/impl.ts",
70
- "_generated/nodeImpl.ts",
71
- ];
55
+ const GENERATED_DIRNAME = "_generated";
56
+
57
+ const GENERATED_SPEC_PATH = Effect.andThen(Path.Path, (path) =>
58
+ path.join(GENERATED_DIRNAME, "spec.ts"),
59
+ );
60
+ const GENERATED_NODE_SPEC_PATH = Effect.andThen(Path.Path, (path) =>
61
+ path.join(GENERATED_DIRNAME, "nodeSpec.ts"),
62
+ );
63
+ const GENERATED_SCHEMA_PATH = Effect.andThen(Path.Path, (path) =>
64
+ path.join(GENERATED_DIRNAME, "schema.ts"),
65
+ );
66
+ const GENERATED_CONVEX_SCHEMA_PATH = Effect.andThen(Path.Path, (path) =>
67
+ path.join(GENERATED_DIRNAME, "convexSchema.ts"),
68
+ );
69
+ const GENERATED_ID_PATH = Effect.andThen(Path.Path, (path) =>
70
+ path.join(GENERATED_DIRNAME, "id.ts"),
71
+ );
72
+ const GENERATED_TABLES_DIRNAME = Effect.andThen(Path.Path, (path) =>
73
+ path.join(GENERATED_DIRNAME, "tables"),
74
+ );
75
+
76
+ const LEGACY_PATHS = Effect.gen(function* () {
77
+ const path = yield* Path.Path;
78
+
79
+ return [
80
+ "spec.ts",
81
+ "nodeSpec.ts",
82
+ "impl.ts",
83
+ "nodeImpl.ts",
84
+ path.join(GENERATED_DIRNAME, "registeredFunctions.ts"),
85
+ path.join(GENERATED_DIRNAME, "nodeRegisteredFunctions.ts"),
86
+ path.join(GENERATED_DIRNAME, "impl.ts"),
87
+ path.join(GENERATED_DIRNAME, "nodeImpl.ts"),
88
+ ];
89
+ });
72
90
 
73
91
  export const codegen = Command.make("codegen", {}, () =>
74
92
  Effect.gen(function* () {
@@ -98,27 +116,50 @@ export const codegenHandler = Effect.gen(function* () {
98
116
 
99
117
  const runCodegen = Effect.gen(function* () {
100
118
  yield* generateConfectGeneratedDirectory;
101
- // Validate schema first so its missing-file / invalid-default-export
102
- // diagnostics surface ahead of impl bundling, which transitively depends
103
- // on schema via `_generated/api.ts` and would otherwise blow up with a
104
- // less actionable bundler error.
105
- yield* validateSchema;
119
+ // Reject a legacy `confect/schema.ts` up front so the user-facing
120
+ // migration message surfaces before any bundler error from impl
121
+ // validation (each impl imports `_generated/schema.ts`).
122
+ yield* rejectLegacySchemaFile;
123
+ // List `confect/tables/*.ts` (filename-only — no bundling yet) so the
124
+ // `_generated/id.ts` constructor can be emitted *before* we bundle any
125
+ // user-authored table module. Tables import from `_generated/id.ts` for
126
+ // cross-table id refs, so it must exist on disk first.
127
+ const tableModules = yield* TableModule.discover;
128
+ yield* warnIfNoTables(tableModules);
129
+ yield* generateIdConstructor(tableModules);
130
+ // Now that `_generated/id.ts` is on disk, bundle each table module and
131
+ // check its default export is an `UnnamedTable`. Surface diagnostics
132
+ // here (rather than later) so they appear before impl-validation noise.
133
+ yield* TableModule.validate(tableModules);
134
+ yield* generateTableWrappers(tableModules);
135
+ yield* removeObsoleteTableWrappers(tableModules);
136
+ yield* generateRuntimeSchema(tableModules);
106
137
  const { leaves, groupSpecsByRelativePath } =
107
138
  yield* loadAndValidateLeafModules;
108
139
  yield* removeLegacyFiles;
109
140
  yield* validateNoParentChildNameCollisions(leaves, groupSpecsByRelativePath);
110
141
  yield* generateAssembledSpecs(leaves);
111
- yield* validateImplModules(leaves);
112
- yield* generateGroupRegisteredFunctions(leaves);
113
- yield* removeObsoleteRegisteredFunctions(leaves);
142
+ // `_generated/api.ts` / `nodeApi.ts` are no longer imported by generated or
143
+ // impl code (impls take the database schema from `_generated/schema`
144
+ // directly), so remove any copies left over from earlier versions before
145
+ // impl validation runs.
114
146
  yield* Effect.all(
115
- [generateApi, generateRefs, generateNodeApi, generateServices],
147
+ [
148
+ removeGeneratedApi,
149
+ generateRefs,
150
+ removeGeneratedNodeApi,
151
+ generateServices,
152
+ generateConvexSchema(tableModules),
153
+ ],
116
154
  { concurrency: "unbounded" },
117
155
  );
156
+ yield* validateImplModules(leaves);
157
+ yield* generateGroupRegisteredFunctions(leaves);
158
+ yield* removeObsoleteRegisteredFunctions(leaves);
118
159
  const [functionPaths] = yield* Effect.all(
119
160
  [
120
161
  generateFunctionModules,
121
- generateSchema,
162
+ generateConvexSchemaReexport,
122
163
  logGenerated(generateHttp),
123
164
  logGenerated(generateCrons),
124
165
  logGenerated(generateAuthConfig),
@@ -318,8 +359,9 @@ const removeLegacyFiles = Effect.gen(function* () {
318
359
  const fs = yield* FileSystem.FileSystem;
319
360
  const path = yield* Path.Path;
320
361
  const confectDirectory = yield* ConfectDirectory.get;
362
+ const legacyPaths = yield* LEGACY_PATHS;
321
363
 
322
- yield* Effect.forEach(LEGACY_PATHS, (relativePath) =>
364
+ yield* Effect.forEach(legacyPaths, (relativePath) =>
323
365
  Effect.gen(function* () {
324
366
  const absolutePath = path.join(confectDirectory, relativePath);
325
367
  if (yield* fs.exists(absolutePath)) {
@@ -334,6 +376,8 @@ const generateAssembledSpecs = (leaves: ReadonlyArray<LeafModule>) =>
334
376
  Effect.gen(function* () {
335
377
  const path = yield* Path.Path;
336
378
  const confectDirectory = yield* ConfectDirectory.get;
379
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
380
+ const generatedNodeSpecPath = yield* GENERATED_NODE_SPEC_PATH;
337
381
  const { convex, node } = partitionByRuntime(leaves);
338
382
 
339
383
  if (convex.length > 0) {
@@ -343,7 +387,7 @@ const generateAssembledSpecs = (leaves: ReadonlyArray<LeafModule>) =>
343
387
  runtime: "Convex",
344
388
  });
345
389
  yield* writeFileStringAndLog(
346
- path.join(confectDirectory, GENERATED_SPEC_PATH),
390
+ path.join(confectDirectory, generatedSpecPath),
347
391
  specContents,
348
392
  );
349
393
  }
@@ -357,7 +401,7 @@ const generateAssembledSpecs = (leaves: ReadonlyArray<LeafModule>) =>
357
401
  runtime: "Node",
358
402
  });
359
403
  yield* writeFileStringAndLog(
360
- path.join(confectDirectory, GENERATED_NODE_SPEC_PATH),
404
+ path.join(confectDirectory, generatedNodeSpecPath),
361
405
  nodeSpecContents,
362
406
  );
363
407
  }
@@ -387,11 +431,18 @@ const generateGroupRegisteredFunctions = (leaves: ReadonlyArray<LeafModule>) =>
387
431
  }
388
432
 
389
433
  const implRelativePath = yield* implPathForSpec(leaf.relativePath);
390
- const apiFileName = leaf.runtime === "Node" ? "nodeApi.ts" : "api.ts";
391
- const apiImportPath = yield* toModuleImportPath(
434
+ const schemaImportPath = yield* toModuleImportPath(
435
+ path.relative(
436
+ path.dirname(registryPath),
437
+ path.join(confectDirectory, "_generated", "schema.ts"),
438
+ ),
439
+ );
440
+ // The group's own leaf spec (sibling of its impl), referenced
441
+ // type-only by the registry to shape its returned record.
442
+ const specImportPath = yield* toModuleImportPath(
392
443
  path.relative(
393
444
  path.dirname(registryPath),
394
- path.join(confectDirectory, "_generated", apiFileName),
445
+ path.join(confectDirectory, leaf.relativePath),
395
446
  ),
396
447
  );
397
448
  const implImportPath = yield* toModuleImportPath(
@@ -402,8 +453,8 @@ const generateGroupRegisteredFunctions = (leaves: ReadonlyArray<LeafModule>) =>
402
453
  );
403
454
 
404
455
  const contents = yield* templates.registeredFunctionsForGroup({
405
- apiImportPath,
406
- groupPathDot: leaf.registryGroupPathDot,
456
+ schemaImportPath,
457
+ specImportPath,
407
458
  implImportPath,
408
459
  layerExportName: leaf.exportName,
409
460
  useNode: leaf.runtime === "Node",
@@ -457,13 +508,15 @@ const removeObsoleteRegisteredFunctions = (leaves: ReadonlyArray<LeafModule>) =>
457
508
  const getGeneratedSpecPath = Effect.gen(function* () {
458
509
  const path = yield* Path.Path;
459
510
  const confectDirectory = yield* ConfectDirectory.get;
460
- return path.join(confectDirectory, GENERATED_SPEC_PATH);
511
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
512
+ return path.join(confectDirectory, generatedSpecPath);
461
513
  });
462
514
 
463
515
  const getGeneratedNodeSpecPath = Effect.gen(function* () {
464
516
  const path = yield* Path.Path;
465
517
  const confectDirectory = yield* ConfectDirectory.get;
466
- return path.join(confectDirectory, GENERATED_NODE_SPEC_PATH);
518
+ const generatedNodeSpecPath = yield* GENERATED_NODE_SPEC_PATH;
519
+ return path.join(confectDirectory, generatedNodeSpecPath);
467
520
  });
468
521
 
469
522
  const loadGeneratedSpec = Effect.gen(function* () {
@@ -526,120 +579,254 @@ export const loadPreviousFunctionPaths = Effect.gen(function* () {
526
579
  });
527
580
  });
528
581
 
529
- const generateApi = Effect.gen(function* () {
530
- const path = yield* Path.Path;
531
- const confectDirectory = yield* ConfectDirectory.get;
582
+ /**
583
+ * Remove a now-obsolete `_generated/<name>.ts` if present (and log it), for
584
+ * projects upgrading from a version that still emitted it.
585
+ */
586
+ const removeObsoleteGeneratedFile = (fileName: string) =>
587
+ Effect.gen(function* () {
588
+ const fs = yield* FileSystem.FileSystem;
589
+ const path = yield* Path.Path;
590
+ const confectDirectory = yield* ConfectDirectory.get;
591
+ const filePath = path.join(confectDirectory, "_generated", fileName);
532
592
 
533
- const apiPath = path.join(confectDirectory, "_generated", "api.ts");
534
- const apiDir = path.dirname(apiPath);
593
+ if (yield* fs.exists(filePath)) {
594
+ yield* removePathIfExists(filePath);
595
+ yield* logFileRemoved(filePath);
596
+ }
597
+ });
535
598
 
536
- const schemaImportPath = yield* toModuleImportPath(
537
- path.relative(apiDir, path.join(confectDirectory, "schema.ts")),
538
- );
599
+ // `_generated/api.ts` is no longer imported by generated or impl code: impls
600
+ // take the database schema (`_generated/schema`) directly, and per-group
601
+ // registries reference the spec type-only. Remove any stale copy.
602
+ const removeGeneratedApi = removeObsoleteGeneratedFile("api.ts");
539
603
 
540
- const specImportPath = yield* toModuleImportPath(
541
- path.relative(apiDir, path.join(confectDirectory, GENERATED_SPEC_PATH)),
542
- );
604
+ // `_generated/nodeApi.ts` is obsolete for the same reason.
605
+ const removeGeneratedNodeApi = removeObsoleteGeneratedFile("nodeApi.ts");
543
606
 
544
- const apiContents = yield* templates.api({
545
- schemaImportPath,
546
- specImportPath,
607
+ const generateFunctionModules = Effect.gen(function* () {
608
+ 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),
547
614
  });
548
615
 
549
- yield* writeFileStringAndLog(apiPath, apiContents);
616
+ return yield* generateFunctions(mergedSpec);
550
617
  });
551
618
 
552
- export const generateNodeApi = Effect.gen(function* () {
619
+ /**
620
+ * The user-authored `confect/schema.ts` is no longer supported: codegen now
621
+ * owns both `_generated/schema.ts` (runtime) and `_generated/convexSchema.ts`
622
+ * (deploy), derived from a single scan of `confect/tables/*.ts`. Detect a
623
+ * stray file and fail with a clear migration message — leaving it in place
624
+ * would silently shadow the codegen-owned `_generated/schema.ts` /
625
+ * `_generated/convexSchema.ts`.
626
+ */
627
+ const rejectLegacySchemaFile = Effect.gen(function* () {
553
628
  const fs = yield* FileSystem.FileSystem;
554
629
  const path = yield* Path.Path;
555
630
  const confectDirectory = yield* ConfectDirectory.get;
631
+ const legacyPath = path.join(confectDirectory, "schema.ts");
556
632
 
557
- const nodeSpecPath = yield* getGeneratedNodeSpecPath;
558
- const nodeApiPath = path.join(confectDirectory, "_generated", "nodeApi.ts");
559
-
560
- if (!(yield* fs.exists(nodeSpecPath))) {
561
- if (yield* fs.exists(nodeApiPath)) {
562
- yield* removePathIfExists(nodeApiPath);
563
- yield* logFileRemoved(nodeApiPath);
564
- }
565
- return;
633
+ if (yield* fs.exists(legacyPath)) {
634
+ return yield* new LegacySchemaFileError({ schemaPath: "schema.ts" });
566
635
  }
636
+ });
567
637
 
568
- const nodeApiDir = path.dirname(nodeApiPath);
638
+ /**
639
+ * Surface a yellow `⚠` warning when codegen sees no tables — either the
640
+ * `confect/tables/` directory is missing or it contains no `.ts` files.
641
+ * Generation still succeeds (emitting an empty `DatabaseSchema` and
642
+ * `defineSchema({})`), since action-only / table-free Confect backends
643
+ * are legal — but the warning catches the much more common case of a
644
+ * typoed directory or files placed under the wrong root.
645
+ */
646
+ const warnIfNoTables = (
647
+ tableModules: ReadonlyArray<TableModule.TableModule>,
648
+ ) =>
649
+ tableModules.length === 0
650
+ ? logWarn(
651
+ `No tables discovered in \`confect/${TableModule.TABLES_DIRNAME}/\`. ` +
652
+ `Generating an empty schema; add a \`Table.make(...)\` module under that ` +
653
+ `directory unless this backend is intentionally tables-free.`,
654
+ )
655
+ : Effect.void;
569
656
 
570
- const schemaImportPath = yield* toModuleImportPath(
571
- path.relative(nodeApiDir, path.join(confectDirectory, "schema.ts")),
572
- );
657
+ const tableModuleBindings = (
658
+ tableModules: ReadonlyArray<TableModule.TableModule>,
659
+ generatedFilePath: string,
660
+ ) =>
661
+ Effect.gen(function* () {
662
+ const path = yield* Path.Path;
663
+ const confectDirectory = yield* ConfectDirectory.get;
664
+ const generatedDir = path.dirname(generatedFilePath);
573
665
 
574
- const nodeSpecImportPath = yield* toModuleImportPath(
575
- path.relative(nodeApiDir, nodeSpecPath),
576
- );
666
+ const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
577
667
 
578
- const nodeApiContents = yield* templates.nodeApi({
579
- schemaImportPath,
580
- nodeSpecImportPath,
668
+ return yield* Effect.forEach(tableModules, (tm) =>
669
+ Effect.gen(function* () {
670
+ const wrapperAbsolutePath = path.join(
671
+ confectDirectory,
672
+ generatedTablesDirname,
673
+ `${tm.tableName}.ts`,
674
+ );
675
+ const importPath = yield* toModuleImportPath(
676
+ path.relative(generatedDir, wrapperAbsolutePath),
677
+ );
678
+ return {
679
+ importPath,
680
+ tableName: tm.tableName,
681
+ };
682
+ }),
683
+ );
581
684
  });
582
685
 
583
- yield* writeFileStringAndLog(nodeApiPath, nodeApiContents);
584
- });
686
+ const generateIdConstructor = (
687
+ tableModules: ReadonlyArray<TableModule.TableModule>,
688
+ ) =>
689
+ Effect.gen(function* () {
690
+ const path = yield* Path.Path;
691
+ const confectDirectory = yield* ConfectDirectory.get;
692
+ const generatedIdPath = yield* GENERATED_ID_PATH;
693
+ const idPath = path.join(confectDirectory, generatedIdPath);
585
694
 
586
- const generateFunctionModules = Effect.gen(function* () {
587
- const spec = yield* loadGeneratedSpec;
588
- const nodeSpecOption = yield* loadGeneratedNodeSpec;
695
+ const tableNames = tableModules.map((tm) => tm.tableName);
696
+ const contents = yield* templates.id({ tableNames });
589
697
 
590
- const mergedSpec = Option.match(nodeSpecOption, {
591
- onNone: () => spec,
592
- onSome: (nodeSpec) => Spec.merge(spec, nodeSpec),
698
+ yield* writeFileStringAndLog(idPath, contents);
593
699
  });
594
700
 
595
- return yield* generateFunctions(mergedSpec);
596
- });
597
-
598
- export const validateSchema = Effect.gen(function* () {
599
- const fs = yield* FileSystem.FileSystem;
600
- const path = yield* Path.Path;
601
- const confectDirectory = yield* ConfectDirectory.get;
602
- const confectSchemaPath = path.join(confectDirectory, "schema.ts");
701
+ const generateTableWrappers = (
702
+ tableModules: ReadonlyArray<TableModule.TableModule>,
703
+ ) =>
704
+ Effect.gen(function* () {
705
+ const fs = yield* FileSystem.FileSystem;
706
+ const path = yield* Path.Path;
707
+ const confectDirectory = yield* ConfectDirectory.get;
708
+ const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
709
+ const wrappersDir = path.join(confectDirectory, generatedTablesDirname);
603
710
 
604
- if (!(yield* fs.exists(confectSchemaPath))) {
605
- return yield* new MissingSchemaFileError({ schemaPath: "schema.ts" });
606
- }
711
+ if (!(yield* fs.exists(wrappersDir))) {
712
+ yield* fs.makeDirectory(wrappersDir, { recursive: true });
713
+ }
607
714
 
608
- yield* Bundler.bundle(confectSchemaPath).pipe(
609
- Effect.mapError((error) => fromBundlerError("schema.ts", error)),
610
- Effect.andThen(({ module: schemaModule }) => {
611
- const defaultExport = schemaModule.default;
612
-
613
- return DatabaseSchema.isDatabaseSchema(defaultExport)
614
- ? Effect.succeed(defaultExport)
615
- : Effect.fail(
616
- new SchemaInvalidDefaultExportError({
617
- schemaPath: "schema.ts",
618
- }),
715
+ yield* Effect.forEach(
716
+ tableModules,
717
+ (tm) =>
718
+ Effect.gen(function* () {
719
+ const wrapperPath = path.join(
720
+ confectDirectory,
721
+ generatedTablesDirname,
722
+ `${tm.tableName}.ts`,
619
723
  );
620
- }),
621
- );
622
- });
724
+ const unnamedAbsolutePath = path.join(
725
+ confectDirectory,
726
+ tm.relativePath,
727
+ );
728
+ const unnamedImportPath = yield* toModuleImportPath(
729
+ path.relative(path.dirname(wrapperPath), unnamedAbsolutePath),
730
+ );
731
+ const contents = yield* templates.tableWrapper({
732
+ tableName: tm.tableName,
733
+ unnamedImportPath,
734
+ });
735
+ yield* writeFileStringAndLog(wrapperPath, contents);
736
+ }),
737
+ { concurrency: "unbounded" },
738
+ );
739
+ });
623
740
 
624
- const generateSchema = Effect.gen(function* () {
741
+ /**
742
+ * Remove any stale `_generated/tables/*.ts` wrapper whose source table
743
+ * has been deleted or renamed. Mirrors `removeObsoleteRegisteredFunctions`
744
+ * for the wrapper directory.
745
+ */
746
+ const removeObsoleteTableWrappers = (
747
+ tableModules: ReadonlyArray<TableModule.TableModule>,
748
+ ) =>
749
+ Effect.gen(function* () {
750
+ const fs = yield* FileSystem.FileSystem;
751
+ const path = yield* Path.Path;
752
+ const confectDirectory = yield* ConfectDirectory.get;
753
+ const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
754
+ const wrappersDir = path.join(confectDirectory, generatedTablesDirname);
755
+
756
+ if (!(yield* fs.exists(wrappersDir))) {
757
+ return;
758
+ }
759
+
760
+ const expected = new Set(tableModules.map((tm) => `${tm.tableName}.ts`));
761
+ const existing = yield* fs.readDirectory(wrappersDir, { recursive: true });
762
+ yield* Effect.forEach(existing, (entry) => {
763
+ if (path.extname(entry) !== ".ts") {
764
+ return Effect.void;
765
+ }
766
+ if (!expected.has(entry)) {
767
+ return Effect.gen(function* () {
768
+ const absolutePath = path.join(wrappersDir, entry);
769
+ if (yield* fs.exists(absolutePath)) {
770
+ yield* removePathIfExists(absolutePath);
771
+ yield* logFileRemoved(absolutePath);
772
+ }
773
+ });
774
+ }
775
+ return Effect.void;
776
+ });
777
+ });
778
+
779
+ const generateRuntimeSchema = (
780
+ tableModules: ReadonlyArray<TableModule.TableModule>,
781
+ ) =>
782
+ Effect.gen(function* () {
783
+ const path = yield* Path.Path;
784
+ const confectDirectory = yield* ConfectDirectory.get;
785
+ const generatedSchemaPath = yield* GENERATED_SCHEMA_PATH;
786
+ const schemaPath = path.join(confectDirectory, generatedSchemaPath);
787
+
788
+ const bindings = yield* tableModuleBindings(tableModules, schemaPath);
789
+ const contents = yield* templates.runtimeSchema({ tableModules: bindings });
790
+
791
+ yield* writeFileStringAndLog(schemaPath, contents);
792
+ });
793
+
794
+ const generateConvexSchema = (
795
+ tableModules: ReadonlyArray<TableModule.TableModule>,
796
+ ) =>
797
+ Effect.gen(function* () {
798
+ const path = yield* Path.Path;
799
+ const confectDirectory = yield* ConfectDirectory.get;
800
+ const generatedConvexSchemaPath = yield* GENERATED_CONVEX_SCHEMA_PATH;
801
+ const convexSchemaPath = path.join(
802
+ confectDirectory,
803
+ generatedConvexSchemaPath,
804
+ );
805
+
806
+ const bindings = yield* tableModuleBindings(tableModules, convexSchemaPath);
807
+ const contents = yield* templates.convexSchema({ tableModules: bindings });
808
+
809
+ yield* writeFileStringAndLog(convexSchemaPath, contents);
810
+ });
811
+
812
+ const generateConvexSchemaReexport = Effect.gen(function* () {
625
813
  const path = yield* Path.Path;
626
814
  const confectDirectory = yield* ConfectDirectory.get;
627
815
  const convexDirectory = yield* ConvexDirectory.get;
628
-
629
- const confectSchemaPath = path.join(confectDirectory, "schema.ts");
630
-
631
- // `validateSchema` runs once at the top of `runCodegen`; no need to
632
- // bundle the schema again here.
816
+ const generatedConvexSchemaRelativePath = yield* GENERATED_CONVEX_SCHEMA_PATH;
633
817
 
634
818
  const convexSchemaPath = path.join(convexDirectory, "schema.ts");
819
+ const generatedConvexSchemaPath = path.join(
820
+ confectDirectory,
821
+ generatedConvexSchemaRelativePath,
822
+ );
635
823
 
636
- const relativeImportPath = path.relative(
637
- path.dirname(convexSchemaPath),
638
- confectSchemaPath,
824
+ const convexSchemaImportPath = yield* toModuleImportPath(
825
+ path.relative(path.dirname(convexSchemaPath), generatedConvexSchemaPath),
639
826
  );
640
- const importPathWithoutExt = yield* removePathExtension(relativeImportPath);
827
+
641
828
  const schemaContents = yield* templates.schema({
642
- schemaImportPath: importPathWithoutExt,
829
+ convexSchemaImportPath,
643
830
  });
644
831
 
645
832
  yield* writeFileStringAndLog(convexSchemaPath, schemaContents);
@@ -652,9 +839,12 @@ const generateServices = Effect.gen(function* () {
652
839
  const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
653
840
 
654
841
  const servicesPath = path.join(confectGeneratedDirectory, "services.ts");
655
- const schemaImportPath = path.relative(
656
- path.dirname(servicesPath),
657
- path.join(confectDirectory, "schema"),
842
+ const generatedSchemaPath = yield* GENERATED_SCHEMA_PATH;
843
+ const schemaImportPath = yield* toModuleImportPath(
844
+ path.relative(
845
+ path.dirname(servicesPath),
846
+ path.join(confectDirectory, generatedSchemaPath),
847
+ ),
658
848
  );
659
849
 
660
850
  const servicesContentsString = yield* templates.services({
@@ -672,9 +862,10 @@ const generateRefs = Effect.gen(function* () {
672
862
  const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
673
863
  const refsPath = path.join(confectGeneratedDirectory, "refs.ts");
674
864
  const refsDir = path.dirname(refsPath);
865
+ const generatedSpecPath = yield* GENERATED_SPEC_PATH;
675
866
 
676
867
  const specImportPath = yield* toModuleImportPath(
677
- path.relative(refsDir, path.join(confectDirectory, GENERATED_SPEC_PATH)),
868
+ path.relative(refsDir, path.join(confectDirectory, generatedSpecPath)),
678
869
  );
679
870
 
680
871
  const nodeSpecPath = yield* getGeneratedNodeSpecPath;