@confect/cli 9.0.0-next.1 → 9.0.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +337 -1
- package/dist/BuildError.mjs +9 -2
- package/dist/BuildError.mjs.map +1 -1
- package/dist/Bundler.mjs +67 -57
- package/dist/Bundler.mjs.map +1 -1
- package/dist/CodeBlockWriter.mjs +1 -1
- package/dist/CodeBlockWriter.mjs.map +1 -1
- package/dist/CodegenError.mjs +29 -21
- package/dist/CodegenError.mjs.map +1 -1
- package/dist/ConfectDirectory.mjs +5 -2
- package/dist/ConfectDirectory.mjs.map +1 -1
- package/dist/ConvexDirectory.mjs +6 -2
- package/dist/ConvexDirectory.mjs.map +1 -1
- package/dist/FunctionPath.mjs +1 -1
- package/dist/FunctionPath.mjs.map +1 -1
- package/dist/FunctionPaths.mjs +5 -1
- package/dist/FunctionPaths.mjs.map +1 -1
- package/dist/GroupPath.mjs +9 -2
- package/dist/GroupPath.mjs.map +1 -1
- package/dist/GroupPaths.mjs +1 -1
- package/dist/GroupPaths.mjs.map +1 -1
- package/dist/LeafModule.mjs +19 -26
- package/dist/LeafModule.mjs.map +1 -1
- package/dist/ProjectRoot.mjs +8 -3
- package/dist/ProjectRoot.mjs.map +1 -1
- package/dist/SpecAssemblyNode.mjs +9 -11
- package/dist/SpecAssemblyNode.mjs.map +1 -1
- package/dist/TableModule.mjs +94 -0
- package/dist/TableModule.mjs.map +1 -0
- package/dist/cliApp.mjs +1 -1
- package/dist/cliApp.mjs.map +1 -1
- package/dist/confect/codegen.mjs +203 -142
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/confect/dev.mjs +36 -17
- package/dist/confect/dev.mjs.map +1 -1
- package/dist/confect.mjs +2 -2
- package/dist/confect.mjs.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/log.mjs +9 -4
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/templates.mjs +132 -45
- package/dist/templates.mjs.map +1 -1
- package/dist/utils.mjs +42 -22
- package/dist/utils.mjs.map +1 -1
- package/package.json +33 -50
- package/dist/index.d.mts +0 -1
- package/src/BuildError.ts +0 -210
- package/src/Bundler.ts +0 -144
- package/src/CodeBlockWriter.ts +0 -65
- package/src/CodegenError.ts +0 -376
- package/src/ConfectDirectory.ts +0 -42
- package/src/ConvexDirectory.ts +0 -68
- package/src/FunctionPath.ts +0 -27
- package/src/FunctionPaths.ts +0 -103
- package/src/GroupPath.ts +0 -118
- package/src/GroupPaths.ts +0 -7
- package/src/LeafModule.ts +0 -317
- package/src/ProjectRoot.ts +0 -50
- package/src/SpecAssemblyNode.ts +0 -82
- package/src/cliApp.ts +0 -8
- package/src/confect/codegen.ts +0 -710
- package/src/confect/dev.ts +0 -749
- package/src/confect.ts +0 -19
- package/src/index.ts +0 -22
- package/src/log.ts +0 -104
- package/src/templates.ts +0 -482
- package/src/utils.ts +0 -429
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,338 @@
|
|
|
1
1
|
# @confect/cli
|
|
2
2
|
|
|
3
|
+
## 9.0.0-next.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ecb3b69: Package `@confect/cli` as a binary-only package: the `.` export—along with the `main`, `module`, and `types` fields—is removed, and no type declarations are published.
|
|
8
|
+
|
|
9
|
+
`@confect/cli` has no public API; its entry module is the `confect` bin script itself. The removed `.` export was both broken (it pointed at `./dist/index.js`, while the build emits `./dist/index.mjs`) and hazardous (importing the entry module would have executed the CLI, since the script runs at module top level). Nothing changes for the supported usage: running the `confect` binary.
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [9eec71c]
|
|
12
|
+
- @confect/core@9.0.0-next.10
|
|
13
|
+
- @confect/server@9.0.0-next.10
|
|
14
|
+
|
|
15
|
+
## 9.0.0-next.9
|
|
16
|
+
|
|
17
|
+
### Major Changes
|
|
18
|
+
|
|
19
|
+
- 4894959: Make Node-runtime functions first-class and remove the separate `node` namespace.
|
|
20
|
+
|
|
21
|
+
A function group's runtime is now declared solely by its spec — `GroupSpec.makeNode()` for a Node action group, `GroupSpec.make()` for a Convex group — exactly like vanilla Convex's per-file `"use node"` directive. The `confect/node/` directory is no longer special: Node specs/impls are ordinary colocated `.spec.ts`/`.impl.ts` pairs that can live anywhere in `confect/`, and codegen emits the `"use node"` directive into the generated `convex/` module based on the spec. This is safe because v9's per-group registries already isolate each Convex function's bundle from every other group's impl, so Node-only code can no longer leak into a Convex-runtime bundle regardless of namespace.
|
|
22
|
+
|
|
23
|
+
### Why
|
|
24
|
+
|
|
25
|
+
The `node` namespace existed only because the pre-v9 architecture aggregated every function's impl into one module that all generated `convex/` modules imported; Node functions had to be quarantined into a separate spec/impl/registry tree so Convex-runtime functions wouldn't transitively import Node-only code. v9's per-group isolation removed that constraint, so the namespace was no longer load-bearing — only ergonomic overhead that diverged from vanilla Convex (which identifies Node modules per-file, with no directory requirement).
|
|
26
|
+
|
|
27
|
+
### Breaking changes
|
|
28
|
+
|
|
29
|
+
- **API namespace removed.** A Node group at `confect/email.spec.ts` is now referenced as `refs.public.email.send` instead of `refs.public.node.email.send`. Node groups are ordinary groups in the refs tree, nesting preserved like any other group.
|
|
30
|
+
- **Generated layout changed.** Node modules are emitted at `convex/<path>.ts` (carrying `"use node"`) instead of `convex/node/<path>.ts`, and their registries at `confect/_generated/registeredFunctions/<path>.ts`. The single assembled `confect/_generated/spec.ts` now contains every group regardless of runtime; `confect/_generated/nodeSpec.ts` is no longer generated (codegen deletes any stale copy on upgrade).
|
|
31
|
+
- **`@confect/core` API.** Removed `Spec.makeNode`, `Spec.merge`, and `Spec.isConvexSpec`/`Spec.isNodeSpec`. `Spec` is now a single mixed-runtime container (`Spec.make()` accepts groups of any runtime). `Refs.make(spec)` takes a single argument (the unified spec) instead of `(convexSpec, nodeSpec)`. `GroupSpec.makeNode()`/`makeNodeAt()` and `FunctionSpec.publicNodeAction()`/`internalNodeAction()` are unchanged; `GroupSpec` subgroups may now be of any runtime (a group is just a namespace for its children).
|
|
32
|
+
|
|
33
|
+
### Migration
|
|
34
|
+
|
|
35
|
+
1. Move any `confect/node/<path>.spec.ts`/`.impl.ts` files to wherever you want them under `confect/` (e.g. `confect/<path>.spec.ts`); the `node/` directory has no special meaning anymore. Their specs already use `GroupSpec.makeNode()`, so no spec-body change is needed — only fix the impl's relative import of `_generated/schema` if its depth changed.
|
|
36
|
+
2. Update call sites to drop the `node` segment: `refs.public.node.<group>.<fn>` → `refs.public.<group>.<fn>`.
|
|
37
|
+
3. Replace `Refs.make(spec, nodeSpec)` with `Refs.make(spec)` (codegen does this for `_generated/refs.ts` automatically).
|
|
38
|
+
4. Run `confect codegen`. The `convex/` tree and `confect/_generated/` are re-emitted; the stale `_generated/nodeSpec.ts` is removed.
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- Updated dependencies [4894959]
|
|
43
|
+
- @confect/core@9.0.0-next.9
|
|
44
|
+
- @confect/server@9.0.0-next.9
|
|
45
|
+
|
|
46
|
+
## 9.0.0-next.8
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- 3fec285: Import Effect from its submodule paths internally to shrink per-function cold-start bundles.
|
|
51
|
+
|
|
52
|
+
Confect's packages now import Effect modules from their submodule paths (`import * as Schema from "effect/Schema"`) instead of the `"effect"` barrel (`import { Schema } from "effect"`).
|
|
53
|
+
|
|
54
|
+
### Why
|
|
55
|
+
|
|
56
|
+
A barrel import of a namespace re-export defeats esbuild's tree-shaking: accessing `Schema.X` from `import { Schema } from "effect"` retains the _entire_ `Schema` namespace, because the bundler can't prune property access on the barrel's `export * as Schema`. So every Convex function's cold-start bundle was pulling all of `effect/Schema` and `effect/Stream` — and, transitively through Schema's `Arbitrary`, `fast-check` — whether the function used them or not.
|
|
57
|
+
|
|
58
|
+
Importing from the submodule path tree-shakes normally. On a minimal function this cut the bundle esbuild produces by ~54% (the `effect/Schema` module alone by ~75%) and its cold-start module-evaluation time by ~35%, with `fast-check` dropped entirely. This is also the import style Effect v4 recommends, so it's forward-compatible. A `no-restricted-imports` ESLint rule now enforces it across the codebase (type-only imports and `@effect/vitest` are exempt).
|
|
59
|
+
|
|
60
|
+
No API changes — your existing code keeps working.
|
|
61
|
+
|
|
62
|
+
### Getting the full win in your own code
|
|
63
|
+
|
|
64
|
+
This change shrinks the Confect code in every function bundle, but a function's bundle also includes your own `confect/tables/*` and `*.spec.ts` files. esbuild retains the union across all importers, so a single barrel import anywhere in a function's module graph re-pins the whole `effect/Schema` namespace and undoes the reduction. To get the full bundle/cold-start savings, import Effect from its submodule paths in your own Confect files too:
|
|
65
|
+
|
|
66
|
+
```diff
|
|
67
|
+
- import { Schema } from "effect";
|
|
68
|
+
+ import * as Schema from "effect/Schema";
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Bare helpers (`pipe`, `flow`, `identity`) come from `"effect/Function"`.
|
|
72
|
+
|
|
73
|
+
- Updated dependencies [3fec285]
|
|
74
|
+
- @confect/core@9.0.0-next.8
|
|
75
|
+
- @confect/server@9.0.0-next.8
|
|
76
|
+
|
|
77
|
+
## 9.0.0-next.7
|
|
78
|
+
|
|
79
|
+
### Patch Changes
|
|
80
|
+
|
|
81
|
+
- Updated dependencies [5d19484]
|
|
82
|
+
- @confect/core@9.0.0-next.7
|
|
83
|
+
- @confect/server@9.0.0-next.7
|
|
84
|
+
|
|
85
|
+
## 9.0.0-next.6
|
|
86
|
+
|
|
87
|
+
### Major Changes
|
|
88
|
+
|
|
89
|
+
- 46045a9: Reduce per-function cold-start cost: make `FunctionSpec` schemas lazy and keep each Convex function's bundle scoped to its own group.
|
|
90
|
+
|
|
91
|
+
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.
|
|
92
|
+
|
|
93
|
+
### Lazy `FunctionSpec` schemas
|
|
94
|
+
|
|
95
|
+
`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.
|
|
96
|
+
|
|
97
|
+
Migration — wrap each schema in `() =>`:
|
|
98
|
+
|
|
99
|
+
```diff
|
|
100
|
+
FunctionSpec.publicQuery({
|
|
101
|
+
name: "list",
|
|
102
|
+
- args: Schema.Struct({}),
|
|
103
|
+
- returns: Schema.Array(notes.Doc),
|
|
104
|
+
+ args: () => Schema.Struct({}),
|
|
105
|
+
+ returns: () => Schema.Array(notes.Doc),
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Impls take the `DatabaseSchema`, and group paths resolve impl-side
|
|
110
|
+
|
|
111
|
+
`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.
|
|
112
|
+
|
|
113
|
+
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).
|
|
114
|
+
|
|
115
|
+
Migration — in each `*.impl.ts`, import the database schema and pass it where you passed `api` / `nodeApi`:
|
|
116
|
+
|
|
117
|
+
```diff
|
|
118
|
+
- import api from "../_generated/api"; // (or nodeApi from "../_generated/nodeApi")
|
|
119
|
+
+ import databaseSchema from "../_generated/schema";
|
|
120
|
+
|
|
121
|
+
- const insert = FunctionImpl.make(api, notes, "insert", handler);
|
|
122
|
+
+ const insert = FunctionImpl.make(databaseSchema, notes, "insert", handler);
|
|
123
|
+
|
|
124
|
+
- export default GroupImpl.make(api, notes).pipe(Layer.provide(insert), GroupImpl.finalize);
|
|
125
|
+
+ export default GroupImpl.make(databaseSchema, notes).pipe(Layer.provide(insert), GroupImpl.finalize);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
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.
|
|
129
|
+
|
|
130
|
+
### `buildForGroup` and the generated registries
|
|
131
|
+
|
|
132
|
+
`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.
|
|
133
|
+
|
|
134
|
+
### `_generated/api.ts` / `nodeApi.ts` are no longer emitted, and `Api` is removed
|
|
135
|
+
|
|
136
|
+
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.
|
|
137
|
+
|
|
138
|
+
### Net effect
|
|
139
|
+
|
|
140
|
+
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.
|
|
141
|
+
|
|
142
|
+
- 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.
|
|
143
|
+
|
|
144
|
+
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.
|
|
145
|
+
|
|
146
|
+
Codegen now scans `confect/tables/*.ts` (every file must default-export a `Table`) and emits two siblings:
|
|
147
|
+
|
|
148
|
+
- `confect/_generated/schema.ts` — the runtime `DatabaseSchema`, consumed by `_generated/api.ts`. Imports `@confect/server` but never `convex/server`.
|
|
149
|
+
- `confect/_generated/convexSchema.ts` — the Convex deploy `SchemaDefinition`, re-exported one-line from `convex/schema.ts`. Imports `convex/server` but never `@confect/server`.
|
|
150
|
+
|
|
151
|
+
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.
|
|
152
|
+
|
|
153
|
+
### Filename-derived table names
|
|
154
|
+
|
|
155
|
+
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.
|
|
156
|
+
|
|
157
|
+
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.
|
|
158
|
+
|
|
159
|
+
Codegen now emits two new sets of files alongside `_generated/schema.ts` and `_generated/convexSchema.ts`:
|
|
160
|
+
|
|
161
|
+
- `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")`.
|
|
162
|
+
- `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`.
|
|
163
|
+
|
|
164
|
+
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`).
|
|
165
|
+
|
|
166
|
+
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`).
|
|
167
|
+
|
|
168
|
+
### Lazy per-table schema construction
|
|
169
|
+
|
|
170
|
+
`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.
|
|
171
|
+
|
|
172
|
+
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.
|
|
173
|
+
|
|
174
|
+
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).
|
|
175
|
+
|
|
176
|
+
### Migration
|
|
177
|
+
|
|
178
|
+
1. Delete your `confect/schema.ts`. Codegen will refuse to run while a stray copy is present.
|
|
179
|
+
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.
|
|
180
|
+
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`:
|
|
181
|
+
|
|
182
|
+
```diff
|
|
183
|
+
- import { GenericId } from "@confect/core";
|
|
184
|
+
import { Table } from "@confect/server";
|
|
185
|
+
import { Schema } from "effect";
|
|
186
|
+
+ import { Id } from "../_generated/id";
|
|
187
|
+
|
|
188
|
+
- export default Table.make(
|
|
189
|
+
- "notes",
|
|
190
|
+
- Schema.Struct({
|
|
191
|
+
- userId: Schema.optional(GenericId.GenericId("users")),
|
|
192
|
+
- text: Schema.String,
|
|
193
|
+
- }),
|
|
194
|
+
- );
|
|
195
|
+
+ export default Table.make(() =>
|
|
196
|
+
+ Schema.Struct({
|
|
197
|
+
+ userId: Schema.optional(Id("users")),
|
|
198
|
+
+ text: Schema.String,
|
|
199
|
+
+ }),
|
|
200
|
+
+ );
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
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):
|
|
204
|
+
|
|
205
|
+
```diff
|
|
206
|
+
- import Notes from "../tables/Notes";
|
|
207
|
+
+ import notes from "../_generated/tables/notes";
|
|
208
|
+
|
|
209
|
+
- returns: Schema.Array(Notes.Doc),
|
|
210
|
+
+ returns: Schema.Array(notes.Doc),
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
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.).
|
|
214
|
+
6. If you read `table.name` anywhere off a bound `Table`, rename it to `table.tableName`.
|
|
215
|
+
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.
|
|
216
|
+
8. If you use `@confect/test`, pass the generated Convex schema definition to `TestConfect.layer`:
|
|
217
|
+
|
|
218
|
+
```diff
|
|
219
|
+
- import confectSchema from "./confect/schema";
|
|
220
|
+
+ import confectSchema from "./confect/_generated/schema";
|
|
221
|
+
+ import convexSchema from "./confect/_generated/convexSchema";
|
|
222
|
+
|
|
223
|
+
export const layer = TestConfect_.layer(
|
|
224
|
+
confectSchema,
|
|
225
|
+
+ convexSchema,
|
|
226
|
+
import.meta.glob("./convex/**/!(*.*.*)*.*s"),
|
|
227
|
+
);
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### New warning: no tables discovered
|
|
231
|
+
|
|
232
|
+
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/`.
|
|
233
|
+
|
|
234
|
+
### New error: invalid table filename
|
|
235
|
+
|
|
236
|
+
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`.
|
|
237
|
+
|
|
238
|
+
### Patch Changes
|
|
239
|
+
|
|
240
|
+
- Updated dependencies [46045a9]
|
|
241
|
+
- Updated dependencies [762f7eb]
|
|
242
|
+
- @confect/core@9.0.0-next.6
|
|
243
|
+
- @confect/server@9.0.0-next.6
|
|
244
|
+
|
|
245
|
+
## 9.0.0-next.5
|
|
246
|
+
|
|
247
|
+
### Patch Changes
|
|
248
|
+
|
|
249
|
+
- 4cebebc: Switch the codegen bundler to [`bundle-require`](https://github.com/egoist/bundle-require).
|
|
250
|
+
|
|
251
|
+
### Why
|
|
252
|
+
|
|
253
|
+
`9.0.0-next.4`'s `absoluteExternalsPlugin` externalized every bare-specifier import and rewrote it to a `file://` URL because the bundle was loaded via a parent-less `data:` URL. esbuild's resolver honors `tsconfig.json#compilerOptions.paths`, so a `~/src/foo`-style alias resolved to a local `.ts` file and got externalized as `file:///…/foo.ts` — which Node ESM cannot import (`ERR_UNKNOWN_FILE_EXTENSION`). The codegen bundler hand-rolled a third reimplementation of "load a TypeScript config file at runtime"; each iteration introduced a new bug.
|
|
254
|
+
|
|
255
|
+
### What changed
|
|
256
|
+
|
|
257
|
+
- `@confect/cli/Bundler` now delegates to `bundle-require`, the library `tsup`, `unbuild`, `vite`, `vitest`, and `vuepress` use for this exact problem. `bundle-require` writes a temp `.mjs` next to the source, `import()`s it, and deletes it — so bare-specifier externals (third-party packages, workspace deps) resolve through Node's normal `node_modules` walk and tsconfig `paths` aliases stay inside the bundle.
|
|
258
|
+
- `@confect/cli/confect/dev`'s watcher swaps `absoluteExternalsPlugin` for `bundle-require`'s `externalPlugin`, fed the project's `tsconfig.json#paths` via `loadTsConfig` so dev-mode rebuilds also bundle aliased local source instead of erroring on it.
|
|
259
|
+
- The `absoluteExternalsPlugin` export is removed from `@confect/cli/Bundler`.
|
|
260
|
+
|
|
261
|
+
### Fixes
|
|
262
|
+
|
|
263
|
+
- Restores `confect codegen` for any project that uses `tsconfig.json` `paths` aliases (e.g. `~/*`, `@/*`, `@app/*`) for its own source.
|
|
264
|
+
- As a side benefit, `__dirname`, `__filename`, and `import.meta.url` inside bundled impls now resolve to the original source path instead of the temporary bundle URL (`bundle-require`'s built-in injection).
|
|
265
|
+
- @confect/core@9.0.0-next.5
|
|
266
|
+
- @confect/server@9.0.0-next.5
|
|
267
|
+
|
|
268
|
+
## 9.0.0-next.4
|
|
269
|
+
|
|
270
|
+
### Patch Changes
|
|
271
|
+
|
|
272
|
+
- 46e17f7: Externalize every bare-specifier dependency during codegen bundling instead of inlining third-party packages.
|
|
273
|
+
|
|
274
|
+
### Why
|
|
275
|
+
|
|
276
|
+
Since `9.0.0-next.0`, codegen bundles each `*.impl.ts` with esbuild so it can load the default-exported `Layer` and read the snapshotted function names from a `Finalized` `GroupImpl`. The bundler only marked `@confect/core`, `@confect/server`, `effect`, and `@effect/*` as external — every other dependency, including all of `node_modules` and every `node:*` built-in reached through inlined CJS source, was bundled into the output. With `format: "esm"`, esbuild rewrites any CJS `require(...)` in that inlined source to a runtime shim that throws `Dynamic require of "<id>" is not supported`, so any impl importing a third-party package (`sharp`, `luxon`, `@clerk/backend`, `jsonwebtoken`, `openai`, etc.) failed during `validateImpl`.
|
|
277
|
+
|
|
278
|
+
### What changed
|
|
279
|
+
|
|
280
|
+
- `@confect/cli/Bundler`'s `absoluteExternalsPlugin` now externalizes every bare specifier (anything not starting with `./`, `../`, or `/`), resolving each to an absolute file URL via the user's `node_modules`. `node:*` built-ins pass through unchanged.
|
|
281
|
+
- The `EXTERNAL_PACKAGES` allow-list is removed; relative imports continue to be bundled so the user's own source is still transpiled together.
|
|
282
|
+
- `@confect/cli/confect/dev` drops the redundant `external: EXTERNAL_PACKAGES` esbuild option — the plugin handles externalization for both codegen and dev-mode watchers.
|
|
283
|
+
|
|
284
|
+
### Fixes
|
|
285
|
+
|
|
286
|
+
This restores `confect codegen` for impls that import any non-`@confect/*` / `effect` / `@effect/*` library, fixing the regression introduced in `9.0.0-next.0`.
|
|
287
|
+
|
|
288
|
+
- @confect/core@9.0.0-next.4
|
|
289
|
+
- @confect/server@9.0.0-next.4
|
|
290
|
+
|
|
291
|
+
## 9.0.0-next.3
|
|
292
|
+
|
|
293
|
+
### Patch Changes
|
|
294
|
+
|
|
295
|
+
- 6d85210: Resolve `FunctionImpl` / `GroupImpl` group paths via an immutable `paths` map on `Spec` instead of identity-walking the assembled tree.
|
|
296
|
+
|
|
297
|
+
### Why
|
|
298
|
+
|
|
299
|
+
Since `9.0.0-next.1`, codegen has wrapped every parent leaf that has sibling subdirectory specs in `<parent>.addGroupAt("child", <child>)`. Because `GroupSpec.addGroupAt` is immutable, that produced a fresh object in the assembled tree, while the parent's `*.impl.ts` continued to hold a reference to the original imported leaf. The runtime resolver compared by `===`, so every such impl failed `validateImpl` with "Could not resolve group path for the provided GroupSpec." Child impls happened to work only because `GroupSpec.withName` was secretly mutating its argument in place to keep the child's identity stable — an asymmetry that was load-bearing for one half of the API and broken for the other.
|
|
300
|
+
|
|
301
|
+
### What changed
|
|
302
|
+
|
|
303
|
+
- `@confect/core/Spec` carries a new `readonly paths: ReadonlyMap<GroupSpec.AnyWithProps, string>` field and exposes a chainable `Spec#addPath(group, path)` builder. `add` / `addAt` / `merge` propagate `paths` unchanged; `merge` re-prefixes a node spec's entries with `"node."` to match the merged tree.
|
|
304
|
+
- `@confect/core/GroupSpec.withName` is now pure: it returns a fresh copy when the name differs and no longer rewrites the input in place. No new identity-tracking machinery is introduced.
|
|
305
|
+
- `@confect/server/FunctionImpl.make` and `GroupImpl.make` resolve their group path via `api.spec.paths.get(group)` — an O(1) map lookup instead of a tree walk — and throw a clearer error pointing at `Spec.addPath` when the spec hasn't been registered.
|
|
306
|
+
- `@confect/server/GroupPath` (the old identity-based resolver) is deleted.
|
|
307
|
+
- `@confect/cli` codegen emits one `.addPath(<binding>, "<dot.path>")` call per leaf in `_generated/spec.ts` (and `_generated/nodeSpec.ts`) so the imported leaves carry their full paths into the assembled spec value.
|
|
308
|
+
|
|
309
|
+
### User-facing impact
|
|
310
|
+
|
|
311
|
+
- Spec authoring (`*.spec.ts`) and impl authoring (`*.impl.ts`) APIs are unchanged. `FunctionImpl.make(api, spec, name, handler)` and `GroupImpl.make(api, spec)` keep their exact signatures.
|
|
312
|
+
- Generated `_generated/spec.ts` (and `_generated/nodeSpec.ts`) pick up one `.addPath(...)` chain entry per leaf on the next `confect codegen` run. The shape is fully immutable — no module-load mutation, no hidden side effects.
|
|
313
|
+
- Hand-rolled tests that construct a `Spec` and pass it to `Api.make` must now also call `.addPath(spec, "dot.path")` for any group they intend to look up.
|
|
314
|
+
|
|
315
|
+
### Fixes
|
|
316
|
+
|
|
317
|
+
This eliminates the runtime regression introduced in `9.0.0-next.1` for any project layout where a `confect/{path}.spec.ts` declares functions alongside a sibling `confect/{path}/` subdirectory of further specs.
|
|
318
|
+
|
|
319
|
+
- Updated dependencies [6d85210]
|
|
320
|
+
- @confect/core@9.0.0-next.3
|
|
321
|
+
- @confect/server@9.0.0-next.3
|
|
322
|
+
|
|
323
|
+
## 9.0.0-next.2
|
|
324
|
+
|
|
325
|
+
### Patch Changes
|
|
326
|
+
|
|
327
|
+
- bf13919: Generate a unique import binding per spec leaf in `_generated/spec.ts`, even when sibling `*.spec.ts` files share a basename across directories.
|
|
328
|
+
|
|
329
|
+
Previously, codegen keyed import bindings by file basename (the filename minus `.spec.ts`). When two leaves shared a basename they collapsed to a single binding, last write wins. Every `addGroupAt(...)` site that should have referenced any of the colliding leaves ended up referencing the same survivor, and every impl whose sibling spec was dropped failed `validateImpl` with `Could not resolve group path for the provided GroupSpec.` because the assembled tree no longer contained that spec object's identity.
|
|
330
|
+
|
|
331
|
+
Each binding now carries its own `localName` derived from the leaf's full path segments (e.g. `scripts_operational_seed_mutations`), used both as the imported identifier and at the matching `addGroupAt` site. Top-level leaves with single-segment paths are unchanged (`env`, `notes`, etc.).
|
|
332
|
+
|
|
333
|
+
- @confect/core@9.0.0-next.2
|
|
334
|
+
- @confect/server@9.0.0-next.2
|
|
335
|
+
|
|
3
336
|
## 9.0.0-next.1
|
|
4
337
|
|
|
5
338
|
### Patch Changes
|
|
@@ -11,6 +344,7 @@
|
|
|
11
344
|
Both `confect codegen` and `confect dev` now generate the parent's functions and the subdirectory's groups side by side, as `refs.{path}.{fn}` and `refs.{path}.{child}.{fn}`.
|
|
12
345
|
|
|
13
346
|
Codegen also now reports a clear error when the parent spec declares a function or subgroup whose name matches one of the subdirectory's child segments, rather than letting the conflict turn into a runtime refs collision. `confect codegen` exits non-zero on this error; `confect dev` logs it and keeps watching so the next save can recover.
|
|
347
|
+
|
|
14
348
|
- @confect/core@9.0.0-next.1
|
|
15
349
|
- @confect/server@9.0.0-next.1
|
|
16
350
|
|
|
@@ -29,6 +363,7 @@
|
|
|
29
363
|
Splitting impl across colocated `*.impl.ts` files is the vehicle for fixing that. With this change, `confect codegen` emits one `_generated/registeredFunctions/{path}.ts` per group, and each generated `convex/` module imports only its own group's per-group registry — which in turn imports only its own sibling `.impl.ts`. A Convex function's cold-start bundle now scales with its own group's impl rather than with the size of the whole project.
|
|
30
364
|
|
|
31
365
|
### Breaking changes
|
|
366
|
+
|
|
32
367
|
- `GroupSpec.make()` and `GroupSpec.makeNode()` no longer take a name argument; the group name is derived from the spec file's path within `confect/`.
|
|
33
368
|
- `FunctionImpl.make(api, groupSpec, fn, handler)` and `GroupImpl.make(api, groupSpec)` now take the imported sibling spec object as their second argument instead of a dot-path string.
|
|
34
369
|
- Every `GroupImpl` pipeline must end with `GroupImpl.finalize`, which only typechecks once every function declared by the spec has a corresponding `FunctionImpl` provided to the group layer. `GroupImpl.finalize` snapshots the names of every registered function onto the produced `Finalized` `GroupImpl` service value, and `confect codegen` reads those names to verify per-function coverage against the spec at runtime.
|
|
@@ -37,6 +372,7 @@
|
|
|
37
372
|
- Every module under `convex/` is re-emitted to import from `_generated/registeredFunctions/{path}` instead of the previous aggregate file. Users who commit `convex/` to source control should expect a full rewrite of that directory on first codegen.
|
|
38
373
|
|
|
39
374
|
### Migration
|
|
375
|
+
|
|
40
376
|
1. For each existing group, create a colocated `confect/{path}.spec.ts` and `confect/{path}.impl.ts` pair (under a subdirectory for nested groups).
|
|
41
377
|
- In each spec, call `GroupSpec.make()` (or `GroupSpec.makeNode()`) without a name and `export default` the result.
|
|
42
378
|
- In each impl, default-import the sibling spec (e.g. `import notes from "./notes.spec"`), pass it to `FunctionImpl.make`/`GroupImpl.make` in place of the previous dot-path string, append `GroupImpl.finalize` to the pipeline, and `export default` the resulting `GroupImpl` layer:
|
|
@@ -44,7 +380,7 @@
|
|
|
44
380
|
export default GroupImpl.make(api, notes).pipe(
|
|
45
381
|
Layer.provide(list),
|
|
46
382
|
Layer.provide(insert),
|
|
47
|
-
GroupImpl.finalize
|
|
383
|
+
GroupImpl.finalize
|
|
48
384
|
);
|
|
49
385
|
```
|
|
50
386
|
2. Delete root `confect/spec.ts`, `confect/impl.ts`, `confect/nodeSpec.ts`, `confect/nodeImpl.ts`, and any parent aggregator spec/impl files. (`confect codegen` will also delete any of these it finds, plus the stale `_generated/registeredFunctions.ts` and `_generated/nodeRegisteredFunctions.ts`, so this step can be skipped.)
|
package/dist/BuildError.mjs
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { formatPathDoc } from "./log.mjs";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Array from "effect/Array";
|
|
4
|
+
import * as Match from "effect/Match";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
import { pipe } from "effect/Function";
|
|
7
|
+
import * as Ansi from "@effect/printer-ansi/Ansi";
|
|
8
|
+
import * as AnsiDoc from "@effect/printer-ansi/AnsiDoc";
|
|
9
|
+
import * as Schema from "effect/Schema";
|
|
10
|
+
import * as String from "effect/String";
|
|
4
11
|
import * as esbuild from "esbuild";
|
|
5
12
|
|
|
6
13
|
//#region src/BuildError.ts
|
package/dist/BuildError.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BuildError.mjs","names":[],"sources":["../src/BuildError.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"BuildError.mjs","names":[],"sources":["../src/BuildError.ts"],"sourcesContent":["import * as Ansi from \"@effect/printer-ansi/Ansi\";\nimport * as AnsiDoc from \"@effect/printer-ansi/AnsiDoc\";\nimport { pipe } from \"effect/Function\";\nimport * as Array from \"effect/Array\";\nimport * as Effect from \"effect/Effect\";\nimport * as Match from \"effect/Match\";\nimport * as Option from \"effect/Option\";\nimport * as Schema from \"effect/Schema\";\nimport * as String from \"effect/String\";\nimport * as esbuild from \"esbuild\";\nimport { formatPathDoc } from \"./log\";\n\n// --- Variants ---\n\nexport class BundleFailedError extends Schema.TaggedError<BundleFailedError>()(\n \"BundleFailedError\",\n {\n file: Schema.String,\n errors: Schema.Array(Schema.Unknown),\n },\n) {}\n\nexport class ImportFailedError extends Schema.TaggedError<ImportFailedError>()(\n \"ImportFailedError\",\n {\n file: Schema.String,\n cause: Schema.Unknown,\n },\n) {}\n\nexport const BuildError = Schema.Union(BundleFailedError, ImportFailedError);\nexport type BuildError = typeof BuildError.Type;\n\nexport const isBuildError = (error: unknown): error is BuildError =>\n Schema.is(BuildError)(error);\n\n// --- Bundler adapter ---\n\n/**\n * Internal failure produced by the esbuild bundle/import pipeline. Always\n * remapped to a {@link BuildError} (which carries enough context for the CLI\n * to render it) before reaching a user-surface boundary.\n */\nexport class BundlerError extends Schema.TaggedError<BundlerError>()(\n \"BundlerError\",\n {\n cause: Schema.Unknown,\n },\n) {}\n\nconst isEsbuildBuildFailure = (error: unknown): error is esbuild.BuildFailure =>\n typeof error === \"object\" &&\n error !== null &&\n \"errors\" in error &&\n globalThis.Array.isArray((error as esbuild.BuildFailure).errors);\n\nexport const fromBundlerError = (\n file: string,\n error: BundlerError,\n): BuildError =>\n isEsbuildBuildFailure(error.cause)\n ? new BundleFailedError({ file, errors: error.cause.errors })\n : new ImportFailedError({ file, cause: error.cause });\n\n// --- Rendering ---\n\nconst cross = pipe(AnsiDoc.char(\"✘\"), AnsiDoc.annotate(Ansi.red));\n\nconst errorGutter = pipe(\n AnsiDoc.char(\"│\"),\n AnsiDoc.annotate(Ansi.red),\n AnsiDoc.render({ style: \"pretty\" }),\n);\n\nconst withErrorGutterBlock = (output: string): string =>\n pipe(\n String.split(output, \"\\n\"),\n Array.map((line) =>\n pipe(line, String.trim) === \"\" ? errorGutter : `${errorGutter} ${line}`,\n ),\n Array.join(\"\\n\"),\n (guttered) => `${errorGutter}\\n${guttered}\\n${errorGutter}`,\n );\n\nconst formatBuildMessage = (\n error: esbuild.Message | undefined,\n formattedMessage: string,\n): string => {\n const lines = String.split(formattedMessage, \"\\n\");\n const redErrorText = pipe(\n AnsiDoc.text(error?.text ?? \"\"),\n AnsiDoc.annotate(Ansi.red),\n AnsiDoc.render({ style: \"pretty\" }),\n );\n const replaced = pipe(\n Array.findFirstIndex(lines, (l) => pipe(l, String.trim, String.isNonEmpty)),\n Option.match({\n onNone: () => lines,\n onSome: (index) => Array.modify(lines, index, () => redErrorText),\n }),\n );\n return pipe(replaced, Array.join(\"\\n\"));\n};\n\n/**\n * Render a list of esbuild messages into a styled, gutter-prefixed block.\n * Used by both {@link renderBundleFailedError} and the dev-mode esbuild\n * watcher's `onEnd` hook (where the messages don't flow through the\n * tagged-error pipeline).\n */\nexport const formatEsbuildMessages = (\n errors: readonly esbuild.Message[],\n formattedMessages: readonly string[],\n): string =>\n pipe(\n formattedMessages,\n Array.map((message, i) => formatBuildMessage(errors[i], message)),\n Array.join(\"\"),\n String.trimEnd,\n withErrorGutterBlock,\n );\n\nconst renderImportFailedError = (error: ImportFailedError): AnsiDoc.AnsiDoc => {\n const causeMessage =\n error.cause instanceof Error\n ? error.cause.message\n : typeof error.cause === \"string\"\n ? error.cause\n : globalThis.String(error.cause);\n const oneLineCause = pipe(\n String.split(causeMessage, \"\\n\"),\n Array.findFirst((line) => pipe(line, String.trim, String.isNonEmpty)),\n Option.map(String.trim),\n Option.getOrElse(() => \"unknown error\"),\n );\n return pipe(\n cross,\n AnsiDoc.catWithSpace(\n AnsiDoc.hcat([\n AnsiDoc.text(\"Failed to load bundled module \"),\n formatPathDoc(error.file),\n AnsiDoc.text(\n `: ${oneLineCause}; check the file's top-level imports and side effects.`,\n ),\n ]),\n ),\n );\n};\n\n/**\n * Render a {@link BundleFailedError} as a multi-line, ANSI-styled string:\n * a one-line `✘ <file>: build errors` header followed by the\n * gutter-prefixed esbuild diagnostic block. Multi-line is appropriate\n * here because a single `BundleFailedError` carries an array of distinct\n * esbuild messages.\n */\nconst renderBundleFailedError = (error: BundleFailedError): string => {\n const messages = error.errors as readonly esbuild.Message[];\n const formatted = esbuild.formatMessagesSync(messages as esbuild.Message[], {\n kind: \"error\",\n color: true,\n terminalWidth: 80,\n });\n const header = pipe(\n cross,\n AnsiDoc.catWithSpace(\n AnsiDoc.hcat([formatPathDoc(error.file), AnsiDoc.text(\": build errors\")]),\n ),\n AnsiDoc.render({ style: \"pretty\" }),\n );\n return `${header}\\n${formatEsbuildMessages(messages, formatted)}`;\n};\n\n/**\n * Render any {@link BuildError} into a styled, ready-to-print string.\n * `ImportFailedError` collapses to a single line; `BundleFailedError`\n * expands to a header plus one diagnostic block per esbuild message.\n */\nexport const renderBuildError = (error: BuildError): string =>\n Match.value(error).pipe(\n Match.tag(\"BundleFailedError\", renderBundleFailedError),\n Match.tag(\"ImportFailedError\", (e) =>\n pipe(renderImportFailedError(e), AnsiDoc.render({ style: \"pretty\" })),\n ),\n Match.exhaustive,\n );\n\nexport const logBuildError = (error: BuildError) =>\n Effect.sync(() => console.error(renderBuildError(error)));\n\n/**\n * Render a flat list of esbuild messages as a single error block with a\n * generic `✘ Build errors` header. Used by dev-mode when multiple\n * watchers surface the same underlying error and we want to log the\n * coalesced set rather than one block per watcher.\n */\nconst renderCoalescedBuildErrors = (\n messages: readonly esbuild.Message[],\n): string => {\n const formatted = esbuild.formatMessagesSync(messages as esbuild.Message[], {\n kind: \"error\",\n color: true,\n terminalWidth: 80,\n });\n const header = pipe(\n cross,\n AnsiDoc.catWithSpace(AnsiDoc.text(\"Build errors\")),\n AnsiDoc.render({ style: \"pretty\" }),\n );\n return `${header}\\n${formatEsbuildMessages(messages, formatted)}`;\n};\n\nexport const logCoalescedBuildErrors = (messages: readonly esbuild.Message[]) =>\n Effect.sync(() => {\n if (messages.length === 0) return;\n console.error(renderCoalescedBuildErrors(messages));\n });\n"],"mappings":"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"}
|
package/dist/Bundler.mjs
CHANGED
|
@@ -1,69 +1,79 @@
|
|
|
1
1
|
import { BundlerError } from "./BuildError.mjs";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import * as
|
|
6
|
-
import {
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Path from "@effect/platform/Path";
|
|
4
|
+
import * as Array from "effect/Array";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
7
|
+
import { bundleRequire } from "bundle-require";
|
|
8
|
+
import { pipe } from "effect/Function";
|
|
7
9
|
|
|
8
10
|
//#region src/Bundler.ts
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
const buildEntry = (entryPoint) => Effect.tryPromise({
|
|
34
|
-
try: () => esbuild.build({
|
|
35
|
-
entryPoints: [entryPoint],
|
|
36
|
-
bundle: true,
|
|
37
|
-
write: false,
|
|
38
|
-
platform: "node",
|
|
39
|
-
format: "esm",
|
|
40
|
-
logLevel: "silent",
|
|
41
|
-
metafile: true,
|
|
42
|
-
plugins: [absoluteExternalsPlugin]
|
|
43
|
-
}),
|
|
44
|
-
catch: (cause) => new BundlerError({ cause })
|
|
45
|
-
});
|
|
46
|
-
const importBundledModule = (result) => {
|
|
47
|
-
const code = result.outputFiles[0].text;
|
|
48
|
-
return import("data:text/javascript;base64," + Buffer.from(code).toString("base64"));
|
|
11
|
+
/**
|
|
12
|
+
* `bundle-require` sets `absWorkingDir: cwd` on the underlying esbuild build,
|
|
13
|
+
* so the metafile's input keys (and each input's `imports[].path`) are stored
|
|
14
|
+
* relative to that cwd. Callers reach for the metafile with absolute paths
|
|
15
|
+
* (e.g. {@link directlyImports}), so we normalize every key/import path to
|
|
16
|
+
* absolute up front. That way the lookup logic stays oblivious to whatever
|
|
17
|
+
* cwd was used during bundling.
|
|
18
|
+
*/
|
|
19
|
+
const absolutizeMetafile = (metafile, cwd) => {
|
|
20
|
+
const absolutize = (p) => isAbsolute(p) ? p : resolve(cwd, p);
|
|
21
|
+
const inputs = {};
|
|
22
|
+
for (const [key, value] of Object.entries(metafile.inputs)) inputs[absolutize(key)] = {
|
|
23
|
+
...value,
|
|
24
|
+
imports: value.imports.map((i) => Object.assign({}, i, { path: absolutize(i.path) }))
|
|
25
|
+
};
|
|
26
|
+
const outputs = {};
|
|
27
|
+
for (const [key, value] of Object.entries(metafile.outputs)) outputs[absolutize(key)] = value;
|
|
28
|
+
return {
|
|
29
|
+
inputs,
|
|
30
|
+
outputs
|
|
31
|
+
};
|
|
49
32
|
};
|
|
50
33
|
/**
|
|
51
|
-
* Bundle a TypeScript entry point with esbuild
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
34
|
+
* Bundle a TypeScript entry point with esbuild via {@link bundleRequire} and
|
|
35
|
+
* import the result. `bundle-require` writes a temp `.mjs` next to the source,
|
|
36
|
+
* `import()`s it, and deletes it — so bare-specifier externals (third-party
|
|
37
|
+
* packages, workspace deps) resolve through the user's normal `node_modules`
|
|
38
|
+
* walk, and tsconfig `paths` aliases stay inside the bundle.
|
|
39
|
+
*
|
|
40
|
+
* `cwd` is set to the entry's directory so `bundle-require`'s `tsconfig.json`
|
|
41
|
+
* discovery (which walks upward from `cwd`) lands on the project's tsconfig
|
|
42
|
+
* regardless of where `confect codegen` was invoked from, and so esbuild
|
|
43
|
+
* resolves relative imports against the entry's location.
|
|
44
|
+
*
|
|
45
|
+
* The returned pair carries both the imported module and the esbuild metafile
|
|
46
|
+
* so callers can inspect the import graph (see {@link directlyImports}); the
|
|
47
|
+
* metafile is captured via a small `onEnd` plugin because `bundle-require`
|
|
48
|
+
* itself only exposes a flat `dependencies: string[]`.
|
|
56
49
|
*/
|
|
57
50
|
const bundle = (entryPoint) => Effect.gen(function* () {
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
51
|
+
let metafile;
|
|
52
|
+
const captureMetafile = {
|
|
53
|
+
name: "confect:capture-metafile",
|
|
54
|
+
setup(build) {
|
|
55
|
+
build.onEnd((result) => {
|
|
56
|
+
metafile = result.metafile;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const cwd = dirname(entryPoint);
|
|
61
|
+
const result = yield* Effect.tryPromise({
|
|
62
|
+
try: () => bundleRequire({
|
|
63
|
+
filepath: entryPoint,
|
|
64
|
+
cwd,
|
|
65
|
+
format: "esm",
|
|
66
|
+
esbuildOptions: {
|
|
67
|
+
plugins: [captureMetafile],
|
|
68
|
+
logLevel: "silent"
|
|
69
|
+
}
|
|
70
|
+
}),
|
|
61
71
|
catch: (cause) => new BundlerError({ cause })
|
|
62
72
|
});
|
|
63
|
-
if (!
|
|
73
|
+
if (!metafile) return yield* Effect.dieMessage("esbuild metafile missing");
|
|
64
74
|
return {
|
|
65
|
-
module,
|
|
66
|
-
metafile:
|
|
75
|
+
module: result.mod,
|
|
76
|
+
metafile: absolutizeMetafile(metafile, cwd)
|
|
67
77
|
};
|
|
68
78
|
});
|
|
69
79
|
const findMetafileInputKey = (metafile, absolutePath) => Effect.gen(function* () {
|
|
@@ -87,5 +97,5 @@ const directlyImports = (bundled, sourceAbsolutePath, targetAbsolutePath) => Eff
|
|
|
87
97
|
});
|
|
88
98
|
|
|
89
99
|
//#endregion
|
|
90
|
-
export {
|
|
100
|
+
export { bundle, directlyImports };
|
|
91
101
|
//# sourceMappingURL=Bundler.mjs.map
|