@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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,170 @@
1
1
  # @confect/cli
2
2
 
3
+ ## 9.0.0-next.7
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [5d19484]
8
+ - @confect/core@9.0.0-next.7
9
+ - @confect/server@9.0.0-next.7
10
+
11
+ ## 9.0.0-next.6
12
+
13
+ ### Major Changes
14
+
15
+ - 46045a9: Reduce per-function cold-start cost: make `FunctionSpec` schemas lazy and keep each Convex function's bundle scoped to its own group.
16
+
17
+ Previously, loading a single Convex function still paid for the whole project — importing the codegen-assembled `_generated/spec.ts` ran `Schema.Struct(...)` / `Schema.Array(...)` for every function at module load, and each per-function bundle transitively imported `_generated/api.ts` → `_generated/spec.ts` (every spec). A function's cold-start cost now scales with its own group rather than the size of the project.
18
+
19
+ ### Lazy `FunctionSpec` schemas
20
+
21
+ `FunctionSpec.*` (`publicQuery` / `internalQuery` / `publicMutation` / `internalMutation` / `publicAction` / `internalAction` / `publicNodeAction` / `internalNodeAction`) takes `args`, `returns`, and (optional) `error` as `() => Schema` thunks instead of bare schemas. The resulting provenance exposes them as sync lazy memoised getters (the same pattern `Table.make` uses), so importing `_generated/spec.ts` builds no schemas — construction is deferred to the first invocation that compiles validators or runs a codec.
22
+
23
+ Migration — wrap each schema in `() =>`:
24
+
25
+ ```diff
26
+ FunctionSpec.publicQuery({
27
+ name: "list",
28
+ - args: Schema.Struct({}),
29
+ - returns: Schema.Array(notes.Doc),
30
+ + args: () => Schema.Struct({}),
31
+ + returns: () => Schema.Array(notes.Doc),
32
+ })
33
+ ```
34
+
35
+ ### Impls take the `DatabaseSchema`, and group paths resolve impl-side
36
+
37
+ `FunctionImpl.make` and `GroupImpl.make` now take the runtime `DatabaseSchema` (the default export of `_generated/schema.ts`) as their first argument instead of the whole `Api`. The handler's ctx-service types only ever depended on the database schema, and switching impls to import `_generated/schema` instead of `_generated/api` removes `_generated/spec.ts` (and the function specs it transitively imports) from every per-function bundle.
38
+
39
+ Each function also registers under a flat, single-segment key into a fresh, isolated `Registry` provided per group by `RegisteredFunctions.buildForGroup` (and the CLI's impl validation), so no group-path lookup against `api.spec` is needed. As a result `Spec#addPath`, `Spec#paths`, and `Api.resolveGroupPathUnsafe` are removed; `GroupImpl` / `FunctionImpl` drop their group-path type parameter; and the codegen-emitted `_generated/spec.ts` / `nodeSpec.ts` no longer contain a `.addPath(...)` chain (the `.addAt(...)` / `.addGroupAt(...)` assembly tree that `Refs.make` consumes is unchanged).
40
+
41
+ Migration — in each `*.impl.ts`, import the database schema and pass it where you passed `api` / `nodeApi`:
42
+
43
+ ```diff
44
+ - import api from "../_generated/api"; // (or nodeApi from "../_generated/nodeApi")
45
+ + import databaseSchema from "../_generated/schema";
46
+
47
+ - const insert = FunctionImpl.make(api, notes, "insert", handler);
48
+ + const insert = FunctionImpl.make(databaseSchema, notes, "insert", handler);
49
+
50
+ - export default GroupImpl.make(api, notes).pipe(Layer.provide(insert), GroupImpl.finalize);
51
+ + export default GroupImpl.make(databaseSchema, notes).pipe(Layer.provide(insert), GroupImpl.finalize);
52
+ ```
53
+
54
+ Node impls migrate identically (from `nodeApi` to the same `_generated/schema`); only their specs differ (`GroupSpec.makeNode()`). Hand-rolled tests that built a `Spec` via `.addPath(group, "dot.path")` should drop those calls.
55
+
56
+ ### `buildForGroup` and the generated registries
57
+
58
+ `RegisteredFunctions.buildForGroup` takes the `DatabaseSchema` value plus the group's own `GroupSpec` as a single type argument (`buildForGroup<typeof groupSpec>(…)`, returning `RegisteredFunctionsForGroupSpec<Group>`); the `api` / `groupPath` parameters and the `ForGroupPath` dot-path navigation are gone. `RegisteredConvexFunction.make` / `RegisteredNodeFunction.make` take the `DatabaseSchema` rather than the `Api`. Each `_generated/registeredFunctions/{path}.ts` imports the runtime schema and references its group's leaf spec **type-only** (`typeof import("…/{group}.spec")["default"]`), so it never imports a spec module at runtime.
59
+
60
+ ### `_generated/api.ts` / `nodeApi.ts` are no longer emitted, and `Api` is removed
61
+
62
+ Nothing imports them anymore, so `confect codegen` no longer emits `_generated/api.ts` / `_generated/nodeApi.ts` and deletes any copies left over from earlier versions. The `Api` module itself (`@confect/server/Api` — `Api.make`, the `Api` type, `Api.resolveGroupPathUnsafe`, etc.) is **removed entirely**: impls and the generated registries take the `DatabaseSchema` value and the spec directly, so the combined database-schema-plus-spec `Api` is no longer used anywhere.
63
+
64
+ ### Net effect
65
+
66
+ A function's `convex/{path}.ts` bundle now imports only its own group's registry → its own `.impl` + `_generated/schema` (table schemas, built lazily) + its own group's spec. No `_generated/api.ts`, no project-wide `_generated/spec.ts`, and no sibling-group impls/specs. Re-run `confect codegen` after upgrading.
67
+
68
+ - 762f7eb: Split the deploy-time Convex schema from the runtime `DatabaseSchema`, make `confect/tables/` the single source of truth — including the table name, which is now derived from the filename — and make per-table schema construction lazy.
69
+
70
+ Previously, `confect/schema.ts` was user-authored and `DatabaseSchema` carried a `convexSchemaDefinition` field that was eagerly rebuilt on every `.addTable(...)`. That field was an `O(n²)` allocation for `n` tables, and it forced both the deploy CLI (which only needs `defineSchema(...)`) and the runtime (which only needs the table codec lookup) through the same module — so any runtime function bundle dragged in `convex/server`'s `defineSchema`. Issue 1.
71
+
72
+ Codegen now scans `confect/tables/*.ts` (every file must default-export a `Table`) and emits two siblings:
73
+ - `confect/_generated/schema.ts` — the runtime `DatabaseSchema`, consumed by `_generated/api.ts`. Imports `@confect/server` but never `convex/server`.
74
+ - `confect/_generated/convexSchema.ts` — the Convex deploy `SchemaDefinition`, re-exported one-line from `convex/schema.ts`. Imports `convex/server` but never `@confect/server`.
75
+
76
+ The `convexSchemaDefinition` field is removed from `DatabaseSchema` and `Api`. `TestConfect.layer` now takes the Convex schema definition as a separate argument so it can stay aligned with the deploy artifact without bringing the runtime schema along for the ride.
77
+
78
+ ### Filename-derived table names
79
+
80
+ The table name is now derived from the file's basename — `confect/tables/notes.ts` defines a table called `notes`. `Table.make` no longer accepts a name argument and returns an _unnamed_ `Table` value; codegen invokes that value with the filename to produce the bound table.
81
+
82
+ This eliminates a class of subtle infelicities: the file basename and the table name can never drift out of sync, cross-table `_id` references are type-constrained against the actual set of declared tables (catching typos at compile time), and ESM cycle hazards for mutual cross-table `Id` references are gone because authoring files no longer transitively import each other.
83
+
84
+ Codegen now emits two new sets of files alongside `_generated/schema.ts` and `_generated/convexSchema.ts`:
85
+ - `confect/_generated/id.ts` — a single `Id` constructor whose argument is type-constrained to the union of your table names. Use `Id("notes")` everywhere you previously wrote `GenericId.GenericId("notes")`.
86
+ - `confect/_generated/tables/<name>.ts` — one thin wrapper per table that binds the unnamed value from `confect/tables/<name>.ts` to its filename. This is what other modules (specs, impls, HTTP handlers) default-import to reach a table's `Doc`, `Fields`, and `tableName`.
87
+
88
+ Table filenames must be valid JS identifiers, may not start with `_` (Convex reserves underscore-prefixed names for system tables), and may not collide with reserved JS keywords like `import.ts`. Pick a casing convention you like — Confect's example code uses `snake_case` (`notes.ts`, `user_profiles.ts`).
89
+
90
+ The bound `Table`'s `name` property has been renamed to `tableName`. This avoids a silent collision with the built-in `Function.prototype.name` that JavaScript carries on every function value (including the new unnamed-callable `UnnamedTable`).
91
+
92
+ ### Lazy per-table schema construction
93
+
94
+ `Table.make` takes a `() => Schema.Struct({...})` callback rather than a bare struct, and a bound `Table`'s `Fields`, `Doc`, and `tableDefinition` are lazy memoised getters that only invoke that callback on first access.
95
+
96
+ Previously, every `confect/tables/<name>.ts` module ran `Schema.Struct({...})` (and the corresponding `compileTableSchema` / `defineTable` work) at module-load time. Because the codegen-emitted `_generated/schema.ts` is imported transitively from every per-group function bundle, loading any one function eagerly built _every_ table's schema graph — paying a cold-start cost proportional to the whole project, not just the function being invoked.
97
+
98
+ The bound `Table` now exposes `Fields` / `Doc` / `tableDefinition` as lazy getters that compute their value on first access, then replace themselves with a plain non-writable data property so second-and-subsequent accesses are observably indistinguishable from a plain property (and skip all function-call overhead). The result: a function bundle only pays the schema-construction cost for tables it actually touches via `db.table(name)` (which reaches `Fields` through `Document.decode`). The `UnnamedTable` callable no longer exposes `Fields` or `tableDefinition` — read these off the bound `Table` (the generated `_generated/tables/<name>.ts` wrapper already binds the name).
99
+
100
+ ### Migration
101
+ 1. Delete your `confect/schema.ts`. Codegen will refuse to run while a stray copy is present.
102
+ 2. Rename each `confect/tables/<Name>.ts` to a valid JS identifier in your chosen casing convention (e.g. `confect/tables/notes.ts`). The basename becomes the table name; you no longer pass it as an argument.
103
+ 3. Convert each table file to a **default-export-only** unnamed module: drop the name argument from `Table.make`, wrap the field-schema struct in a `() => ...` callback, and switch any `GenericId.GenericId("users")` references to `Id("users")` imported from `../_generated/id`:
104
+
105
+ ```diff
106
+ - import { GenericId } from "@confect/core";
107
+ import { Table } from "@confect/server";
108
+ import { Schema } from "effect";
109
+ + import { Id } from "../_generated/id";
110
+
111
+ - export default Table.make(
112
+ - "notes",
113
+ - Schema.Struct({
114
+ - userId: Schema.optional(GenericId.GenericId("users")),
115
+ - text: Schema.String,
116
+ - }),
117
+ - );
118
+ + export default Table.make(() =>
119
+ + Schema.Struct({
120
+ + userId: Schema.optional(Id("users")),
121
+ + text: Schema.String,
122
+ + }),
123
+ + );
124
+ ```
125
+
126
+ 4. Rewire every consumer site (specs, impls, integration tests, HTTP handlers, etc.) to import from the generated wrapper rather than directly from `tables/`. The wrapper is also where you now read `Doc` / `Fields` / `tableDefinition` (the unnamed `Table.make(...)` callable no longer exposes them):
127
+
128
+ ```diff
129
+ - import Notes from "../tables/Notes";
130
+ + import notes from "../_generated/tables/notes";
131
+
132
+ - returns: Schema.Array(Notes.Doc),
133
+ + returns: Schema.Array(notes.Doc),
134
+ ```
135
+
136
+ 5. Replace every remaining `GenericId.GenericId("x")` call site with `Id("x")` from `_generated/id` (in spec `args`/`returns`, in `TaggedError` schemas, in `TestConfect.run`, etc.).
137
+ 6. If you read `table.name` anywhere off a bound `Table`, rename it to `table.tableName`.
138
+ 7. Re-run `confect codegen`. It will create `confect/_generated/schema.ts`, `confect/_generated/convexSchema.ts`, `confect/_generated/id.ts`, and one `confect/_generated/tables/<name>.ts` wrapper per table; and it will rewrite `convex/schema.ts` to a one-line re-export.
139
+ 8. If you use `@confect/test`, pass the generated Convex schema definition to `TestConfect.layer`:
140
+
141
+ ```diff
142
+ - import confectSchema from "./confect/schema";
143
+ + import confectSchema from "./confect/_generated/schema";
144
+ + import convexSchema from "./confect/_generated/convexSchema";
145
+
146
+ export const layer = TestConfect_.layer(
147
+ confectSchema,
148
+ + convexSchema,
149
+ import.meta.glob("./convex/**/!(*.*.*)*.*s"),
150
+ );
151
+ ```
152
+
153
+ ### New warning: no tables discovered
154
+
155
+ If a Confect project has no tables — either `confect/tables/` is missing entirely or it exists but contains no `.ts` files — codegen now emits a yellow `⚠` warning and continues, producing an empty `DatabaseSchema.make()` / `defineSchema({})`. Table-free backends (e.g. action-only proxies, webhook bridges) are still legal; the warning just catches the much more common case of a typoed directory name or files placed at the wrong path. To silence it, add at least one `Table.make(...)` module under `confect/tables/`.
156
+
157
+ ### New error: invalid table filename
158
+
159
+ Codegen now rejects table files whose basename is not a valid JS identifier (e.g. `user-profiles.ts`), starts with `_` (reserved for Convex system tables), or shadows a reserved JS keyword (e.g. `import.ts`). Rename the offending file to fix it — for example, `user-profiles.ts` → `user_profiles.ts` or `userProfiles.ts`.
160
+
161
+ ### Patch Changes
162
+
163
+ - Updated dependencies [46045a9]
164
+ - Updated dependencies [762f7eb]
165
+ - @confect/core@9.0.0-next.6
166
+ - @confect/server@9.0.0-next.6
167
+
3
168
  ## 9.0.0-next.5
4
169
 
5
170
  ### Patch Changes
@@ -29,15 +29,23 @@ var ImplMissingFunctionsError = class extends Schema.TaggedError()("ImplMissingF
29
29
  groupPath: Schema.String,
30
30
  missingFunctionNames: Schema.Array(Schema.String)
31
31
  }) {};
32
- var SchemaInvalidDefaultExportError = class extends Schema.TaggedError()("SchemaInvalidDefaultExportError", { schemaPath: Schema.String }) {};
33
32
  var ParentChildNameCollisionError = class extends Schema.TaggedError()("ParentChildNameCollisionError", {
34
33
  parentSpecPath: Schema.String,
35
34
  childSpecPath: Schema.String,
36
35
  collisionName: Schema.String,
37
36
  collisionKind: Schema.Literal("function", "group")
38
37
  }) {};
39
- var MissingSchemaFileError = class extends Schema.TaggedError()("MissingSchemaFileError", { schemaPath: Schema.String }) {};
40
- const CodegenError = Schema.Union(BuildError, MissingImplFileError, MissingSpecFileError, SpecMissingDefaultGroupSpecError, SpecRuntimeMismatchError, ImplMissingSpecImportError, ImplMissingDefaultLayerError, ImplNotFinalizedError, ImplMissingFunctionsError, SchemaInvalidDefaultExportError, MissingSchemaFileError, ParentChildNameCollisionError);
38
+ var InvalidTableDefaultExportError = class extends Schema.TaggedError()("InvalidTableDefaultExportError", { tablePath: Schema.String }) {};
39
+ var InvalidTableFilenameError = class extends Schema.TaggedError()("InvalidTableFilenameError", {
40
+ tablePath: Schema.String,
41
+ reason: Schema.String
42
+ }) {};
43
+ var DuplicateTableNameError = class extends Schema.TaggedError()("DuplicateTableNameError", { collisions: Schema.Array(Schema.Struct({
44
+ tableName: Schema.String,
45
+ tablePaths: Schema.Array(Schema.String)
46
+ })) }) {};
47
+ var LegacySchemaFileError = class extends Schema.TaggedError()("LegacySchemaFileError", { schemaPath: Schema.String }) {};
48
+ const CodegenError = Schema.Union(BuildError, MissingImplFileError, MissingSpecFileError, SpecMissingDefaultGroupSpecError, SpecRuntimeMismatchError, ImplMissingSpecImportError, ImplMissingDefaultLayerError, ImplNotFinalizedError, ImplMissingFunctionsError, ParentChildNameCollisionError, InvalidTableDefaultExportError, InvalidTableFilenameError, DuplicateTableNameError, LegacySchemaFileError);
41
49
  const isCodegenError = (error) => {
42
50
  if (isBuildError(error)) return true;
43
51
  return Schema.is(CodegenError)(error);
@@ -61,14 +69,19 @@ const renderImplMissingSpecImportError = (error) => {
61
69
  const stem = stemFromSpecPath(error.expectedSpecPath);
62
70
  return singleLine(AnsiDoc.text("Impl "), formatPathDoc(error.implPath), AnsiDoc.text(` does not import its sibling spec; add \`import ${stem} from "./${stem}.spec"\` and pass it to FunctionImpl.make / GroupImpl.make.`));
63
71
  };
64
- const renderImplMissingDefaultLayerError = (error) => singleLine(AnsiDoc.text("Impl "), formatPathDoc(error.implPath), AnsiDoc.text(" must default-export a GroupImpl layer; wrap your handlers with `GroupImpl.make(api, groupSpec).pipe(Layer.provide(...))` and `export default` it."));
65
- const renderImplNotFinalizedError = (error) => singleLine(AnsiDoc.text("Impl "), formatPathDoc(error.implPath), AnsiDoc.text(" is not finalized; append `GroupImpl.finalize` to the end of the pipeline (e.g. `GroupImpl.make(api, group).pipe(Layer.provide(...), GroupImpl.finalize)`)."));
72
+ const renderImplMissingDefaultLayerError = (error) => singleLine(AnsiDoc.text("Impl "), formatPathDoc(error.implPath), AnsiDoc.text(" must default-export a GroupImpl layer; wrap your handlers with `GroupImpl.make(databaseSchema, groupSpec).pipe(Layer.provide(...))` and `export default` it."));
73
+ const renderImplNotFinalizedError = (error) => singleLine(AnsiDoc.text("Impl "), formatPathDoc(error.implPath), AnsiDoc.text(" is not finalized; append `GroupImpl.finalize` to the end of the pipeline (e.g. `GroupImpl.make(databaseSchema, group).pipe(Layer.provide(...), GroupImpl.finalize)`)."));
66
74
  const renderImplMissingFunctionsError = (error) => {
67
75
  const names = error.missingFunctionNames.join(", ");
68
76
  return singleLine(AnsiDoc.text("Impl "), formatPathDoc(error.implPath), AnsiDoc.text(` does not implement every function declared by group \`${error.groupPath}\`; missing: ${names}. Add a \`FunctionImpl.make\` for each missing function and provide it to the group layer.`));
69
77
  };
70
- const renderSchemaInvalidDefaultExportError = (error) => singleLine(AnsiDoc.text("Schema "), formatPathDoc(error.schemaPath), AnsiDoc.text(" must default-export a DatabaseSchema; build it with DatabaseSchema.make({ ... })."));
71
- const renderMissingSchemaFileError = (error) => singleLine(AnsiDoc.text("Schema "), formatPathDoc(error.schemaPath), AnsiDoc.text(" is required but is missing; create it and default-export a DatabaseSchema (DatabaseSchema.make({ ... }))."));
78
+ const renderInvalidTableDefaultExportError = (error) => singleLine(AnsiDoc.text("Table "), formatPathDoc(error.tablePath), AnsiDoc.text(" must default-export a Table (e.g. `export default Table.make({ ... })`); convert any named export to a default export."));
79
+ const renderInvalidTableFilenameError = (error) => singleLine(AnsiDoc.text("Table "), formatPathDoc(error.tablePath), AnsiDoc.text(` has an invalid filename: ${error.reason} Convex table names must start with a letter and contain only letters, numbers, and underscores; leading underscores are reserved for system tables.`));
80
+ const renderDuplicateTableNameError = (error) => {
81
+ const conflicts = error.collisions.map(({ tableName, tablePaths }) => `\`${tableName}\` (${tablePaths.join(", ")})`).join("; ");
82
+ return singleLine(AnsiDoc.text(`Multiple files under \`confect/tables/\` resolve to the same table name. Table names are derived from filenames, so each must be unique across the directory (including subdirectories); rename or remove all but one. Conflicts: ${conflicts}.`));
83
+ };
84
+ const renderLegacySchemaFileError = (error) => singleLine(AnsiDoc.text("Found a legacy "), formatPathDoc(error.schemaPath), AnsiDoc.text(". Delete it: tables in `confect/tables/*.ts` are now the single source of truth, and the runtime schema is generated as `confect/_generated/schema.ts`."));
72
85
  const renderParentChildNameCollisionError = (error) => singleLine(AnsiDoc.text("Spec "), formatPathDoc(error.parentSpecPath), AnsiDoc.text(` declares a ${error.collisionKind} \`${error.collisionName}\` whose name collides with the sibling subdirectory spec `), formatPathDoc(error.childSpecPath), AnsiDoc.text(`. Rename one of them so the assembled spec has a unique key at this path.`));
73
86
  /**
74
87
  * Render any {@link CodegenError} into a styled, ready-to-print string.
@@ -78,7 +91,7 @@ const renderParentChildNameCollisionError = (error) => singleLine(AnsiDoc.text("
78
91
  */
79
92
  const renderCodegenError = (error) => {
80
93
  if (isBuildError(error)) return renderBuildError(error);
81
- return Match.value(error).pipe(Match.tag("MissingImplFileError", (e) => pipe(renderMissingImplFileError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("MissingSpecFileError", (e) => pipe(renderMissingSpecFileError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("SpecMissingDefaultGroupSpecError", (e) => pipe(renderSpecMissingDefaultGroupSpecError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("SpecRuntimeMismatchError", (e) => pipe(renderSpecRuntimeMismatchError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ImplMissingSpecImportError", (e) => pipe(renderImplMissingSpecImportError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ImplMissingDefaultLayerError", (e) => pipe(renderImplMissingDefaultLayerError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ImplNotFinalizedError", (e) => pipe(renderImplNotFinalizedError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ImplMissingFunctionsError", (e) => pipe(renderImplMissingFunctionsError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("SchemaInvalidDefaultExportError", (e) => pipe(renderSchemaInvalidDefaultExportError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("MissingSchemaFileError", (e) => pipe(renderMissingSchemaFileError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ParentChildNameCollisionError", (e) => pipe(renderParentChildNameCollisionError(e), AnsiDoc.render({ style: "pretty" }))), Match.exhaustive);
94
+ return Match.value(error).pipe(Match.tag("MissingImplFileError", (e) => pipe(renderMissingImplFileError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("MissingSpecFileError", (e) => pipe(renderMissingSpecFileError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("SpecMissingDefaultGroupSpecError", (e) => pipe(renderSpecMissingDefaultGroupSpecError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("SpecRuntimeMismatchError", (e) => pipe(renderSpecRuntimeMismatchError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ImplMissingSpecImportError", (e) => pipe(renderImplMissingSpecImportError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ImplMissingDefaultLayerError", (e) => pipe(renderImplMissingDefaultLayerError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ImplNotFinalizedError", (e) => pipe(renderImplNotFinalizedError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ImplMissingFunctionsError", (e) => pipe(renderImplMissingFunctionsError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("ParentChildNameCollisionError", (e) => pipe(renderParentChildNameCollisionError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("InvalidTableDefaultExportError", (e) => pipe(renderInvalidTableDefaultExportError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("InvalidTableFilenameError", (e) => pipe(renderInvalidTableFilenameError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("DuplicateTableNameError", (e) => pipe(renderDuplicateTableNameError(e), AnsiDoc.render({ style: "pretty" }))), Match.tag("LegacySchemaFileError", (e) => pipe(renderLegacySchemaFileError(e), AnsiDoc.render({ style: "pretty" }))), Match.exhaustive);
82
95
  };
83
96
  const logCodegenError = (error) => Effect.sync(() => console.error(renderCodegenError(error)));
84
97
  /**
@@ -97,5 +110,5 @@ const tapAndLog = (effect) => effect.pipe(Effect.tapError((error) => isCodegenEr
97
110
  const catchAndLog = (effect) => Effect.catchIf(Effect.map(effect, Option.some), isCodegenError, (error) => logCodegenError(error).pipe(Effect.as(Option.none())));
98
111
 
99
112
  //#endregion
100
- export { ImplMissingDefaultLayerError, ImplMissingFunctionsError, ImplMissingSpecImportError, ImplNotFinalizedError, MissingImplFileError, MissingSchemaFileError, MissingSpecFileError, ParentChildNameCollisionError, SchemaInvalidDefaultExportError, SpecMissingDefaultGroupSpecError, SpecRuntimeMismatchError, catchAndLog, tapAndLog };
113
+ export { DuplicateTableNameError, ImplMissingDefaultLayerError, ImplMissingFunctionsError, ImplMissingSpecImportError, ImplNotFinalizedError, InvalidTableDefaultExportError, InvalidTableFilenameError, LegacySchemaFileError, MissingImplFileError, MissingSpecFileError, ParentChildNameCollisionError, SpecMissingDefaultGroupSpecError, SpecRuntimeMismatchError, catchAndLog, tapAndLog };
101
114
  //# sourceMappingURL=CodegenError.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"CodegenError.mjs","names":[],"sources":["../src/CodegenError.ts"],"sourcesContent":["import { Ansi, AnsiDoc } from \"@effect/printer-ansi\";\nimport { Effect, Match, Option, pipe, Schema } from \"effect\";\nimport { BuildError, isBuildError, renderBuildError } from \"./BuildError\";\nimport { formatPathDoc } from \"./log\";\n\n// --- Variants ---\n\nexport class MissingImplFileError extends Schema.TaggedError<MissingImplFileError>()(\n \"MissingImplFileError\",\n {\n specPath: Schema.String,\n expectedImplPath: Schema.String,\n },\n) {}\n\nexport class MissingSpecFileError extends Schema.TaggedError<MissingSpecFileError>()(\n \"MissingSpecFileError\",\n {\n implPath: Schema.String,\n expectedSpecPath: Schema.String,\n },\n) {}\n\nexport class SpecMissingDefaultGroupSpecError extends Schema.TaggedError<SpecMissingDefaultGroupSpecError>()(\n \"SpecMissingDefaultGroupSpecError\",\n {\n specPath: Schema.String,\n },\n) {}\n\nexport class SpecRuntimeMismatchError extends Schema.TaggedError<SpecRuntimeMismatchError>()(\n \"SpecRuntimeMismatchError\",\n {\n specPath: Schema.String,\n expectedRuntime: Schema.Literal(\"Convex\", \"Node\"),\n actualRuntime: Schema.Literal(\"Convex\", \"Node\"),\n },\n) {}\n\nexport class ImplMissingSpecImportError extends Schema.TaggedError<ImplMissingSpecImportError>()(\n \"ImplMissingSpecImportError\",\n {\n implPath: Schema.String,\n expectedSpecPath: Schema.String,\n },\n) {}\n\nexport class ImplMissingDefaultLayerError extends Schema.TaggedError<ImplMissingDefaultLayerError>()(\n \"ImplMissingDefaultLayerError\",\n {\n implPath: Schema.String,\n },\n) {}\n\nexport class ImplNotFinalizedError extends Schema.TaggedError<ImplNotFinalizedError>()(\n \"ImplNotFinalizedError\",\n {\n implPath: Schema.String,\n },\n) {}\n\nexport class ImplMissingFunctionsError extends Schema.TaggedError<ImplMissingFunctionsError>()(\n \"ImplMissingFunctionsError\",\n {\n implPath: Schema.String,\n groupPath: Schema.String,\n missingFunctionNames: Schema.Array(Schema.String),\n },\n) {}\n\nexport class SchemaInvalidDefaultExportError extends Schema.TaggedError<SchemaInvalidDefaultExportError>()(\n \"SchemaInvalidDefaultExportError\",\n {\n schemaPath: Schema.String,\n },\n) {}\n\nexport class ParentChildNameCollisionError extends Schema.TaggedError<ParentChildNameCollisionError>()(\n \"ParentChildNameCollisionError\",\n {\n parentSpecPath: Schema.String,\n childSpecPath: Schema.String,\n collisionName: Schema.String,\n collisionKind: Schema.Literal(\"function\", \"group\"),\n },\n) {}\n\nexport class MissingSchemaFileError extends Schema.TaggedError<MissingSchemaFileError>()(\n \"MissingSchemaFileError\",\n {\n schemaPath: Schema.String,\n },\n) {}\n\nexport const CodegenError = Schema.Union(\n BuildError,\n MissingImplFileError,\n MissingSpecFileError,\n SpecMissingDefaultGroupSpecError,\n SpecRuntimeMismatchError,\n ImplMissingSpecImportError,\n ImplMissingDefaultLayerError,\n ImplNotFinalizedError,\n ImplMissingFunctionsError,\n SchemaInvalidDefaultExportError,\n MissingSchemaFileError,\n ParentChildNameCollisionError,\n);\nexport type CodegenError = typeof CodegenError.Type;\n\nexport const isCodegenError = (error: unknown): error is CodegenError => {\n if (isBuildError(error)) return true;\n return Schema.is(CodegenError)(error);\n};\n\n// --- Per-variant rendering ---\n\nconst cross = pipe(AnsiDoc.char(\"✘\"), AnsiDoc.annotate(Ansi.red));\n\nconst stemFromSpecPath = (specPath: string): string => {\n const lastSep = Math.max(\n specPath.lastIndexOf(\"/\"),\n specPath.lastIndexOf(\"\\\\\"),\n );\n const basename = lastSep < 0 ? specPath : specPath.slice(lastSep + 1);\n return basename.endsWith(\".spec.ts\")\n ? basename.slice(0, -\".spec.ts\".length)\n : basename;\n};\n\nconst singleLine = (\n ...parts: ReadonlyArray<AnsiDoc.AnsiDoc>\n): AnsiDoc.AnsiDoc => pipe(cross, AnsiDoc.catWithSpace(AnsiDoc.hcat(parts)));\n\nconst renderMissingImplFileError = (\n error: MissingImplFileError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Spec \"),\n formatPathDoc(error.specPath),\n AnsiDoc.text(\" has no sibling impl; create \"),\n formatPathDoc(error.expectedImplPath),\n AnsiDoc.text(\" and default-export a GroupImpl layer from it.\"),\n );\n\nconst renderMissingSpecFileError = (\n error: MissingSpecFileError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\" has no sibling spec; create \"),\n formatPathDoc(error.expectedSpecPath),\n AnsiDoc.text(\n \" and default-export a GroupSpec from it, or remove the impl.\",\n ),\n );\n\nconst renderSpecMissingDefaultGroupSpecError = (\n error: SpecMissingDefaultGroupSpecError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Spec \"),\n formatPathDoc(error.specPath),\n AnsiDoc.text(\n \" must default-export a GroupSpec; build it with GroupSpec.make() or GroupSpec.makeNode().\",\n ),\n );\n\nconst renderSpecRuntimeMismatchError = (\n error: SpecRuntimeMismatchError,\n): AnsiDoc.AnsiDoc => {\n const constructor =\n error.expectedRuntime === \"Node\"\n ? \"GroupSpec.makeNode()\"\n : \"GroupSpec.make()\";\n const moveHint =\n error.expectedRuntime === \"Node\"\n ? \" or move the file into confect/node/.\"\n : \" or move the file out of confect/node/.\";\n return singleLine(\n AnsiDoc.text(\"Spec \"),\n formatPathDoc(error.specPath),\n AnsiDoc.text(\n ` declares a ${error.actualRuntime} GroupSpec but its location requires ${error.expectedRuntime}; use ${constructor}${moveHint}`,\n ),\n );\n};\n\nconst renderImplMissingSpecImportError = (\n error: ImplMissingSpecImportError,\n): AnsiDoc.AnsiDoc => {\n const stem = stemFromSpecPath(error.expectedSpecPath);\n return singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\n ` does not import its sibling spec; add \\`import ${stem} from \"./${stem}.spec\"\\` and pass it to FunctionImpl.make / GroupImpl.make.`,\n ),\n );\n};\n\nconst renderImplMissingDefaultLayerError = (\n error: ImplMissingDefaultLayerError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\n \" must default-export a GroupImpl layer; wrap your handlers with `GroupImpl.make(api, groupSpec).pipe(Layer.provide(...))` and `export default` it.\",\n ),\n );\n\nconst renderImplNotFinalizedError = (\n error: ImplNotFinalizedError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\n \" is not finalized; append `GroupImpl.finalize` to the end of the pipeline (e.g. `GroupImpl.make(api, group).pipe(Layer.provide(...), GroupImpl.finalize)`).\",\n ),\n );\n\nconst renderImplMissingFunctionsError = (\n error: ImplMissingFunctionsError,\n): AnsiDoc.AnsiDoc => {\n const names = error.missingFunctionNames.join(\", \");\n return singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\n ` does not implement every function declared by group \\`${error.groupPath}\\`; missing: ${names}. Add a \\`FunctionImpl.make\\` for each missing function and provide it to the group layer.`,\n ),\n );\n};\n\nconst renderSchemaInvalidDefaultExportError = (\n error: SchemaInvalidDefaultExportError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Schema \"),\n formatPathDoc(error.schemaPath),\n AnsiDoc.text(\n \" must default-export a DatabaseSchema; build it with DatabaseSchema.make({ ... }).\",\n ),\n );\n\nconst renderMissingSchemaFileError = (\n error: MissingSchemaFileError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Schema \"),\n formatPathDoc(error.schemaPath),\n AnsiDoc.text(\n \" is required but is missing; create it and default-export a DatabaseSchema (DatabaseSchema.make({ ... })).\",\n ),\n );\n\nconst renderParentChildNameCollisionError = (\n error: ParentChildNameCollisionError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Spec \"),\n formatPathDoc(error.parentSpecPath),\n AnsiDoc.text(\n ` declares a ${error.collisionKind} \\`${error.collisionName}\\` whose name collides with the sibling subdirectory spec `,\n ),\n formatPathDoc(error.childSpecPath),\n AnsiDoc.text(\n `. Rename one of them so the assembled spec has a unique key at this path.`,\n ),\n );\n\n/**\n * Render any {@link CodegenError} into a styled, ready-to-print string.\n * Single-error variants render to a one-line `✘`-prefixed message;\n * `BundleFailedError` (the only multi-error variant) renders to a header\n * plus an esbuild diagnostic block.\n */\nexport const renderCodegenError = (error: CodegenError): string => {\n if (isBuildError(error)) return renderBuildError(error);\n return Match.value(error).pipe(\n Match.tag(\"MissingImplFileError\", (e) =>\n pipe(renderMissingImplFileError(e), AnsiDoc.render({ style: \"pretty\" })),\n ),\n Match.tag(\"MissingSpecFileError\", (e) =>\n pipe(renderMissingSpecFileError(e), AnsiDoc.render({ style: \"pretty\" })),\n ),\n Match.tag(\"SpecMissingDefaultGroupSpecError\", (e) =>\n pipe(\n renderSpecMissingDefaultGroupSpecError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"SpecRuntimeMismatchError\", (e) =>\n pipe(\n renderSpecRuntimeMismatchError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"ImplMissingSpecImportError\", (e) =>\n pipe(\n renderImplMissingSpecImportError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"ImplMissingDefaultLayerError\", (e) =>\n pipe(\n renderImplMissingDefaultLayerError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"ImplNotFinalizedError\", (e) =>\n pipe(renderImplNotFinalizedError(e), AnsiDoc.render({ style: \"pretty\" })),\n ),\n Match.tag(\"ImplMissingFunctionsError\", (e) =>\n pipe(\n renderImplMissingFunctionsError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"SchemaInvalidDefaultExportError\", (e) =>\n pipe(\n renderSchemaInvalidDefaultExportError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"MissingSchemaFileError\", (e) =>\n pipe(\n renderMissingSchemaFileError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"ParentChildNameCollisionError\", (e) =>\n pipe(\n renderParentChildNameCollisionError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.exhaustive,\n );\n};\n\nexport const logCodegenError = (error: CodegenError) =>\n Effect.sync(() => console.error(renderCodegenError(error)));\n\n// --- Effect combinators ---\n\n/**\n * Log any {@link CodegenError} thrown by `effect` and propagate the failure\n * unchanged so the caller's error channel is preserved (used by the\n * `codegen` command, which needs the failure to surface as a non-zero exit\n * code).\n */\nexport const tapAndLog = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E, R> =>\n effect.pipe(\n Effect.tapError((error) =>\n isCodegenError(error) ? logCodegenError(error) : Effect.void,\n ),\n );\n\n/**\n * Catch any {@link CodegenError} thrown by `effect`, log it, and resolve to\n * `Option.none()` (used by the `dev` command's sync loop, which continues\n * after a failed sync rather than exiting). Success resolves to\n * `Option.some(value)`.\n */\nexport const catchAndLog = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.Effect<Option.Option<A>, Exclude<E, CodegenError>, R> =>\n Effect.catchIf(Effect.map(effect, Option.some<A>), isCodegenError, (error) =>\n logCodegenError(error).pipe(Effect.as(Option.none<A>())),\n ) as Effect.Effect<Option.Option<A>, Exclude<E, CodegenError>, R>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"CodegenError.mjs","names":[],"sources":["../src/CodegenError.ts"],"sourcesContent":["import { Ansi, AnsiDoc } from \"@effect/printer-ansi\";\nimport { Effect, Match, Option, pipe, Schema } from \"effect\";\nimport { BuildError, isBuildError, renderBuildError } from \"./BuildError\";\nimport { formatPathDoc } from \"./log\";\n\n// --- Variants ---\n\nexport class MissingImplFileError extends Schema.TaggedError<MissingImplFileError>()(\n \"MissingImplFileError\",\n {\n specPath: Schema.String,\n expectedImplPath: Schema.String,\n },\n) {}\n\nexport class MissingSpecFileError extends Schema.TaggedError<MissingSpecFileError>()(\n \"MissingSpecFileError\",\n {\n implPath: Schema.String,\n expectedSpecPath: Schema.String,\n },\n) {}\n\nexport class SpecMissingDefaultGroupSpecError extends Schema.TaggedError<SpecMissingDefaultGroupSpecError>()(\n \"SpecMissingDefaultGroupSpecError\",\n {\n specPath: Schema.String,\n },\n) {}\n\nexport class SpecRuntimeMismatchError extends Schema.TaggedError<SpecRuntimeMismatchError>()(\n \"SpecRuntimeMismatchError\",\n {\n specPath: Schema.String,\n expectedRuntime: Schema.Literal(\"Convex\", \"Node\"),\n actualRuntime: Schema.Literal(\"Convex\", \"Node\"),\n },\n) {}\n\nexport class ImplMissingSpecImportError extends Schema.TaggedError<ImplMissingSpecImportError>()(\n \"ImplMissingSpecImportError\",\n {\n implPath: Schema.String,\n expectedSpecPath: Schema.String,\n },\n) {}\n\nexport class ImplMissingDefaultLayerError extends Schema.TaggedError<ImplMissingDefaultLayerError>()(\n \"ImplMissingDefaultLayerError\",\n {\n implPath: Schema.String,\n },\n) {}\n\nexport class ImplNotFinalizedError extends Schema.TaggedError<ImplNotFinalizedError>()(\n \"ImplNotFinalizedError\",\n {\n implPath: Schema.String,\n },\n) {}\n\nexport class ImplMissingFunctionsError extends Schema.TaggedError<ImplMissingFunctionsError>()(\n \"ImplMissingFunctionsError\",\n {\n implPath: Schema.String,\n groupPath: Schema.String,\n missingFunctionNames: Schema.Array(Schema.String),\n },\n) {}\n\nexport class ParentChildNameCollisionError extends Schema.TaggedError<ParentChildNameCollisionError>()(\n \"ParentChildNameCollisionError\",\n {\n parentSpecPath: Schema.String,\n childSpecPath: Schema.String,\n collisionName: Schema.String,\n collisionKind: Schema.Literal(\"function\", \"group\"),\n },\n) {}\n\nexport class InvalidTableDefaultExportError extends Schema.TaggedError<InvalidTableDefaultExportError>()(\n \"InvalidTableDefaultExportError\",\n {\n tablePath: Schema.String,\n },\n) {}\n\nexport class InvalidTableFilenameError extends Schema.TaggedError<InvalidTableFilenameError>()(\n \"InvalidTableFilenameError\",\n {\n tablePath: Schema.String,\n reason: Schema.String,\n },\n) {}\n\nexport class DuplicateTableNameError extends Schema.TaggedError<DuplicateTableNameError>()(\n \"DuplicateTableNameError\",\n {\n // Every table name that more than one file resolves to, each paired with\n // the colliding file paths. All collisions are captured in a single pass\n // so the user can fix them together rather than re-running codegen once\n // per conflict.\n collisions: Schema.Array(\n Schema.Struct({\n tableName: Schema.String,\n tablePaths: Schema.Array(Schema.String),\n }),\n ),\n },\n) {}\n\nexport class LegacySchemaFileError extends Schema.TaggedError<LegacySchemaFileError>()(\n \"LegacySchemaFileError\",\n {\n schemaPath: Schema.String,\n },\n) {}\n\nexport const CodegenError = Schema.Union(\n BuildError,\n MissingImplFileError,\n MissingSpecFileError,\n SpecMissingDefaultGroupSpecError,\n SpecRuntimeMismatchError,\n ImplMissingSpecImportError,\n ImplMissingDefaultLayerError,\n ImplNotFinalizedError,\n ImplMissingFunctionsError,\n ParentChildNameCollisionError,\n InvalidTableDefaultExportError,\n InvalidTableFilenameError,\n DuplicateTableNameError,\n LegacySchemaFileError,\n);\nexport type CodegenError = typeof CodegenError.Type;\n\nexport const isCodegenError = (error: unknown): error is CodegenError => {\n if (isBuildError(error)) return true;\n return Schema.is(CodegenError)(error);\n};\n\n// --- Per-variant rendering ---\n\nconst cross = pipe(AnsiDoc.char(\"✘\"), AnsiDoc.annotate(Ansi.red));\n\nconst stemFromSpecPath = (specPath: string): string => {\n const lastSep = Math.max(\n specPath.lastIndexOf(\"/\"),\n specPath.lastIndexOf(\"\\\\\"),\n );\n const basename = lastSep < 0 ? specPath : specPath.slice(lastSep + 1);\n return basename.endsWith(\".spec.ts\")\n ? basename.slice(0, -\".spec.ts\".length)\n : basename;\n};\n\nconst singleLine = (\n ...parts: ReadonlyArray<AnsiDoc.AnsiDoc>\n): AnsiDoc.AnsiDoc => pipe(cross, AnsiDoc.catWithSpace(AnsiDoc.hcat(parts)));\n\nconst renderMissingImplFileError = (\n error: MissingImplFileError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Spec \"),\n formatPathDoc(error.specPath),\n AnsiDoc.text(\" has no sibling impl; create \"),\n formatPathDoc(error.expectedImplPath),\n AnsiDoc.text(\" and default-export a GroupImpl layer from it.\"),\n );\n\nconst renderMissingSpecFileError = (\n error: MissingSpecFileError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\" has no sibling spec; create \"),\n formatPathDoc(error.expectedSpecPath),\n AnsiDoc.text(\n \" and default-export a GroupSpec from it, or remove the impl.\",\n ),\n );\n\nconst renderSpecMissingDefaultGroupSpecError = (\n error: SpecMissingDefaultGroupSpecError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Spec \"),\n formatPathDoc(error.specPath),\n AnsiDoc.text(\n \" must default-export a GroupSpec; build it with GroupSpec.make() or GroupSpec.makeNode().\",\n ),\n );\n\nconst renderSpecRuntimeMismatchError = (\n error: SpecRuntimeMismatchError,\n): AnsiDoc.AnsiDoc => {\n const constructor =\n error.expectedRuntime === \"Node\"\n ? \"GroupSpec.makeNode()\"\n : \"GroupSpec.make()\";\n const moveHint =\n error.expectedRuntime === \"Node\"\n ? \" or move the file into confect/node/.\"\n : \" or move the file out of confect/node/.\";\n return singleLine(\n AnsiDoc.text(\"Spec \"),\n formatPathDoc(error.specPath),\n AnsiDoc.text(\n ` declares a ${error.actualRuntime} GroupSpec but its location requires ${error.expectedRuntime}; use ${constructor}${moveHint}`,\n ),\n );\n};\n\nconst renderImplMissingSpecImportError = (\n error: ImplMissingSpecImportError,\n): AnsiDoc.AnsiDoc => {\n const stem = stemFromSpecPath(error.expectedSpecPath);\n return singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\n ` does not import its sibling spec; add \\`import ${stem} from \"./${stem}.spec\"\\` and pass it to FunctionImpl.make / GroupImpl.make.`,\n ),\n );\n};\n\nconst renderImplMissingDefaultLayerError = (\n error: ImplMissingDefaultLayerError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\n \" must default-export a GroupImpl layer; wrap your handlers with `GroupImpl.make(databaseSchema, groupSpec).pipe(Layer.provide(...))` and `export default` it.\",\n ),\n );\n\nconst renderImplNotFinalizedError = (\n error: ImplNotFinalizedError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\n \" is not finalized; append `GroupImpl.finalize` to the end of the pipeline (e.g. `GroupImpl.make(databaseSchema, group).pipe(Layer.provide(...), GroupImpl.finalize)`).\",\n ),\n );\n\nconst renderImplMissingFunctionsError = (\n error: ImplMissingFunctionsError,\n): AnsiDoc.AnsiDoc => {\n const names = error.missingFunctionNames.join(\", \");\n return singleLine(\n AnsiDoc.text(\"Impl \"),\n formatPathDoc(error.implPath),\n AnsiDoc.text(\n ` does not implement every function declared by group \\`${error.groupPath}\\`; missing: ${names}. Add a \\`FunctionImpl.make\\` for each missing function and provide it to the group layer.`,\n ),\n );\n};\n\nconst renderInvalidTableDefaultExportError = (\n error: InvalidTableDefaultExportError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Table \"),\n formatPathDoc(error.tablePath),\n AnsiDoc.text(\n \" must default-export a Table (e.g. `export default Table.make({ ... })`); convert any named export to a default export.\",\n ),\n );\n\nconst renderInvalidTableFilenameError = (\n error: InvalidTableFilenameError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Table \"),\n formatPathDoc(error.tablePath),\n AnsiDoc.text(\n ` has an invalid filename: ${error.reason} Convex table names must start with a letter and contain only letters, numbers, and underscores; leading underscores are reserved for system tables.`,\n ),\n );\n\nconst renderDuplicateTableNameError = (\n error: DuplicateTableNameError,\n): AnsiDoc.AnsiDoc => {\n const conflicts = error.collisions\n .map(\n ({ tableName, tablePaths }) =>\n `\\`${tableName}\\` (${tablePaths.join(\", \")})`,\n )\n .join(\"; \");\n return singleLine(\n AnsiDoc.text(\n `Multiple files under \\`confect/tables/\\` resolve to the same table name. Table names are derived from filenames, so each must be unique across the directory (including subdirectories); rename or remove all but one. Conflicts: ${conflicts}.`,\n ),\n );\n};\n\nconst renderLegacySchemaFileError = (\n error: LegacySchemaFileError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Found a legacy \"),\n formatPathDoc(error.schemaPath),\n AnsiDoc.text(\n \". Delete it: tables in `confect/tables/*.ts` are now the single source of truth, and the runtime schema is generated as `confect/_generated/schema.ts`.\",\n ),\n );\n\nconst renderParentChildNameCollisionError = (\n error: ParentChildNameCollisionError,\n): AnsiDoc.AnsiDoc =>\n singleLine(\n AnsiDoc.text(\"Spec \"),\n formatPathDoc(error.parentSpecPath),\n AnsiDoc.text(\n ` declares a ${error.collisionKind} \\`${error.collisionName}\\` whose name collides with the sibling subdirectory spec `,\n ),\n formatPathDoc(error.childSpecPath),\n AnsiDoc.text(\n `. Rename one of them so the assembled spec has a unique key at this path.`,\n ),\n );\n\n/**\n * Render any {@link CodegenError} into a styled, ready-to-print string.\n * Single-error variants render to a one-line `✘`-prefixed message;\n * `BundleFailedError` (the only multi-error variant) renders to a header\n * plus an esbuild diagnostic block.\n */\nexport const renderCodegenError = (error: CodegenError): string => {\n if (isBuildError(error)) return renderBuildError(error);\n return Match.value(error).pipe(\n Match.tag(\"MissingImplFileError\", (e) =>\n pipe(renderMissingImplFileError(e), AnsiDoc.render({ style: \"pretty\" })),\n ),\n Match.tag(\"MissingSpecFileError\", (e) =>\n pipe(renderMissingSpecFileError(e), AnsiDoc.render({ style: \"pretty\" })),\n ),\n Match.tag(\"SpecMissingDefaultGroupSpecError\", (e) =>\n pipe(\n renderSpecMissingDefaultGroupSpecError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"SpecRuntimeMismatchError\", (e) =>\n pipe(\n renderSpecRuntimeMismatchError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"ImplMissingSpecImportError\", (e) =>\n pipe(\n renderImplMissingSpecImportError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"ImplMissingDefaultLayerError\", (e) =>\n pipe(\n renderImplMissingDefaultLayerError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"ImplNotFinalizedError\", (e) =>\n pipe(renderImplNotFinalizedError(e), AnsiDoc.render({ style: \"pretty\" })),\n ),\n Match.tag(\"ImplMissingFunctionsError\", (e) =>\n pipe(\n renderImplMissingFunctionsError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"ParentChildNameCollisionError\", (e) =>\n pipe(\n renderParentChildNameCollisionError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"InvalidTableDefaultExportError\", (e) =>\n pipe(\n renderInvalidTableDefaultExportError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"InvalidTableFilenameError\", (e) =>\n pipe(\n renderInvalidTableFilenameError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"DuplicateTableNameError\", (e) =>\n pipe(\n renderDuplicateTableNameError(e),\n AnsiDoc.render({ style: \"pretty\" }),\n ),\n ),\n Match.tag(\"LegacySchemaFileError\", (e) =>\n pipe(renderLegacySchemaFileError(e), AnsiDoc.render({ style: \"pretty\" })),\n ),\n Match.exhaustive,\n );\n};\n\nexport const logCodegenError = (error: CodegenError) =>\n Effect.sync(() => console.error(renderCodegenError(error)));\n\n// --- Effect combinators ---\n\n/**\n * Log any {@link CodegenError} thrown by `effect` and propagate the failure\n * unchanged so the caller's error channel is preserved (used by the\n * `codegen` command, which needs the failure to surface as a non-zero exit\n * code).\n */\nexport const tapAndLog = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E, R> =>\n effect.pipe(\n Effect.tapError((error) =>\n isCodegenError(error) ? logCodegenError(error) : Effect.void,\n ),\n );\n\n/**\n * Catch any {@link CodegenError} thrown by `effect`, log it, and resolve to\n * `Option.none()` (used by the `dev` command's sync loop, which continues\n * after a failed sync rather than exiting). Success resolves to\n * `Option.some(value)`.\n */\nexport const catchAndLog = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.Effect<Option.Option<A>, Exclude<E, CodegenError>, R> =>\n Effect.catchIf(Effect.map(effect, Option.some<A>), isCodegenError, (error) =>\n logCodegenError(error).pipe(Effect.as(Option.none<A>())),\n ) as Effect.Effect<Option.Option<A>, Exclude<E, CodegenError>, R>;\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { fromBundlerError } from "./BuildError.mjs";
2
+ import { bundle, directlyImports } from "./Bundler.mjs";
2
3
  import { ImplMissingDefaultLayerError, ImplMissingFunctionsError, ImplMissingSpecImportError, ImplNotFinalizedError, SpecMissingDefaultGroupSpecError, SpecRuntimeMismatchError } from "./CodegenError.mjs";
3
4
  import { ConfectDirectory } from "./ConfectDirectory.mjs";
4
- import { bundle, directlyImports } from "./Bundler.mjs";
5
5
  import { removePathExtension } from "./utils.mjs";
6
6
  import { Array, Effect, Layer, Option, Ref, String } from "effect";
7
7
  import { GroupSpec, Registry } from "@confect/core";
@@ -160,7 +160,7 @@ const validateImpl = (leaf) => Effect.gen(function* () {
160
160
  const missing = expectedFunctionNames.filter((name) => !registeredSet.has(name));
161
161
  if (missing.length > 0) return yield* new ImplMissingFunctionsError({
162
162
  implPath: implRelativePath,
163
- groupPath: finalizedGroupImpl.groupPath,
163
+ groupPath: leaf.groupPathDot,
164
164
  missingFunctionNames: missing
165
165
  });
166
166
  });
@@ -1 +1 @@
1
- {"version":3,"file":"LeafModule.mjs","names":["Bundler.bundle","Bundler.directlyImports"],"sources":["../src/LeafModule.ts"],"sourcesContent":["import { GroupSpec, Registry } from \"@confect/core\";\nimport * as GroupImpl from \"@confect/server/GroupImpl\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport type { Context } from \"effect\";\nimport { Array, Effect, Layer, Option, Ref, String } from \"effect\";\nimport { fromBundlerError } from \"./BuildError\";\nimport * as Bundler from \"./Bundler\";\nimport {\n ImplMissingDefaultLayerError,\n ImplMissingFunctionsError,\n ImplMissingSpecImportError,\n ImplNotFinalizedError,\n SpecMissingDefaultGroupSpecError,\n SpecRuntimeMismatchError,\n} from \"./CodegenError\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\nimport { removePathExtension } from \"./utils\";\n\nexport interface LeafModule {\n readonly relativePath: string;\n readonly pathSegments: readonly [string, ...string[]];\n readonly groupPathDot: string;\n readonly registryGroupPathDot: string;\n readonly exportName: string;\n readonly runtime: \"Convex\" | \"Node\";\n readonly specImportPath: string;\n}\n\nexport const SPEC_SUFFIX = \".spec.ts\";\nexport const IMPL_SUFFIX = \".impl.ts\";\n\nconst swapModuleSuffix = (\n relativePath: string,\n fromSuffix: string,\n toSuffix: string,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n if (ext !== \".ts\" || !name.endsWith(fromSuffix.slice(0, -\".ts\".length))) {\n return relativePath;\n }\n\n const stem = name.slice(0, -fromSuffix.slice(0, -\".ts\".length).length);\n const nextName = `${stem}${toSuffix.slice(0, -\".ts\".length)}`;\n return dir.length > 0\n ? path.join(dir, `${nextName}${ext}`)\n : `${nextName}${ext}`;\n });\n\nexport const isLeafSpecPath = (relativePath: string) =>\n relativePath.endsWith(SPEC_SUFFIX);\n\nexport const isLeafImplPath = (relativePath: string) =>\n relativePath.endsWith(IMPL_SUFFIX);\n\nexport const exportNameFromModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { name, ext } = path.parse(relativePath);\n if (ext !== \".ts\") {\n return name;\n }\n return name.endsWith(\".spec\") ? name.slice(0, -\".spec\".length) : name;\n });\n\nexport const groupPathFromRelativeModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n const stem =\n ext === \".ts\" && name.endsWith(\".spec\")\n ? name.slice(0, -\".spec\".length)\n : name;\n const dirSegments = Array.filter(\n String.split(dir, path.sep),\n String.isNonEmpty,\n );\n const pathSegments = Array.append(dirSegments, stem) as [\n string,\n ...string[],\n ];\n return {\n pathSegments,\n groupPathDot: Array.join(pathSegments, \".\"),\n };\n });\n\nexport const specImportPathFromGenerated = (specRelativePath: string) =>\n Effect.gen(function* () {\n const withoutExt = yield* removePathExtension(specRelativePath);\n return `../${withoutExt}`;\n });\n\nexport const specPathForImpl = (implRelativePath: string) =>\n swapModuleSuffix(implRelativePath, IMPL_SUFFIX, SPEC_SUFFIX);\n\nexport const implPathForSpec = (specRelativePath: string) =>\n swapModuleSuffix(specRelativePath, SPEC_SUFFIX, IMPL_SUFFIX);\n\nexport const isNodeLeafModule = (relativePath: string) =>\n relativePath.startsWith(\"node/\") || relativePath.startsWith(\"node\\\\\");\n\nexport const toNodeRegistryLeaf = (leaf: LeafModule): LeafModule => ({\n ...leaf,\n pathSegments: [leaf.exportName],\n groupPathDot: leaf.exportName,\n});\n\nexport const registeredFunctionsRelativePath = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n return (\n path.join(\n \"registeredFunctions\",\n ...leaf.pathSegments.slice(leaf.runtime === \"Node\" ? 1 : 0),\n ) + \".ts\"\n );\n });\n\nexport const discoverLeafSpecFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n const excludedFiles = new Set([\"nodeSpec.ts\", \"spec.ts\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafSpecPath(relativePath)) {\n return false;\n }\n\n if (excludedFiles.has(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const discoverLeafImplFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafImplPath(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const toLeafModule = (specRelativePath: string) =>\n Effect.gen(function* () {\n const exportName = yield* exportNameFromModulePath(specRelativePath);\n const { pathSegments, groupPathDot } =\n yield* groupPathFromRelativeModulePath(specRelativePath);\n const specImportPath = yield* specImportPathFromGenerated(specRelativePath);\n const runtime = isNodeLeafModule(specRelativePath) ? \"Node\" : \"Convex\";\n\n return {\n relativePath: specRelativePath,\n pathSegments,\n groupPathDot,\n exportName,\n runtime,\n registryGroupPathDot: runtime === \"Node\" ? exportName : groupPathDot,\n specImportPath,\n } satisfies LeafModule;\n });\n\nconst absoluteModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const confectDirectory = yield* ConfectDirectory.get;\n const path = yield* Path.Path;\n return path.resolve(confectDirectory, relativePath);\n });\n\n/**\n * Validate that the leaf's spec file default-exports a `GroupSpec` whose\n * runtime matches the leaf's location (`Convex` for files outside\n * `confect/node/`, `Node` for files inside it). Returns the validated\n * `GroupSpec` so callers can avoid re-bundling for later inspection (e.g.\n * parent/child name-collision checks at codegen time).\n */\nexport const validateSpec = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const absolutePath = yield* absoluteModulePath(leaf.relativePath);\n const { module } = yield* Bundler.bundle(absolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n\n const groupSpec = module.default;\n\n if (!GroupSpec.isGroupSpec(groupSpec)) {\n return yield* new SpecMissingDefaultGroupSpecError({\n specPath: leaf.relativePath,\n });\n }\n\n if (groupSpec.runtime !== leaf.runtime) {\n return yield* new SpecRuntimeMismatchError({\n specPath: leaf.relativePath,\n expectedRuntime: leaf.runtime,\n actualRuntime: groupSpec.runtime,\n });\n }\n\n return groupSpec;\n });\n\n/**\n * Walk the built `Context` for a `Finalized` `GroupImpl` service value. The\n * lookup is value-shaped (via `GroupImpl.isFinalizedGroupImpl`) so we don't\n * need to know the group's path up front to construct a typed tag for it.\n */\nconst findFinalizedGroupImpl = <S>(\n context: Context.Context<S>,\n): Option.Option<GroupImpl.AnyFinalized> =>\n Array.findFirst(context.unsafeMap.values(), GroupImpl.isFinalizedGroupImpl);\n\n/**\n * Build the impl layer with a fresh `Registry` so each validation is\n * isolated from prior validations' `FunctionImpl.make` writes. The CLI no\n * longer reads the registry directly — `GroupImpl.finalize` snapshots the\n * registered function names onto the produced `Finalized` `GroupImpl`\n * service value — but a fresh `Ref` is still required because the default\n * `Context.Reference` is cached globally and would otherwise accumulate\n * items across impls.\n */\nconst buildImplLayer = (implLayer: Layer.Layer<unknown>) =>\n Effect.gen(function* () {\n const registry = Ref.unsafeMake<Registry.RegistryItems>({});\n return yield* Layer.build(\n implLayer as Layer.Layer<unknown, never, never>,\n ).pipe(Effect.provideService(Registry.Registry, registry));\n }).pipe(Effect.scoped);\n\n/**\n * Validate that the leaf's sibling impl file imports the spec, default-exports\n * a finalized `GroupImpl` layer, and provides a `FunctionImpl` for every\n * function declared by the spec.\n */\nexport const validateImpl = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const implRelativePath = yield* implPathForSpec(leaf.relativePath);\n const implAbsolutePath = yield* absoluteModulePath(implRelativePath);\n const specAbsolutePath = yield* absoluteModulePath(leaf.relativePath);\n\n const bundled = yield* Bundler.bundle(implAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(implRelativePath, error)),\n );\n\n if (\n !(yield* Bundler.directlyImports(\n bundled,\n implAbsolutePath,\n specAbsolutePath,\n ))\n ) {\n return yield* new ImplMissingSpecImportError({\n implPath: implRelativePath,\n expectedSpecPath: leaf.relativePath,\n });\n }\n\n if (!Layer.isLayer(bundled.module.default)) {\n return yield* new ImplMissingDefaultLayerError({\n implPath: implRelativePath,\n });\n }\n\n const { module: specModule } = yield* Bundler.bundle(specAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n const groupSpec = specModule.default as GroupSpec.AnyWithProps;\n const expectedFunctionNames = Object.keys(groupSpec.functions);\n\n const context = yield* buildImplLayer(\n bundled.module.default as Layer.Layer<unknown>,\n );\n const finalizedGroupImpl = yield* Option.match(\n findFinalizedGroupImpl(context),\n {\n onNone: () => new ImplNotFinalizedError({ implPath: implRelativePath }),\n onSome: Effect.succeed,\n },\n );\n\n const registeredSet = new Set(finalizedGroupImpl.registeredFunctionNames);\n const missing = expectedFunctionNames.filter(\n (name) => !registeredSet.has(name),\n );\n\n if (missing.length > 0) {\n return yield* new ImplMissingFunctionsError({\n implPath: implRelativePath,\n groupPath: finalizedGroupImpl.groupPath,\n missingFunctionNames: missing,\n });\n }\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"LeafModule.mjs","names":["Bundler.bundle","Bundler.directlyImports"],"sources":["../src/LeafModule.ts"],"sourcesContent":["import { GroupSpec, Registry } from \"@confect/core\";\nimport * as GroupImpl from \"@confect/server/GroupImpl\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport type { Context } from \"effect\";\nimport { Array, Effect, Layer, Option, Ref, String } from \"effect\";\nimport { fromBundlerError } from \"./BuildError\";\nimport * as Bundler from \"./Bundler\";\nimport {\n ImplMissingDefaultLayerError,\n ImplMissingFunctionsError,\n ImplMissingSpecImportError,\n ImplNotFinalizedError,\n SpecMissingDefaultGroupSpecError,\n SpecRuntimeMismatchError,\n} from \"./CodegenError\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\nimport { removePathExtension } from \"./utils\";\n\nexport interface LeafModule {\n readonly relativePath: string;\n readonly pathSegments: readonly [string, ...string[]];\n readonly groupPathDot: string;\n readonly registryGroupPathDot: string;\n readonly exportName: string;\n readonly runtime: \"Convex\" | \"Node\";\n readonly specImportPath: string;\n}\n\nexport const SPEC_SUFFIX = \".spec.ts\";\nexport const IMPL_SUFFIX = \".impl.ts\";\n\nconst swapModuleSuffix = (\n relativePath: string,\n fromSuffix: string,\n toSuffix: string,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n if (ext !== \".ts\" || !name.endsWith(fromSuffix.slice(0, -\".ts\".length))) {\n return relativePath;\n }\n\n const stem = name.slice(0, -fromSuffix.slice(0, -\".ts\".length).length);\n const nextName = `${stem}${toSuffix.slice(0, -\".ts\".length)}`;\n return dir.length > 0\n ? path.join(dir, `${nextName}${ext}`)\n : `${nextName}${ext}`;\n });\n\nexport const isLeafSpecPath = (relativePath: string) =>\n relativePath.endsWith(SPEC_SUFFIX);\n\nexport const isLeafImplPath = (relativePath: string) =>\n relativePath.endsWith(IMPL_SUFFIX);\n\nexport const exportNameFromModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { name, ext } = path.parse(relativePath);\n if (ext !== \".ts\") {\n return name;\n }\n return name.endsWith(\".spec\") ? name.slice(0, -\".spec\".length) : name;\n });\n\nexport const groupPathFromRelativeModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n const stem =\n ext === \".ts\" && name.endsWith(\".spec\")\n ? name.slice(0, -\".spec\".length)\n : name;\n const dirSegments = Array.filter(\n String.split(dir, path.sep),\n String.isNonEmpty,\n );\n const pathSegments = Array.append(dirSegments, stem) as [\n string,\n ...string[],\n ];\n return {\n pathSegments,\n groupPathDot: Array.join(pathSegments, \".\"),\n };\n });\n\nexport const specImportPathFromGenerated = (specRelativePath: string) =>\n Effect.gen(function* () {\n const withoutExt = yield* removePathExtension(specRelativePath);\n return `../${withoutExt}`;\n });\n\nexport const specPathForImpl = (implRelativePath: string) =>\n swapModuleSuffix(implRelativePath, IMPL_SUFFIX, SPEC_SUFFIX);\n\nexport const implPathForSpec = (specRelativePath: string) =>\n swapModuleSuffix(specRelativePath, SPEC_SUFFIX, IMPL_SUFFIX);\n\nexport const isNodeLeafModule = (relativePath: string) =>\n relativePath.startsWith(\"node/\") || relativePath.startsWith(\"node\\\\\");\n\nexport const toNodeRegistryLeaf = (leaf: LeafModule): LeafModule => ({\n ...leaf,\n pathSegments: [leaf.exportName],\n groupPathDot: leaf.exportName,\n});\n\nexport const registeredFunctionsRelativePath = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n return (\n path.join(\n \"registeredFunctions\",\n ...leaf.pathSegments.slice(leaf.runtime === \"Node\" ? 1 : 0),\n ) + \".ts\"\n );\n });\n\nexport const discoverLeafSpecFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n const excludedFiles = new Set([\"nodeSpec.ts\", \"spec.ts\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafSpecPath(relativePath)) {\n return false;\n }\n\n if (excludedFiles.has(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const discoverLeafImplFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafImplPath(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const toLeafModule = (specRelativePath: string) =>\n Effect.gen(function* () {\n const exportName = yield* exportNameFromModulePath(specRelativePath);\n const { pathSegments, groupPathDot } =\n yield* groupPathFromRelativeModulePath(specRelativePath);\n const specImportPath = yield* specImportPathFromGenerated(specRelativePath);\n const runtime = isNodeLeafModule(specRelativePath) ? \"Node\" : \"Convex\";\n\n return {\n relativePath: specRelativePath,\n pathSegments,\n groupPathDot,\n exportName,\n runtime,\n registryGroupPathDot: runtime === \"Node\" ? exportName : groupPathDot,\n specImportPath,\n } satisfies LeafModule;\n });\n\nconst absoluteModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const confectDirectory = yield* ConfectDirectory.get;\n const path = yield* Path.Path;\n return path.resolve(confectDirectory, relativePath);\n });\n\n/**\n * Validate that the leaf's spec file default-exports a `GroupSpec` whose\n * runtime matches the leaf's location (`Convex` for files outside\n * `confect/node/`, `Node` for files inside it). Returns the validated\n * `GroupSpec` so callers can avoid re-bundling for later inspection (e.g.\n * parent/child name-collision checks at codegen time).\n */\nexport const validateSpec = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const absolutePath = yield* absoluteModulePath(leaf.relativePath);\n const { module } = yield* Bundler.bundle(absolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n\n const groupSpec = module.default;\n\n if (!GroupSpec.isGroupSpec(groupSpec)) {\n return yield* new SpecMissingDefaultGroupSpecError({\n specPath: leaf.relativePath,\n });\n }\n\n if (groupSpec.runtime !== leaf.runtime) {\n return yield* new SpecRuntimeMismatchError({\n specPath: leaf.relativePath,\n expectedRuntime: leaf.runtime,\n actualRuntime: groupSpec.runtime,\n });\n }\n\n return groupSpec;\n });\n\n/**\n * Walk the built `Context` for a `Finalized` `GroupImpl` service value. The\n * lookup is value-shaped (via `GroupImpl.isFinalizedGroupImpl`) so we don't\n * need to know the group's path up front to construct a typed tag for it.\n */\nconst findFinalizedGroupImpl = <S>(\n context: Context.Context<S>,\n): Option.Option<GroupImpl.AnyFinalized> =>\n Array.findFirst(context.unsafeMap.values(), GroupImpl.isFinalizedGroupImpl);\n\n/**\n * Build the impl layer with a fresh `Registry` so each validation is\n * isolated from prior validations' `FunctionImpl.make` writes. The CLI no\n * longer reads the registry directly — `GroupImpl.finalize` snapshots the\n * registered function names onto the produced `Finalized` `GroupImpl`\n * service value — but a fresh `Ref` is still required because the default\n * `Context.Reference` is cached globally and would otherwise accumulate\n * items across impls.\n */\nconst buildImplLayer = (implLayer: Layer.Layer<unknown>) =>\n Effect.gen(function* () {\n const registry = Ref.unsafeMake<Registry.RegistryItems>({});\n return yield* Layer.build(\n implLayer as Layer.Layer<unknown, never, never>,\n ).pipe(Effect.provideService(Registry.Registry, registry));\n }).pipe(Effect.scoped);\n\n/**\n * Validate that the leaf's sibling impl file imports the spec, default-exports\n * a finalized `GroupImpl` layer, and provides a `FunctionImpl` for every\n * function declared by the spec.\n */\nexport const validateImpl = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const implRelativePath = yield* implPathForSpec(leaf.relativePath);\n const implAbsolutePath = yield* absoluteModulePath(implRelativePath);\n const specAbsolutePath = yield* absoluteModulePath(leaf.relativePath);\n\n const bundled = yield* Bundler.bundle(implAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(implRelativePath, error)),\n );\n\n if (\n !(yield* Bundler.directlyImports(\n bundled,\n implAbsolutePath,\n specAbsolutePath,\n ))\n ) {\n return yield* new ImplMissingSpecImportError({\n implPath: implRelativePath,\n expectedSpecPath: leaf.relativePath,\n });\n }\n\n if (!Layer.isLayer(bundled.module.default)) {\n return yield* new ImplMissingDefaultLayerError({\n implPath: implRelativePath,\n });\n }\n\n const { module: specModule } = yield* Bundler.bundle(specAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n const groupSpec = specModule.default as GroupSpec.AnyWithProps;\n const expectedFunctionNames = Object.keys(groupSpec.functions);\n\n const context = yield* buildImplLayer(\n bundled.module.default as Layer.Layer<unknown>,\n );\n const finalizedGroupImpl = yield* Option.match(\n findFinalizedGroupImpl(context),\n {\n onNone: () => new ImplNotFinalizedError({ implPath: implRelativePath }),\n onSome: Effect.succeed,\n },\n );\n\n const registeredSet = new Set(finalizedGroupImpl.registeredFunctionNames);\n const missing = expectedFunctionNames.filter(\n (name) => !registeredSet.has(name),\n );\n\n if (missing.length > 0) {\n return yield* new ImplMissingFunctionsError({\n implPath: implRelativePath,\n groupPath: leaf.groupPathDot,\n missingFunctionNames: missing,\n });\n }\n });\n"],"mappings":"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"}
@@ -28,25 +28,7 @@ const importBindingsForNode = (node) => pipe(node.children, Array.flatMap(import
28
28
  onSome: (binding) => Array.prepend(childBindings, binding)
29
29
  }));
30
30
  const collectImportBindings = (nodes) => pipe(Array.flatMap(nodes, importBindingsForNode), (bindings) => Record.fromIterableBy(bindings, (binding) => binding.importPath), Record.toEntries, Array.map(([, binding]) => binding), Array.sortBy(Order.mapInput(Order.string, (binding) => binding.localName)));
31
- const leafPathsForNode = (node, ancestorSegments) => {
32
- const segments = [...ancestorSegments, node.segment];
33
- const childPaths = Array.flatMap(node.children, (child) => leafPathsForNode(child, segments));
34
- return Option.match(node.importBinding, {
35
- onNone: () => childPaths,
36
- onSome: (binding) => Array.prepend(childPaths, {
37
- binding,
38
- dotPath: segments.join(".")
39
- })
40
- });
41
- };
42
- /**
43
- * Walk the assembly tree and produce one entry per leaf spec, pairing its
44
- * import binding with the full dot-path codegen will register via
45
- * `Spec.addPath`. Ordering matches `collectImportBindings` (sorted by the
46
- * binding's local name) so the generated file is stable across runs.
47
- */
48
- const collectLeafPaths = (nodes) => pipe(Array.flatMap(nodes, (node) => leafPathsForNode(node, [])), Array.sortBy(Order.mapInput(Order.string, (entry) => entry.binding.localName)));
49
31
 
50
32
  //#endregion
51
- export { assemblyNodesFromLeaves, collectImportBindings, collectLeafPaths, partitionByRuntime };
33
+ export { assemblyNodesFromLeaves, collectImportBindings, partitionByRuntime };
52
34
  //# sourceMappingURL=SpecAssemblyNode.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpecAssemblyNode.mjs","names":[],"sources":["../src/SpecAssemblyNode.ts"],"sourcesContent":["import type { LeafModule } from \"./LeafModule\";\nimport { Array, Option, Order, pipe, Record } from \"effect\";\n\nexport interface SpecImportBinding {\n readonly importPath: string;\n readonly exportName: string;\n readonly localName: string;\n}\n\nexport interface SpecAssemblyNode {\n readonly segment: string;\n readonly importBinding: Option.Option<SpecImportBinding>;\n readonly children: ReadonlyArray<SpecAssemblyNode>;\n}\n\nconst importBindingFromLeaf = (leaf: LeafModule): SpecImportBinding => ({\n importPath: leaf.specImportPath,\n exportName: leaf.exportName,\n localName: leaf.pathSegments.join(\"_\"),\n});\n\nconst assemblyNodesAtDepth = (\n leaves: ReadonlyArray<LeafModule>,\n depth: number,\n): ReadonlyArray<SpecAssemblyNode> =>\n pipe(\n Array.groupBy(leaves, (leaf) => leaf.pathSegments[depth]!),\n Record.toEntries,\n Array.sortBy(Order.mapInput(Order.string, ([segment]) => segment)),\n Array.map(([segment, groupLeaves]) => {\n const terminal = Array.findFirst(\n groupLeaves,\n (leaf) => leaf.pathSegments.length === depth + 1,\n );\n const descendants = Array.filter(\n groupLeaves,\n (leaf) => leaf.pathSegments.length > depth + 1,\n );\n return {\n segment,\n importBinding: Option.map(terminal, importBindingFromLeaf),\n children: assemblyNodesAtDepth(descendants, depth + 1),\n };\n }),\n );\n\nexport const assemblyNodesFromLeaves = (\n leaves: ReadonlyArray<LeafModule>,\n): ReadonlyArray<SpecAssemblyNode> => assemblyNodesAtDepth(leaves, 0);\n\nexport const partitionByRuntime = (\n leaves: ReadonlyArray<LeafModule>,\n): {\n readonly convex: ReadonlyArray<LeafModule>;\n readonly node: ReadonlyArray<LeafModule>;\n} => {\n const [node, convex] = Array.partition(\n leaves,\n (leaf) => leaf.runtime === \"Convex\",\n );\n return { convex, node };\n};\n\nconst importBindingsForNode = (\n node: SpecAssemblyNode,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(node.children, Array.flatMap(importBindingsForNode), (childBindings) =>\n Option.match(node.importBinding, {\n onNone: () => childBindings,\n onSome: (binding) => Array.prepend(childBindings, binding),\n }),\n );\n\nexport const collectImportBindings = (\n nodes: ReadonlyArray<SpecAssemblyNode>,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(\n Array.flatMap(nodes, importBindingsForNode),\n (bindings) =>\n Record.fromIterableBy(bindings, (binding) => binding.importPath),\n Record.toEntries,\n Array.map(([, binding]) => binding),\n Array.sortBy(Order.mapInput(Order.string, (binding) => binding.localName)),\n );\n\nexport interface SpecLeafPath {\n readonly binding: SpecImportBinding;\n readonly dotPath: string;\n}\n\nconst leafPathsForNode = (\n node: SpecAssemblyNode,\n ancestorSegments: ReadonlyArray<string>,\n): ReadonlyArray<SpecLeafPath> => {\n const segments = [...ancestorSegments, node.segment];\n const childPaths = Array.flatMap(node.children, (child) =>\n leafPathsForNode(child, segments),\n );\n return Option.match(node.importBinding, {\n onNone: () => childPaths,\n onSome: (binding) =>\n Array.prepend(childPaths, { binding, dotPath: segments.join(\".\") }),\n });\n};\n\n/**\n * Walk the assembly tree and produce one entry per leaf spec, pairing its\n * import binding with the full dot-path codegen will register via\n * `Spec.addPath`. Ordering matches `collectImportBindings` (sorted by the\n * binding's local name) so the generated file is stable across runs.\n */\nexport const collectLeafPaths = (\n nodes: ReadonlyArray<SpecAssemblyNode>,\n): ReadonlyArray<SpecLeafPath> =>\n pipe(\n Array.flatMap(nodes, (node) => leafPathsForNode(node, [])),\n Array.sortBy(\n Order.mapInput(Order.string, (entry) => entry.binding.localName),\n ),\n );\n"],"mappings":";;;AAeA,MAAM,yBAAyB,UAAyC;CACtE,YAAY,KAAK;CACjB,YAAY,KAAK;CACjB,WAAW,KAAK,aAAa,KAAK,IAAI;CACvC;AAED,MAAM,wBACJ,QACA,UAEA,KACE,MAAM,QAAQ,SAAS,SAAS,KAAK,aAAa,OAAQ,EAC1D,OAAO,WACP,MAAM,OAAO,MAAM,SAAS,MAAM,SAAS,CAAC,aAAa,QAAQ,CAAC,EAClE,MAAM,KAAK,CAAC,SAAS,iBAAiB;CACpC,MAAM,WAAW,MAAM,UACrB,cACC,SAAS,KAAK,aAAa,WAAW,QAAQ,EAChD;CACD,MAAM,cAAc,MAAM,OACxB,cACC,SAAS,KAAK,aAAa,SAAS,QAAQ,EAC9C;AACD,QAAO;EACL;EACA,eAAe,OAAO,IAAI,UAAU,sBAAsB;EAC1D,UAAU,qBAAqB,aAAa,QAAQ,EAAE;EACvD;EACD,CACH;AAEH,MAAa,2BACX,WACoC,qBAAqB,QAAQ,EAAE;AAErE,MAAa,sBACX,WAIG;CACH,MAAM,CAAC,MAAM,UAAU,MAAM,UAC3B,SACC,SAAS,KAAK,YAAY,SAC5B;AACD,QAAO;EAAE;EAAQ;EAAM;;AAGzB,MAAM,yBACJ,SAEA,KAAK,KAAK,UAAU,MAAM,QAAQ,sBAAsB,GAAG,kBACzD,OAAO,MAAM,KAAK,eAAe;CAC/B,cAAc;CACd,SAAS,YAAY,MAAM,QAAQ,eAAe,QAAQ;CAC3D,CAAC,CACH;AAEH,MAAa,yBACX,UAEA,KACE,MAAM,QAAQ,OAAO,sBAAsB,GAC1C,aACC,OAAO,eAAe,WAAW,YAAY,QAAQ,WAAW,EAClE,OAAO,WACP,MAAM,KAAK,GAAG,aAAa,QAAQ,EACnC,MAAM,OAAO,MAAM,SAAS,MAAM,SAAS,YAAY,QAAQ,UAAU,CAAC,CAC3E;AAOH,MAAM,oBACJ,MACA,qBACgC;CAChC,MAAM,WAAW,CAAC,GAAG,kBAAkB,KAAK,QAAQ;CACpD,MAAM,aAAa,MAAM,QAAQ,KAAK,WAAW,UAC/C,iBAAiB,OAAO,SAAS,CAClC;AACD,QAAO,OAAO,MAAM,KAAK,eAAe;EACtC,cAAc;EACd,SAAS,YACP,MAAM,QAAQ,YAAY;GAAE;GAAS,SAAS,SAAS,KAAK,IAAI;GAAE,CAAC;EACtE,CAAC;;;;;;;;AASJ,MAAa,oBACX,UAEA,KACE,MAAM,QAAQ,QAAQ,SAAS,iBAAiB,MAAM,EAAE,CAAC,CAAC,EAC1D,MAAM,OACJ,MAAM,SAAS,MAAM,SAAS,UAAU,MAAM,QAAQ,UAAU,CACjE,CACF"}
1
+ {"version":3,"file":"SpecAssemblyNode.mjs","names":[],"sources":["../src/SpecAssemblyNode.ts"],"sourcesContent":["import type { LeafModule } from \"./LeafModule\";\nimport { Array, Option, Order, pipe, Record } from \"effect\";\n\nexport interface SpecImportBinding {\n readonly importPath: string;\n readonly exportName: string;\n readonly localName: string;\n}\n\nexport interface SpecAssemblyNode {\n readonly segment: string;\n readonly importBinding: Option.Option<SpecImportBinding>;\n readonly children: ReadonlyArray<SpecAssemblyNode>;\n}\n\nconst importBindingFromLeaf = (leaf: LeafModule): SpecImportBinding => ({\n importPath: leaf.specImportPath,\n exportName: leaf.exportName,\n localName: leaf.pathSegments.join(\"_\"),\n});\n\nconst assemblyNodesAtDepth = (\n leaves: ReadonlyArray<LeafModule>,\n depth: number,\n): ReadonlyArray<SpecAssemblyNode> =>\n pipe(\n Array.groupBy(leaves, (leaf) => leaf.pathSegments[depth]!),\n Record.toEntries,\n Array.sortBy(Order.mapInput(Order.string, ([segment]) => segment)),\n Array.map(([segment, groupLeaves]) => {\n const terminal = Array.findFirst(\n groupLeaves,\n (leaf) => leaf.pathSegments.length === depth + 1,\n );\n const descendants = Array.filter(\n groupLeaves,\n (leaf) => leaf.pathSegments.length > depth + 1,\n );\n return {\n segment,\n importBinding: Option.map(terminal, importBindingFromLeaf),\n children: assemblyNodesAtDepth(descendants, depth + 1),\n };\n }),\n );\n\nexport const assemblyNodesFromLeaves = (\n leaves: ReadonlyArray<LeafModule>,\n): ReadonlyArray<SpecAssemblyNode> => assemblyNodesAtDepth(leaves, 0);\n\nexport const partitionByRuntime = (\n leaves: ReadonlyArray<LeafModule>,\n): {\n readonly convex: ReadonlyArray<LeafModule>;\n readonly node: ReadonlyArray<LeafModule>;\n} => {\n const [node, convex] = Array.partition(\n leaves,\n (leaf) => leaf.runtime === \"Convex\",\n );\n return { convex, node };\n};\n\nconst importBindingsForNode = (\n node: SpecAssemblyNode,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(node.children, Array.flatMap(importBindingsForNode), (childBindings) =>\n Option.match(node.importBinding, {\n onNone: () => childBindings,\n onSome: (binding) => Array.prepend(childBindings, binding),\n }),\n );\n\nexport const collectImportBindings = (\n nodes: ReadonlyArray<SpecAssemblyNode>,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(\n Array.flatMap(nodes, importBindingsForNode),\n (bindings) =>\n Record.fromIterableBy(bindings, (binding) => binding.importPath),\n Record.toEntries,\n Array.map(([, binding]) => binding),\n Array.sortBy(Order.mapInput(Order.string, (binding) => binding.localName)),\n );\n"],"mappings":";;;AAeA,MAAM,yBAAyB,UAAyC;CACtE,YAAY,KAAK;CACjB,YAAY,KAAK;CACjB,WAAW,KAAK,aAAa,KAAK,IAAI;CACvC;AAED,MAAM,wBACJ,QACA,UAEA,KACE,MAAM,QAAQ,SAAS,SAAS,KAAK,aAAa,OAAQ,EAC1D,OAAO,WACP,MAAM,OAAO,MAAM,SAAS,MAAM,SAAS,CAAC,aAAa,QAAQ,CAAC,EAClE,MAAM,KAAK,CAAC,SAAS,iBAAiB;CACpC,MAAM,WAAW,MAAM,UACrB,cACC,SAAS,KAAK,aAAa,WAAW,QAAQ,EAChD;CACD,MAAM,cAAc,MAAM,OACxB,cACC,SAAS,KAAK,aAAa,SAAS,QAAQ,EAC9C;AACD,QAAO;EACL;EACA,eAAe,OAAO,IAAI,UAAU,sBAAsB;EAC1D,UAAU,qBAAqB,aAAa,QAAQ,EAAE;EACvD;EACD,CACH;AAEH,MAAa,2BACX,WACoC,qBAAqB,QAAQ,EAAE;AAErE,MAAa,sBACX,WAIG;CACH,MAAM,CAAC,MAAM,UAAU,MAAM,UAC3B,SACC,SAAS,KAAK,YAAY,SAC5B;AACD,QAAO;EAAE;EAAQ;EAAM;;AAGzB,MAAM,yBACJ,SAEA,KAAK,KAAK,UAAU,MAAM,QAAQ,sBAAsB,GAAG,kBACzD,OAAO,MAAM,KAAK,eAAe;CAC/B,cAAc;CACd,SAAS,YAAY,MAAM,QAAQ,eAAe,QAAQ;CAC3D,CAAC,CACH;AAEH,MAAa,yBACX,UAEA,KACE,MAAM,QAAQ,OAAO,sBAAsB,GAC1C,aACC,OAAO,eAAe,WAAW,YAAY,QAAQ,WAAW,EAClE,OAAO,WACP,MAAM,KAAK,GAAG,aAAa,QAAQ,EACnC,MAAM,OAAO,MAAM,SAAS,MAAM,SAAS,YAAY,QAAQ,UAAU,CAAC,CAC3E"}
@@ -0,0 +1,90 @@
1
+ import { fromBundlerError } from "./BuildError.mjs";
2
+ import { bundle } from "./Bundler.mjs";
3
+ import { DuplicateTableNameError, InvalidTableDefaultExportError, InvalidTableFilenameError } from "./CodegenError.mjs";
4
+ import { ConfectDirectory } from "./ConfectDirectory.mjs";
5
+ import { Array, Effect, Order, pipe } from "effect";
6
+ import { Identifier } from "@confect/core";
7
+ import { FileSystem, Path } from "@effect/platform";
8
+ import * as Table from "@confect/server/Table";
9
+
10
+ //#region src/TableModule.ts
11
+ const TABLES_DIRNAME = "tables";
12
+ const tableNameFromRelativePath = (relativePath) => Effect.gen(function* () {
13
+ const { name } = (yield* Path.Path).parse(relativePath);
14
+ return name;
15
+ });
16
+ const listTableFiles = Effect.gen(function* () {
17
+ const fs = yield* FileSystem.FileSystem;
18
+ const path = yield* Path.Path;
19
+ const confectDirectory = yield* ConfectDirectory.get;
20
+ const tablesDirectory = path.join(confectDirectory, TABLES_DIRNAME);
21
+ if (!(yield* fs.exists(tablesDirectory))) return [];
22
+ return pipe(yield* fs.readDirectory(tablesDirectory, { recursive: true }), Array.filter((p) => p.endsWith(".ts") && !p.endsWith(".test.ts")), Array.map((p) => path.join(TABLES_DIRNAME, p)));
23
+ });
24
+ const byTableName = Order.mapInput(Order.string, (tableModule) => tableModule.tableName);
25
+ /**
26
+ * Discover every `confect/tables/**\/*.ts` module by listing the directory.
27
+ * Validates that each filename is a legal table identifier — the table name is
28
+ * derived from the file basename, so the filename must be a valid JavaScript
29
+ * identifier with no leading underscore (Convex reserves `_<name>` for system
30
+ * tables).
31
+ *
32
+ * This step does *not* bundle the table modules. It runs early in the codegen
33
+ * pipeline so the `_generated/id.ts` constructor can be emitted *before* any
34
+ * user-authored table is bundled (those modules import from `_generated/id.ts`
35
+ * for cross-table refs).
36
+ *
37
+ * A missing `confect/tables/` directory is allowed and produces an empty list.
38
+ *
39
+ * Fails with {@link InvalidTableFilenameError} if any filename is not a valid
40
+ * table identifier, or {@link DuplicateTableNameError} if two files resolve to
41
+ * the same table name (the directory is scanned recursively but names are
42
+ * derived from the basename alone, so `tables/a/notes.ts` and
43
+ * `tables/b/notes.ts` would collide).
44
+ */
45
+ const discover = Effect.gen(function* () {
46
+ const relativePaths = yield* listTableFiles;
47
+ const sorted = pipe(yield* Effect.forEach(relativePaths, (relativePath) => Effect.gen(function* () {
48
+ const tableName = yield* tableNameFromRelativePath(relativePath);
49
+ yield* Effect.try({
50
+ try: () => Identifier.validateConfectTableIdentifier(tableName),
51
+ catch: (e) => new InvalidTableFilenameError({
52
+ tablePath: relativePath,
53
+ reason: e instanceof Error ? e.message : String(e)
54
+ })
55
+ });
56
+ return {
57
+ relativePath,
58
+ tableName
59
+ };
60
+ }), { concurrency: "unbounded" }), Array.sortBy(byTableName));
61
+ const collisions = Object.entries(Array.groupBy(sorted, (tableModule) => tableModule.tableName)).filter(([, group]) => group.length > 1).map(([tableName, group]) => ({
62
+ tableName,
63
+ tablePaths: Array.map(group, (tableModule) => tableModule.relativePath)
64
+ }));
65
+ if (collisions.length > 0) return yield* new DuplicateTableNameError({ collisions });
66
+ return sorted;
67
+ });
68
+ /**
69
+ * Bundle every discovered table module and verify that its default export is
70
+ * an {@link Table.UnnamedTable} (the result of `Table.make(...)` before a
71
+ * name has been bound). Fails with {@link InvalidTableDefaultExportError} if
72
+ * any module's default export is missing or has the wrong shape.
73
+ *
74
+ * Must run *after* `_generated/id.ts` has been emitted, because user-authored
75
+ * table modules typically `import { Id } from "../_generated/id"` for
76
+ * cross-table references.
77
+ */
78
+ const validate = (tableModules) => Effect.gen(function* () {
79
+ const path = yield* Path.Path;
80
+ const confectDirectory = yield* ConfectDirectory.get;
81
+ yield* Effect.forEach(tableModules, ({ relativePath }) => Effect.gen(function* () {
82
+ const absolutePath = path.resolve(confectDirectory, relativePath);
83
+ const { module } = yield* bundle(absolutePath).pipe(Effect.mapError((error) => fromBundlerError(relativePath, error)));
84
+ if (!Table.isUnnamedTable(module.default)) return yield* new InvalidTableDefaultExportError({ tablePath: relativePath });
85
+ }), { concurrency: "unbounded" });
86
+ });
87
+
88
+ //#endregion
89
+ export { TABLES_DIRNAME, discover, validate };
90
+ //# sourceMappingURL=TableModule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableModule.mjs","names":["Bundler.bundle"],"sources":["../src/TableModule.ts"],"sourcesContent":["import { Identifier } from \"@confect/core\";\nimport * as Table from \"@confect/server/Table\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport { Array, Effect, Order, pipe } from \"effect\";\nimport { fromBundlerError } from \"./BuildError\";\nimport * as Bundler from \"./Bundler\";\nimport {\n DuplicateTableNameError,\n InvalidTableDefaultExportError,\n InvalidTableFilenameError,\n} from \"./CodegenError\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\n\nexport const TABLES_DIRNAME = \"tables\";\n\n/**\n * Discovered metadata for a single user-authored table module under\n * `confect/tables/`.\n *\n * - `relativePath` — path from `confect/` to the file (e.g. `tables/notes.ts`).\n * - `tableName` — the file basename (e.g. `notes`). This is also the import\n * binding used in generated files, and the table name surfaced to Convex.\n */\nexport interface TableModule {\n readonly relativePath: string;\n readonly tableName: string;\n}\n\nconst tableNameFromRelativePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { name } = path.parse(relativePath);\n return name;\n });\n\nconst listTableFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n const tablesDirectory = path.join(confectDirectory, TABLES_DIRNAME);\n\n if (!(yield* fs.exists(tablesDirectory))) {\n return [] as ReadonlyArray<string>;\n }\n\n const allPaths = yield* fs.readDirectory(tablesDirectory, {\n recursive: true,\n });\n\n return pipe(\n allPaths,\n Array.filter((p) => p.endsWith(\".ts\") && !p.endsWith(\".test.ts\")),\n Array.map((p) => path.join(TABLES_DIRNAME, p)),\n );\n});\n\nconst byTableName = Order.mapInput(\n Order.string,\n (tableModule: TableModule) => tableModule.tableName,\n);\n\n/**\n * Discover every `confect/tables/**\\/*.ts` module by listing the directory.\n * Validates that each filename is a legal table identifier — the table name is\n * derived from the file basename, so the filename must be a valid JavaScript\n * identifier with no leading underscore (Convex reserves `_<name>` for system\n * tables).\n *\n * This step does *not* bundle the table modules. It runs early in the codegen\n * pipeline so the `_generated/id.ts` constructor can be emitted *before* any\n * user-authored table is bundled (those modules import from `_generated/id.ts`\n * for cross-table refs).\n *\n * A missing `confect/tables/` directory is allowed and produces an empty list.\n *\n * Fails with {@link InvalidTableFilenameError} if any filename is not a valid\n * table identifier, or {@link DuplicateTableNameError} if two files resolve to\n * the same table name (the directory is scanned recursively but names are\n * derived from the basename alone, so `tables/a/notes.ts` and\n * `tables/b/notes.ts` would collide).\n */\nexport const discover = Effect.gen(function* () {\n const relativePaths = yield* listTableFiles;\n\n const tableModules = yield* Effect.forEach(\n relativePaths,\n (relativePath) =>\n Effect.gen(function* () {\n const tableName = yield* tableNameFromRelativePath(relativePath);\n yield* Effect.try({\n try: () => Identifier.validateConfectTableIdentifier(tableName),\n catch: (e) =>\n new InvalidTableFilenameError({\n tablePath: relativePath,\n reason: e instanceof Error ? e.message : String(e),\n }),\n });\n return { relativePath, tableName } satisfies TableModule;\n }),\n { concurrency: \"unbounded\" },\n );\n\n const sorted = pipe(tableModules, Array.sortBy(byTableName));\n\n const collisions = Object.entries(\n Array.groupBy(sorted, (tableModule) => tableModule.tableName),\n )\n .filter(([, group]) => group.length > 1)\n .map(([tableName, group]) => ({\n tableName,\n tablePaths: Array.map(group, (tableModule) => tableModule.relativePath),\n }));\n\n if (collisions.length > 0) {\n return yield* new DuplicateTableNameError({ collisions });\n }\n\n return sorted;\n});\n\n/**\n * Bundle every discovered table module and verify that its default export is\n * an {@link Table.UnnamedTable} (the result of `Table.make(...)` before a\n * name has been bound). Fails with {@link InvalidTableDefaultExportError} if\n * any module's default export is missing or has the wrong shape.\n *\n * Must run *after* `_generated/id.ts` has been emitted, because user-authored\n * table modules typically `import { Id } from \"../_generated/id\"` for\n * cross-table references.\n */\nexport const validate = (tableModules: ReadonlyArray<TableModule>) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n yield* Effect.forEach(\n tableModules,\n ({ relativePath }) =>\n Effect.gen(function* () {\n const absolutePath = path.resolve(confectDirectory, relativePath);\n const { module } = yield* Bundler.bundle(absolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(relativePath, error)),\n );\n\n if (!Table.isUnnamedTable(module.default)) {\n return yield* new InvalidTableDefaultExportError({\n tablePath: relativePath,\n });\n }\n }),\n { concurrency: \"unbounded\" },\n );\n });\n"],"mappings":";;;;;;;;;;AAaA,MAAa,iBAAiB;AAe9B,MAAM,6BAA6B,iBACjC,OAAO,IAAI,aAAa;CAEtB,MAAM,EAAE,UADK,OAAO,KAAK,MACH,MAAM,aAAa;AACzC,QAAO;EACP;AAEJ,MAAM,iBAAiB,OAAO,IAAI,aAAa;CAC7C,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,mBAAmB,OAAO,iBAAiB;CACjD,MAAM,kBAAkB,KAAK,KAAK,kBAAkB,eAAe;AAEnE,KAAI,EAAE,OAAO,GAAG,OAAO,gBAAgB,EACrC,QAAO,EAAE;AAOX,QAAO,KAJU,OAAO,GAAG,cAAc,iBAAiB,EACxD,WAAW,MACZ,CAAC,EAIA,MAAM,QAAQ,MAAM,EAAE,SAAS,MAAM,IAAI,CAAC,EAAE,SAAS,WAAW,CAAC,EACjE,MAAM,KAAK,MAAM,KAAK,KAAK,gBAAgB,EAAE,CAAC,CAC/C;EACD;AAEF,MAAM,cAAc,MAAM,SACxB,MAAM,SACL,gBAA6B,YAAY,UAC3C;;;;;;;;;;;;;;;;;;;;;AAsBD,MAAa,WAAW,OAAO,IAAI,aAAa;CAC9C,MAAM,gBAAgB,OAAO;CAoB7B,MAAM,SAAS,KAlBM,OAAO,OAAO,QACjC,gBACC,iBACC,OAAO,IAAI,aAAa;EACtB,MAAM,YAAY,OAAO,0BAA0B,aAAa;AAChE,SAAO,OAAO,IAAI;GAChB,WAAW,WAAW,+BAA+B,UAAU;GAC/D,QAAQ,MACN,IAAI,0BAA0B;IAC5B,WAAW;IACX,QAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACnD,CAAC;GACL,CAAC;AACF,SAAO;GAAE;GAAc;GAAW;GAClC,EACJ,EAAE,aAAa,aAAa,CAC7B,EAEiC,MAAM,OAAO,YAAY,CAAC;CAE5D,MAAM,aAAa,OAAO,QACxB,MAAM,QAAQ,SAAS,gBAAgB,YAAY,UAAU,CAC9D,CACE,QAAQ,GAAG,WAAW,MAAM,SAAS,EAAE,CACvC,KAAK,CAAC,WAAW,YAAY;EAC5B;EACA,YAAY,MAAM,IAAI,QAAQ,gBAAgB,YAAY,aAAa;EACxE,EAAE;AAEL,KAAI,WAAW,SAAS,EACtB,QAAO,OAAO,IAAI,wBAAwB,EAAE,YAAY,CAAC;AAG3D,QAAO;EACP;;;;;;;;;;;AAYF,MAAa,YAAY,iBACvB,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,mBAAmB,OAAO,iBAAiB;AAEjD,QAAO,OAAO,QACZ,eACC,EAAE,mBACD,OAAO,IAAI,aAAa;EACtB,MAAM,eAAe,KAAK,QAAQ,kBAAkB,aAAa;EACjE,MAAM,EAAE,WAAW,OAAOA,OAAe,aAAa,CAAC,KACrD,OAAO,UAAU,UAAU,iBAAiB,cAAc,MAAM,CAAC,CAClE;AAED,MAAI,CAAC,MAAM,eAAe,OAAO,QAAQ,CACvC,QAAO,OAAO,IAAI,+BAA+B,EAC/C,WAAW,cACZ,CAAC;GAEJ,EACJ,EAAE,aAAa,aAAa,CAC7B;EACD"}