@confect/cli 8.0.0 → 9.0.0-next.1
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 +57 -1
- package/dist/BuildError.mjs +101 -0
- package/dist/BuildError.mjs.map +1 -0
- package/dist/Bundler.mjs +91 -0
- package/dist/Bundler.mjs.map +1 -0
- package/dist/CodeBlockWriter.mjs +55 -0
- package/dist/CodeBlockWriter.mjs.map +1 -0
- package/dist/CodegenError.mjs +101 -0
- package/dist/CodegenError.mjs.map +1 -0
- package/dist/FunctionPaths.mjs +1 -1
- package/dist/LeafModule.mjs +170 -0
- package/dist/LeafModule.mjs.map +1 -0
- package/dist/SpecAssemblyNode.mjs +33 -0
- package/dist/SpecAssemblyNode.mjs.map +1 -0
- package/dist/confect/codegen.mjs +292 -72
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/confect/dev.mjs +234 -180
- package/dist/confect/dev.mjs.map +1 -1
- package/dist/log.mjs +13 -1
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/templates.mjs +69 -72
- package/dist/templates.mjs.map +1 -1
- package/dist/utils.mjs +77 -73
- package/dist/utils.mjs.map +1 -1
- package/package.json +4 -3
- package/src/BuildError.ts +210 -0
- package/src/Bundler.ts +144 -0
- package/src/CodeBlockWriter.ts +65 -0
- package/src/CodegenError.ts +376 -0
- package/src/LeafModule.ts +317 -0
- package/src/SpecAssemblyNode.ts +82 -0
- package/src/confect/codegen.ts +511 -141
- package/src/confect/dev.ts +556 -435
- package/src/log.ts +21 -0
- package/src/templates.ts +146 -109
- package/src/utils.ts +118 -93
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# @confect/cli
|
|
2
2
|
|
|
3
|
+
## 9.0.0-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7cb20ab: Allow a `confect/{path}.spec.ts` file to declare functions even when a sibling `confect/{path}/` subdirectory contains further specs.
|
|
8
|
+
|
|
9
|
+
Previously, every function on the parent spec silently disappeared from the generated api and refs in this layout: `refs.{path}.{fn}` was not defined, while `refs.{path}.{child}.{fn}` (from the subdirectory specs) worked. The parent's `*.impl.ts` continued to type-check on its own, so the missing functions only showed up when something tried to call them.
|
|
10
|
+
|
|
11
|
+
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
|
+
|
|
13
|
+
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.
|
|
14
|
+
- @confect/core@9.0.0-next.1
|
|
15
|
+
- @confect/server@9.0.0-next.1
|
|
16
|
+
|
|
17
|
+
## 9.0.0-next.0
|
|
18
|
+
|
|
19
|
+
### Major Changes
|
|
20
|
+
|
|
21
|
+
- 6db3a3a: Derive Confect function paths from the filesystem layout of `confect/`. Each group lives in a colocated `*.spec.ts`/`*.impl.ts` pair, and the group's name is its path within `confect/` (file stem for top-level groups, dot-joined directory path for nested groups). See [Project Structure](https://confect.dev/concepts/project-structure), [File Naming Conventions](https://confect.dev/concepts/file-naming-conventions), and [The Spec/Impl Model](https://confect.dev/concepts/spec-impl-model) for the current model.
|
|
22
|
+
|
|
23
|
+
Each `*.spec.ts` default-exports its `GroupSpec`. Each `*.impl.ts` default-imports its sibling spec, builds its `GroupImpl`, and default-exports the result of `GroupImpl.finalize`. Named co-exports on `*.spec.ts` (such as error classes) remain allowed.
|
|
24
|
+
|
|
25
|
+
### Why
|
|
26
|
+
|
|
27
|
+
The previous model assembled every group's impl into a single root `confect/impl.ts` (plus `confect/nodeImpl.ts`), which `confect codegen` emitted as the aggregate `_generated/registeredFunctions.ts`. Every generated `convex/` module — one per Convex function — imported from that aggregate, so loading any single query, mutation, or action transitively loaded the impl module of every other Convex function in the project, along with all of their dependencies. For large projects this inflated each function's bundle and added meaningful cold-start cost on Convex.
|
|
28
|
+
|
|
29
|
+
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
|
+
|
|
31
|
+
### Breaking changes
|
|
32
|
+
- `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
|
+
- `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
|
+
- 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.
|
|
35
|
+
- The previously-exported `Impl.make` and `Impl.finalize` (used by the old root `impl.ts`/`nodeImpl.ts` files) are removed. Per-group completeness is now enforced by `GroupImpl.finalize`.
|
|
36
|
+
- Root `confect/spec.ts`, `confect/impl.ts`, `confect/nodeSpec.ts`, `confect/nodeImpl.ts`, and any parent aggregator `*.spec.ts`/`*.impl.ts` files are no longer used. `confect codegen` deletes any of these on upgrade, along with the stale `_generated/registeredFunctions.ts` and `_generated/nodeRegisteredFunctions.ts`.
|
|
37
|
+
- 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
|
+
|
|
39
|
+
### Migration
|
|
40
|
+
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
|
+
- In each spec, call `GroupSpec.make()` (or `GroupSpec.makeNode()`) without a name and `export default` the result.
|
|
42
|
+
- 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:
|
|
43
|
+
```ts
|
|
44
|
+
export default GroupImpl.make(api, notes).pipe(
|
|
45
|
+
Layer.provide(list),
|
|
46
|
+
Layer.provide(insert),
|
|
47
|
+
GroupImpl.finalize,
|
|
48
|
+
);
|
|
49
|
+
```
|
|
50
|
+
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.)
|
|
51
|
+
3. Run `confect codegen`. Every module under `convex/` will be re-emitted.
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- Updated dependencies [6db3a3a]
|
|
56
|
+
- @confect/core@9.0.0-next.0
|
|
57
|
+
- @confect/server@9.0.0-next.0
|
|
58
|
+
|
|
3
59
|
## 8.0.0
|
|
4
60
|
|
|
5
61
|
### Minor Changes
|
|
@@ -13,7 +69,7 @@
|
|
|
13
69
|
|
|
14
70
|
### Patch Changes
|
|
15
71
|
|
|
16
|
-
- f308edd: Fix `confect codegen` and `confect dev` failing with "Cannot find package '@confect/core'"
|
|
72
|
+
- f308edd: Fix `confect codegen` and `confect dev` failing with "Cannot find package '@confect/core'"/"'@confect/server'" when the user's spec or impl files are bundled. The internal esbuild plugin used `import.meta.resolve(specifier, parent)` to resolve external imports, but Node silently ignores the second argument, so resolution always walked up from the CLI's own bundled file instead of from the user's project. Switched to `createRequire` keyed on the importing file's directory so external packages resolve out of the user's `node_modules`.
|
|
17
73
|
- Updated dependencies [4bb2722]
|
|
18
74
|
- Updated dependencies [40c1cff]
|
|
19
75
|
- @confect/core@8.0.0
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { formatPathDoc } from "./log.mjs";
|
|
2
|
+
import { Array, Effect, Match, Option, Schema, String, pipe } from "effect";
|
|
3
|
+
import { Ansi, AnsiDoc } from "@effect/printer-ansi";
|
|
4
|
+
import * as esbuild from "esbuild";
|
|
5
|
+
|
|
6
|
+
//#region src/BuildError.ts
|
|
7
|
+
var BundleFailedError = class extends Schema.TaggedError()("BundleFailedError", {
|
|
8
|
+
file: Schema.String,
|
|
9
|
+
errors: Schema.Array(Schema.Unknown)
|
|
10
|
+
}) {};
|
|
11
|
+
var ImportFailedError = class extends Schema.TaggedError()("ImportFailedError", {
|
|
12
|
+
file: Schema.String,
|
|
13
|
+
cause: Schema.Unknown
|
|
14
|
+
}) {};
|
|
15
|
+
const BuildError = Schema.Union(BundleFailedError, ImportFailedError);
|
|
16
|
+
const isBuildError = (error) => Schema.is(BuildError)(error);
|
|
17
|
+
/**
|
|
18
|
+
* Internal failure produced by the esbuild bundle/import pipeline. Always
|
|
19
|
+
* remapped to a {@link BuildError} (which carries enough context for the CLI
|
|
20
|
+
* to render it) before reaching a user-surface boundary.
|
|
21
|
+
*/
|
|
22
|
+
var BundlerError = class extends Schema.TaggedError()("BundlerError", { cause: Schema.Unknown }) {};
|
|
23
|
+
const isEsbuildBuildFailure = (error) => typeof error === "object" && error !== null && "errors" in error && globalThis.Array.isArray(error.errors);
|
|
24
|
+
const fromBundlerError = (file, error) => isEsbuildBuildFailure(error.cause) ? new BundleFailedError({
|
|
25
|
+
file,
|
|
26
|
+
errors: error.cause.errors
|
|
27
|
+
}) : new ImportFailedError({
|
|
28
|
+
file,
|
|
29
|
+
cause: error.cause
|
|
30
|
+
});
|
|
31
|
+
const cross = pipe(AnsiDoc.char("✘"), AnsiDoc.annotate(Ansi.red));
|
|
32
|
+
const errorGutter = pipe(AnsiDoc.char("│"), AnsiDoc.annotate(Ansi.red), AnsiDoc.render({ style: "pretty" }));
|
|
33
|
+
const withErrorGutterBlock = (output) => pipe(String.split(output, "\n"), Array.map((line) => pipe(line, String.trim) === "" ? errorGutter : `${errorGutter} ${line}`), Array.join("\n"), (guttered) => `${errorGutter}\n${guttered}\n${errorGutter}`);
|
|
34
|
+
const formatBuildMessage = (error, formattedMessage) => {
|
|
35
|
+
const lines = String.split(formattedMessage, "\n");
|
|
36
|
+
const redErrorText = pipe(AnsiDoc.text(error?.text ?? ""), AnsiDoc.annotate(Ansi.red), AnsiDoc.render({ style: "pretty" }));
|
|
37
|
+
return pipe(pipe(Array.findFirstIndex(lines, (l) => pipe(l, String.trim, String.isNonEmpty)), Option.match({
|
|
38
|
+
onNone: () => lines,
|
|
39
|
+
onSome: (index) => Array.modify(lines, index, () => redErrorText)
|
|
40
|
+
})), Array.join("\n"));
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Render a list of esbuild messages into a styled, gutter-prefixed block.
|
|
44
|
+
* Used by both {@link renderBundleFailedError} and the dev-mode esbuild
|
|
45
|
+
* watcher's `onEnd` hook (where the messages don't flow through the
|
|
46
|
+
* tagged-error pipeline).
|
|
47
|
+
*/
|
|
48
|
+
const formatEsbuildMessages = (errors, formattedMessages) => pipe(formattedMessages, Array.map((message, i) => formatBuildMessage(errors[i], message)), Array.join(""), String.trimEnd, withErrorGutterBlock);
|
|
49
|
+
const renderImportFailedError = (error) => {
|
|
50
|
+
const causeMessage = error.cause instanceof Error ? error.cause.message : typeof error.cause === "string" ? error.cause : globalThis.String(error.cause);
|
|
51
|
+
const oneLineCause = pipe(String.split(causeMessage, "\n"), Array.findFirst((line) => pipe(line, String.trim, String.isNonEmpty)), Option.map(String.trim), Option.getOrElse(() => "unknown error"));
|
|
52
|
+
return pipe(cross, AnsiDoc.catWithSpace(AnsiDoc.hcat([
|
|
53
|
+
AnsiDoc.text("Failed to load bundled module "),
|
|
54
|
+
formatPathDoc(error.file),
|
|
55
|
+
AnsiDoc.text(`: ${oneLineCause}; check the file's top-level imports and side effects.`)
|
|
56
|
+
])));
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Render a {@link BundleFailedError} as a multi-line, ANSI-styled string:
|
|
60
|
+
* a one-line `✘ <file>: build errors` header followed by the
|
|
61
|
+
* gutter-prefixed esbuild diagnostic block. Multi-line is appropriate
|
|
62
|
+
* here because a single `BundleFailedError` carries an array of distinct
|
|
63
|
+
* esbuild messages.
|
|
64
|
+
*/
|
|
65
|
+
const renderBundleFailedError = (error) => {
|
|
66
|
+
const messages = error.errors;
|
|
67
|
+
const formatted = esbuild.formatMessagesSync(messages, {
|
|
68
|
+
kind: "error",
|
|
69
|
+
color: true,
|
|
70
|
+
terminalWidth: 80
|
|
71
|
+
});
|
|
72
|
+
return `${pipe(cross, AnsiDoc.catWithSpace(AnsiDoc.hcat([formatPathDoc(error.file), AnsiDoc.text(": build errors")])), AnsiDoc.render({ style: "pretty" }))}\n${formatEsbuildMessages(messages, formatted)}`;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Render any {@link BuildError} into a styled, ready-to-print string.
|
|
76
|
+
* `ImportFailedError` collapses to a single line; `BundleFailedError`
|
|
77
|
+
* expands to a header plus one diagnostic block per esbuild message.
|
|
78
|
+
*/
|
|
79
|
+
const renderBuildError = (error) => Match.value(error).pipe(Match.tag("BundleFailedError", renderBundleFailedError), Match.tag("ImportFailedError", (e) => pipe(renderImportFailedError(e), AnsiDoc.render({ style: "pretty" }))), Match.exhaustive);
|
|
80
|
+
/**
|
|
81
|
+
* Render a flat list of esbuild messages as a single error block with a
|
|
82
|
+
* generic `✘ Build errors` header. Used by dev-mode when multiple
|
|
83
|
+
* watchers surface the same underlying error and we want to log the
|
|
84
|
+
* coalesced set rather than one block per watcher.
|
|
85
|
+
*/
|
|
86
|
+
const renderCoalescedBuildErrors = (messages) => {
|
|
87
|
+
const formatted = esbuild.formatMessagesSync(messages, {
|
|
88
|
+
kind: "error",
|
|
89
|
+
color: true,
|
|
90
|
+
terminalWidth: 80
|
|
91
|
+
});
|
|
92
|
+
return `${pipe(cross, AnsiDoc.catWithSpace(AnsiDoc.text("Build errors")), AnsiDoc.render({ style: "pretty" }))}\n${formatEsbuildMessages(messages, formatted)}`;
|
|
93
|
+
};
|
|
94
|
+
const logCoalescedBuildErrors = (messages) => Effect.sync(() => {
|
|
95
|
+
if (messages.length === 0) return;
|
|
96
|
+
console.error(renderCoalescedBuildErrors(messages));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
export { BuildError, BundlerError, fromBundlerError, isBuildError, logCoalescedBuildErrors, renderBuildError };
|
|
101
|
+
//# sourceMappingURL=BuildError.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BuildError.mjs","names":[],"sources":["../src/BuildError.ts"],"sourcesContent":["import { Ansi, AnsiDoc } from \"@effect/printer-ansi\";\nimport { Array, Effect, Match, Option, pipe, Schema, String } from \"effect\";\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
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { BundlerError } from "./BuildError.mjs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { Array, Effect, Option, pipe } from "effect";
|
|
4
|
+
import { Path } from "@effect/platform";
|
|
5
|
+
import * as esbuild from "esbuild";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
//#region src/Bundler.ts
|
|
9
|
+
const EXTERNAL_PACKAGES = [
|
|
10
|
+
"@confect/core",
|
|
11
|
+
"@confect/server",
|
|
12
|
+
"effect",
|
|
13
|
+
"@effect/*"
|
|
14
|
+
];
|
|
15
|
+
const isExternalImport = (path) => EXTERNAL_PACKAGES.some((p) => {
|
|
16
|
+
if (p.endsWith("/*")) return path.startsWith(p.slice(0, -1));
|
|
17
|
+
return path === p || path.startsWith(p + "/");
|
|
18
|
+
});
|
|
19
|
+
const absoluteExternalsPlugin = {
|
|
20
|
+
name: "absolute-externals",
|
|
21
|
+
setup(build) {
|
|
22
|
+
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
23
|
+
if (args.kind !== "import-statement" && args.kind !== "dynamic-import") return;
|
|
24
|
+
if (!isExternalImport(args.path)) return;
|
|
25
|
+
const parentFile = pathToFileURL(args.resolveDir + "/_").href;
|
|
26
|
+
return {
|
|
27
|
+
path: pathToFileURL(createRequire(parentFile).resolve(args.path)).href,
|
|
28
|
+
external: true
|
|
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"));
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Bundle a TypeScript entry point with esbuild and import the result via a
|
|
52
|
+
* data URL. This handles extensionless `.ts` imports regardless of whether
|
|
53
|
+
* the user's project sets `"type": "module"` in package.json. The returned
|
|
54
|
+
* pair carries both the imported module and the esbuild metafile so callers
|
|
55
|
+
* can inspect the import graph (see {@link directlyImports}).
|
|
56
|
+
*/
|
|
57
|
+
const bundle = (entryPoint) => Effect.gen(function* () {
|
|
58
|
+
const result = yield* buildEntry(entryPoint);
|
|
59
|
+
const module = yield* Effect.tryPromise({
|
|
60
|
+
try: () => importBundledModule(result),
|
|
61
|
+
catch: (cause) => new BundlerError({ cause })
|
|
62
|
+
});
|
|
63
|
+
if (!result.metafile) return yield* Effect.dieMessage("esbuild metafile missing");
|
|
64
|
+
return {
|
|
65
|
+
module,
|
|
66
|
+
metafile: result.metafile
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
const findMetafileInputKey = (metafile, absolutePath) => Effect.gen(function* () {
|
|
70
|
+
const path = yield* Path.Path;
|
|
71
|
+
const resolved = path.resolve(absolutePath);
|
|
72
|
+
return Array.findFirst(Object.keys(metafile.inputs), (key) => path.resolve(key) === resolved);
|
|
73
|
+
});
|
|
74
|
+
/**
|
|
75
|
+
* Returns `true` when the module bundled from `sourceAbsolutePath` declares a
|
|
76
|
+
* direct import of `targetAbsolutePath` (according to the bundle's esbuild
|
|
77
|
+
* metafile). Returns `false` if either path is missing from the metafile.
|
|
78
|
+
*/
|
|
79
|
+
const directlyImports = (bundled, sourceAbsolutePath, targetAbsolutePath) => Effect.gen(function* () {
|
|
80
|
+
const path = yield* Path.Path;
|
|
81
|
+
const sourceKey = yield* findMetafileInputKey(bundled.metafile, sourceAbsolutePath);
|
|
82
|
+
const targetKey = yield* findMetafileInputKey(bundled.metafile, targetAbsolutePath);
|
|
83
|
+
return pipe(Option.all([sourceKey, targetKey]), Option.flatMap(([sourceKey_, targetKey_]) => Option.fromNullable(bundled.metafile.inputs[sourceKey_]).pipe(Option.map((sourceInput) => {
|
|
84
|
+
const targetResolved = path.resolve(targetKey_);
|
|
85
|
+
return sourceInput.imports.some((importedFile) => path.resolve(importedFile.path) === targetResolved);
|
|
86
|
+
}))), Option.getOrElse(() => false));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
export { EXTERNAL_PACKAGES, absoluteExternalsPlugin, bundle, directlyImports };
|
|
91
|
+
//# sourceMappingURL=Bundler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Bundler.mjs","names":[],"sources":["../src/Bundler.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { pathToFileURL } from \"node:url\";\nimport { Path } from \"@effect/platform\";\nimport { Array, Effect, Option, pipe } from \"effect\";\nimport * as esbuild from \"esbuild\";\nimport { BundlerError } from \"./BuildError\";\n\nexport interface Bundled {\n readonly module: any;\n readonly metafile: esbuild.Metafile;\n}\n\nexport const EXTERNAL_PACKAGES = [\n \"@confect/core\",\n \"@confect/server\",\n \"effect\",\n \"@effect/*\",\n];\n\nconst isExternalImport = (path: string) =>\n EXTERNAL_PACKAGES.some((p) => {\n if (p.endsWith(\"/*\")) {\n return path.startsWith(p.slice(0, -1));\n }\n return path === p || path.startsWith(p + \"/\");\n });\n\nexport const absoluteExternalsPlugin: esbuild.Plugin = {\n name: \"absolute-externals\",\n setup(build) {\n build.onResolve({ filter: /.*/ }, async (args) => {\n if (args.kind !== \"import-statement\" && args.kind !== \"dynamic-import\")\n return;\n if (!isExternalImport(args.path)) return;\n // `import.meta.resolve`'s second argument is silently ignored in modern\n // Node, so resolution would always walk up from the CLI's bundled file\n // (`packages/cli/dist/utils.mjs`) instead of from the user's project.\n // Use `createRequire` keyed on the importing file's directory so we\n // resolve out of *their* `node_modules`. The synthetic filename is just\n // a CommonJS resolution anchor; the file does not need to exist.\n const parentFile = pathToFileURL(args.resolveDir + \"/_\").href;\n const require_ = createRequire(parentFile);\n const resolvedPath = require_.resolve(args.path);\n const resolved = pathToFileURL(resolvedPath).href;\n return { path: resolved, external: true };\n });\n },\n};\n\nconst buildEntry = (entryPoint: string) =>\n Effect.tryPromise({\n try: () =>\n esbuild.build({\n entryPoints: [entryPoint],\n bundle: true,\n write: false,\n platform: \"node\",\n format: \"esm\",\n logLevel: \"silent\",\n metafile: true,\n plugins: [absoluteExternalsPlugin],\n }),\n catch: (cause) => new BundlerError({ cause }),\n });\n\nconst importBundledModule = (result: esbuild.BuildResult) => {\n const code = result.outputFiles![0]!.text;\n const dataUrl =\n \"data:text/javascript;base64,\" + Buffer.from(code).toString(\"base64\");\n return import(dataUrl);\n};\n\n/**\n * Bundle a TypeScript entry point with esbuild and import the result via a\n * data URL. This handles extensionless `.ts` imports regardless of whether\n * the user's project sets `\"type\": \"module\"` in package.json. The returned\n * pair carries both the imported module and the esbuild metafile so callers\n * can inspect the import graph (see {@link directlyImports}).\n */\nexport const bundle = (\n entryPoint: string,\n): Effect.Effect<Bundled, BundlerError> =>\n Effect.gen(function* () {\n const result = yield* buildEntry(entryPoint);\n const module = yield* Effect.tryPromise({\n try: () => importBundledModule(result),\n catch: (cause) => new BundlerError({ cause }),\n });\n if (!result.metafile) {\n return yield* Effect.dieMessage(\"esbuild metafile missing\");\n }\n return { module, metafile: result.metafile };\n });\n\nconst findMetafileInputKey = (\n metafile: esbuild.Metafile,\n absolutePath: string,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const resolved = path.resolve(absolutePath);\n return Array.findFirst(\n Object.keys(metafile.inputs),\n (key) => path.resolve(key) === resolved,\n );\n });\n\n/**\n * Returns `true` when the module bundled from `sourceAbsolutePath` declares a\n * direct import of `targetAbsolutePath` (according to the bundle's esbuild\n * metafile). Returns `false` if either path is missing from the metafile.\n */\nexport const directlyImports = (\n bundled: Bundled,\n sourceAbsolutePath: string,\n targetAbsolutePath: string,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const sourceKey = yield* findMetafileInputKey(\n bundled.metafile,\n sourceAbsolutePath,\n );\n const targetKey = yield* findMetafileInputKey(\n bundled.metafile,\n targetAbsolutePath,\n );\n\n return pipe(\n Option.all([sourceKey, targetKey]),\n Option.flatMap(([sourceKey_, targetKey_]) =>\n Option.fromNullable(bundled.metafile.inputs[sourceKey_]).pipe(\n Option.map((sourceInput) => {\n const targetResolved = path.resolve(targetKey_);\n return sourceInput.imports.some(\n (importedFile) =>\n path.resolve(importedFile.path) === targetResolved,\n );\n }),\n ),\n ),\n Option.getOrElse(() => false),\n );\n });\n"],"mappings":";;;;;;;;AAYA,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACD;AAED,MAAM,oBAAoB,SACxB,kBAAkB,MAAM,MAAM;AAC5B,KAAI,EAAE,SAAS,KAAK,CAClB,QAAO,KAAK,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC;AAExC,QAAO,SAAS,KAAK,KAAK,WAAW,IAAI,IAAI;EAC7C;AAEJ,MAAa,0BAA0C;CACrD,MAAM;CACN,MAAM,OAAO;AACX,QAAM,UAAU,EAAE,QAAQ,MAAM,EAAE,OAAO,SAAS;AAChD,OAAI,KAAK,SAAS,sBAAsB,KAAK,SAAS,iBACpD;AACF,OAAI,CAAC,iBAAiB,KAAK,KAAK,CAAE;GAOlC,MAAM,aAAa,cAAc,KAAK,aAAa,KAAK,CAAC;AAIzD,UAAO;IAAE,MADQ,cAFA,cAAc,WAAW,CACZ,QAAQ,KAAK,KAAK,CACJ,CAAC;IACpB,UAAU;IAAM;IACzC;;CAEL;AAED,MAAM,cAAc,eAClB,OAAO,WAAW;CAChB,WACE,QAAQ,MAAM;EACZ,aAAa,CAAC,WAAW;EACzB,QAAQ;EACR,OAAO;EACP,UAAU;EACV,QAAQ;EACR,UAAU;EACV,UAAU;EACV,SAAS,CAAC,wBAAwB;EACnC,CAAC;CACJ,QAAQ,UAAU,IAAI,aAAa,EAAE,OAAO,CAAC;CAC9C,CAAC;AAEJ,MAAM,uBAAuB,WAAgC;CAC3D,MAAM,OAAO,OAAO,YAAa,GAAI;AAGrC,QAAO,OADL,iCAAiC,OAAO,KAAK,KAAK,CAAC,SAAS,SAAS;;;;;;;;;AAWzE,MAAa,UACX,eAEA,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO,WAAW,WAAW;CAC5C,MAAM,SAAS,OAAO,OAAO,WAAW;EACtC,WAAW,oBAAoB,OAAO;EACtC,QAAQ,UAAU,IAAI,aAAa,EAAE,OAAO,CAAC;EAC9C,CAAC;AACF,KAAI,CAAC,OAAO,SACV,QAAO,OAAO,OAAO,WAAW,2BAA2B;AAE7D,QAAO;EAAE;EAAQ,UAAU,OAAO;EAAU;EAC5C;AAEJ,MAAM,wBACJ,UACA,iBAEA,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,WAAW,KAAK,QAAQ,aAAa;AAC3C,QAAO,MAAM,UACX,OAAO,KAAK,SAAS,OAAO,GAC3B,QAAQ,KAAK,QAAQ,IAAI,KAAK,SAChC;EACD;;;;;;AAOJ,MAAa,mBACX,SACA,oBACA,uBAEA,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,YAAY,OAAO,qBACvB,QAAQ,UACR,mBACD;CACD,MAAM,YAAY,OAAO,qBACvB,QAAQ,UACR,mBACD;AAED,QAAO,KACL,OAAO,IAAI,CAAC,WAAW,UAAU,CAAC,EAClC,OAAO,SAAS,CAAC,YAAY,gBAC3B,OAAO,aAAa,QAAQ,SAAS,OAAO,YAAY,CAAC,KACvD,OAAO,KAAK,gBAAgB;EAC1B,MAAM,iBAAiB,KAAK,QAAQ,WAAW;AAC/C,SAAO,YAAY,QAAQ,MACxB,iBACC,KAAK,QAAQ,aAAa,KAAK,KAAK,eACvC;GACD,CACH,CACF,EACD,OAAO,gBAAgB,MAAM,CAC9B;EACD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import CodeBlockWriter_ from "code-block-writer";
|
|
3
|
+
|
|
4
|
+
//#region src/CodeBlockWriter.ts
|
|
5
|
+
var CodeBlockWriter = class {
|
|
6
|
+
writer;
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
this.writer = new CodeBlockWriter_(opts);
|
|
9
|
+
}
|
|
10
|
+
indent(effect) {
|
|
11
|
+
return Effect.gen(this, function* () {
|
|
12
|
+
const indentationLevel = this.writer.getIndentationLevel();
|
|
13
|
+
this.writer.setIndentationLevel(indentationLevel + 1);
|
|
14
|
+
yield* effect;
|
|
15
|
+
this.writer.setIndentationLevel(indentationLevel);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
writeLine(line) {
|
|
19
|
+
return Effect.sync(() => {
|
|
20
|
+
this.writer.writeLine(line);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
write(text) {
|
|
24
|
+
return Effect.sync(() => {
|
|
25
|
+
this.writer.write(text);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
quote(text) {
|
|
29
|
+
return Effect.sync(() => {
|
|
30
|
+
this.writer.quote(text);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
conditionalWriteLine(condition, text) {
|
|
34
|
+
return Effect.sync(() => {
|
|
35
|
+
this.writer.conditionalWriteLine(condition, text);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
newLine() {
|
|
39
|
+
return Effect.sync(() => {
|
|
40
|
+
this.writer.newLine();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
blankLine() {
|
|
44
|
+
return Effect.sync(() => {
|
|
45
|
+
this.writer.blankLine();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
toString() {
|
|
49
|
+
return Effect.sync(() => this.writer.toString());
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { CodeBlockWriter };
|
|
55
|
+
//# sourceMappingURL=CodeBlockWriter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CodeBlockWriter.mjs","names":[],"sources":["../src/CodeBlockWriter.ts"],"sourcesContent":["import type { Options as CodeBlockWriterOptions } from \"code-block-writer\";\nimport CodeBlockWriter_ from \"code-block-writer\";\nimport { Effect } from \"effect\";\n\nexport class CodeBlockWriter {\n private readonly writer: CodeBlockWriter_;\n\n constructor(opts?: Partial<CodeBlockWriterOptions>) {\n this.writer = new CodeBlockWriter_(opts);\n }\n\n indent<E = never, R = never>(\n effect: Effect.Effect<void, E, R>,\n ): Effect.Effect<void, E, R> {\n return Effect.gen(this, function* () {\n const indentationLevel = this.writer.getIndentationLevel();\n this.writer.setIndentationLevel(indentationLevel + 1);\n yield* effect;\n this.writer.setIndentationLevel(indentationLevel);\n });\n }\n\n writeLine<E = never, R = never>(line: string): Effect.Effect<void, E, R> {\n return Effect.sync(() => {\n this.writer.writeLine(line);\n });\n }\n\n write<E = never, R = never>(text: string): Effect.Effect<void, E, R> {\n return Effect.sync(() => {\n this.writer.write(text);\n });\n }\n\n quote<E = never, R = never>(text: string): Effect.Effect<void, E, R> {\n return Effect.sync(() => {\n this.writer.quote(text);\n });\n }\n\n conditionalWriteLine<E = never, R = never>(\n condition: boolean,\n text: string,\n ): Effect.Effect<void, E, R> {\n return Effect.sync(() => {\n this.writer.conditionalWriteLine(condition, text);\n });\n }\n\n newLine<E = never, R = never>(): Effect.Effect<void, E, R> {\n return Effect.sync(() => {\n this.writer.newLine();\n });\n }\n\n blankLine<E = never, R = never>(): Effect.Effect<void, E, R> {\n return Effect.sync(() => {\n this.writer.blankLine();\n });\n }\n\n toString<E = never, R = never>(): Effect.Effect<string, E, R> {\n return Effect.sync(() => this.writer.toString());\n }\n}\n"],"mappings":";;;;AAIA,IAAa,kBAAb,MAA6B;CAC3B,AAAiB;CAEjB,YAAY,MAAwC;AAClD,OAAK,SAAS,IAAI,iBAAiB,KAAK;;CAG1C,OACE,QAC2B;AAC3B,SAAO,OAAO,IAAI,MAAM,aAAa;GACnC,MAAM,mBAAmB,KAAK,OAAO,qBAAqB;AAC1D,QAAK,OAAO,oBAAoB,mBAAmB,EAAE;AACrD,UAAO;AACP,QAAK,OAAO,oBAAoB,iBAAiB;IACjD;;CAGJ,UAAgC,MAAyC;AACvE,SAAO,OAAO,WAAW;AACvB,QAAK,OAAO,UAAU,KAAK;IAC3B;;CAGJ,MAA4B,MAAyC;AACnE,SAAO,OAAO,WAAW;AACvB,QAAK,OAAO,MAAM,KAAK;IACvB;;CAGJ,MAA4B,MAAyC;AACnE,SAAO,OAAO,WAAW;AACvB,QAAK,OAAO,MAAM,KAAK;IACvB;;CAGJ,qBACE,WACA,MAC2B;AAC3B,SAAO,OAAO,WAAW;AACvB,QAAK,OAAO,qBAAqB,WAAW,KAAK;IACjD;;CAGJ,UAA2D;AACzD,SAAO,OAAO,WAAW;AACvB,QAAK,OAAO,SAAS;IACrB;;CAGJ,YAA6D;AAC3D,SAAO,OAAO,WAAW;AACvB,QAAK,OAAO,WAAW;IACvB;;CAGJ,WAA8D;AAC5D,SAAO,OAAO,WAAW,KAAK,OAAO,UAAU,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { formatPathDoc } from "./log.mjs";
|
|
2
|
+
import { BuildError, isBuildError, renderBuildError } from "./BuildError.mjs";
|
|
3
|
+
import { Effect, Match, Option, Schema, pipe } from "effect";
|
|
4
|
+
import { Ansi, AnsiDoc } from "@effect/printer-ansi";
|
|
5
|
+
|
|
6
|
+
//#region src/CodegenError.ts
|
|
7
|
+
var MissingImplFileError = class extends Schema.TaggedError()("MissingImplFileError", {
|
|
8
|
+
specPath: Schema.String,
|
|
9
|
+
expectedImplPath: Schema.String
|
|
10
|
+
}) {};
|
|
11
|
+
var MissingSpecFileError = class extends Schema.TaggedError()("MissingSpecFileError", {
|
|
12
|
+
implPath: Schema.String,
|
|
13
|
+
expectedSpecPath: Schema.String
|
|
14
|
+
}) {};
|
|
15
|
+
var SpecMissingDefaultGroupSpecError = class extends Schema.TaggedError()("SpecMissingDefaultGroupSpecError", { specPath: Schema.String }) {};
|
|
16
|
+
var SpecRuntimeMismatchError = class extends Schema.TaggedError()("SpecRuntimeMismatchError", {
|
|
17
|
+
specPath: Schema.String,
|
|
18
|
+
expectedRuntime: Schema.Literal("Convex", "Node"),
|
|
19
|
+
actualRuntime: Schema.Literal("Convex", "Node")
|
|
20
|
+
}) {};
|
|
21
|
+
var ImplMissingSpecImportError = class extends Schema.TaggedError()("ImplMissingSpecImportError", {
|
|
22
|
+
implPath: Schema.String,
|
|
23
|
+
expectedSpecPath: Schema.String
|
|
24
|
+
}) {};
|
|
25
|
+
var ImplMissingDefaultLayerError = class extends Schema.TaggedError()("ImplMissingDefaultLayerError", { implPath: Schema.String }) {};
|
|
26
|
+
var ImplNotFinalizedError = class extends Schema.TaggedError()("ImplNotFinalizedError", { implPath: Schema.String }) {};
|
|
27
|
+
var ImplMissingFunctionsError = class extends Schema.TaggedError()("ImplMissingFunctionsError", {
|
|
28
|
+
implPath: Schema.String,
|
|
29
|
+
groupPath: Schema.String,
|
|
30
|
+
missingFunctionNames: Schema.Array(Schema.String)
|
|
31
|
+
}) {};
|
|
32
|
+
var SchemaInvalidDefaultExportError = class extends Schema.TaggedError()("SchemaInvalidDefaultExportError", { schemaPath: Schema.String }) {};
|
|
33
|
+
var ParentChildNameCollisionError = class extends Schema.TaggedError()("ParentChildNameCollisionError", {
|
|
34
|
+
parentSpecPath: Schema.String,
|
|
35
|
+
childSpecPath: Schema.String,
|
|
36
|
+
collisionName: Schema.String,
|
|
37
|
+
collisionKind: Schema.Literal("function", "group")
|
|
38
|
+
}) {};
|
|
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);
|
|
41
|
+
const isCodegenError = (error) => {
|
|
42
|
+
if (isBuildError(error)) return true;
|
|
43
|
+
return Schema.is(CodegenError)(error);
|
|
44
|
+
};
|
|
45
|
+
const cross = pipe(AnsiDoc.char("✘"), AnsiDoc.annotate(Ansi.red));
|
|
46
|
+
const stemFromSpecPath = (specPath) => {
|
|
47
|
+
const lastSep = Math.max(specPath.lastIndexOf("/"), specPath.lastIndexOf("\\"));
|
|
48
|
+
const basename = lastSep < 0 ? specPath : specPath.slice(lastSep + 1);
|
|
49
|
+
return basename.endsWith(".spec.ts") ? basename.slice(0, -8) : basename;
|
|
50
|
+
};
|
|
51
|
+
const singleLine = (...parts) => pipe(cross, AnsiDoc.catWithSpace(AnsiDoc.hcat(parts)));
|
|
52
|
+
const renderMissingImplFileError = (error) => singleLine(AnsiDoc.text("Spec "), formatPathDoc(error.specPath), AnsiDoc.text(" has no sibling impl; create "), formatPathDoc(error.expectedImplPath), AnsiDoc.text(" and default-export a GroupImpl layer from it."));
|
|
53
|
+
const renderMissingSpecFileError = (error) => singleLine(AnsiDoc.text("Impl "), formatPathDoc(error.implPath), AnsiDoc.text(" has no sibling spec; create "), formatPathDoc(error.expectedSpecPath), AnsiDoc.text(" and default-export a GroupSpec from it, or remove the impl."));
|
|
54
|
+
const renderSpecMissingDefaultGroupSpecError = (error) => singleLine(AnsiDoc.text("Spec "), formatPathDoc(error.specPath), AnsiDoc.text(" must default-export a GroupSpec; build it with GroupSpec.make() or GroupSpec.makeNode()."));
|
|
55
|
+
const renderSpecRuntimeMismatchError = (error) => {
|
|
56
|
+
const constructor = error.expectedRuntime === "Node" ? "GroupSpec.makeNode()" : "GroupSpec.make()";
|
|
57
|
+
const moveHint = error.expectedRuntime === "Node" ? " or move the file into confect/node/." : " or move the file out of confect/node/.";
|
|
58
|
+
return singleLine(AnsiDoc.text("Spec "), formatPathDoc(error.specPath), AnsiDoc.text(` declares a ${error.actualRuntime} GroupSpec but its location requires ${error.expectedRuntime}; use ${constructor}${moveHint}`));
|
|
59
|
+
};
|
|
60
|
+
const renderImplMissingSpecImportError = (error) => {
|
|
61
|
+
const stem = stemFromSpecPath(error.expectedSpecPath);
|
|
62
|
+
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
|
+
};
|
|
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)`)."));
|
|
66
|
+
const renderImplMissingFunctionsError = (error) => {
|
|
67
|
+
const names = error.missingFunctionNames.join(", ");
|
|
68
|
+
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
|
+
};
|
|
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({ ... }))."));
|
|
72
|
+
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
|
+
/**
|
|
74
|
+
* Render any {@link CodegenError} into a styled, ready-to-print string.
|
|
75
|
+
* Single-error variants render to a one-line `✘`-prefixed message;
|
|
76
|
+
* `BundleFailedError` (the only multi-error variant) renders to a header
|
|
77
|
+
* plus an esbuild diagnostic block.
|
|
78
|
+
*/
|
|
79
|
+
const renderCodegenError = (error) => {
|
|
80
|
+
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);
|
|
82
|
+
};
|
|
83
|
+
const logCodegenError = (error) => Effect.sync(() => console.error(renderCodegenError(error)));
|
|
84
|
+
/**
|
|
85
|
+
* Log any {@link CodegenError} thrown by `effect` and propagate the failure
|
|
86
|
+
* unchanged so the caller's error channel is preserved (used by the
|
|
87
|
+
* `codegen` command, which needs the failure to surface as a non-zero exit
|
|
88
|
+
* code).
|
|
89
|
+
*/
|
|
90
|
+
const tapAndLog = (effect) => effect.pipe(Effect.tapError((error) => isCodegenError(error) ? logCodegenError(error) : Effect.void));
|
|
91
|
+
/**
|
|
92
|
+
* Catch any {@link CodegenError} thrown by `effect`, log it, and resolve to
|
|
93
|
+
* `Option.none()` (used by the `dev` command's sync loop, which continues
|
|
94
|
+
* after a failed sync rather than exiting). Success resolves to
|
|
95
|
+
* `Option.some(value)`.
|
|
96
|
+
*/
|
|
97
|
+
const catchAndLog = (effect) => Effect.catchIf(Effect.map(effect, Option.some), isCodegenError, (error) => logCodegenError(error).pipe(Effect.as(Option.none())));
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
export { ImplMissingDefaultLayerError, ImplMissingFunctionsError, ImplMissingSpecImportError, ImplNotFinalizedError, MissingImplFileError, MissingSchemaFileError, MissingSpecFileError, ParentChildNameCollisionError, SchemaInvalidDefaultExportError, SpecMissingDefaultGroupSpecError, SpecRuntimeMismatchError, catchAndLog, tapAndLog };
|
|
101
|
+
//# sourceMappingURL=CodegenError.mjs.map
|
|
@@ -0,0 +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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"}
|
package/dist/FunctionPaths.mjs
CHANGED