@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.
@@ -0,0 +1,170 @@
1
+ import { fromBundlerError } from "./BuildError.mjs";
2
+ import { ImplMissingDefaultLayerError, ImplMissingFunctionsError, ImplMissingSpecImportError, ImplNotFinalizedError, SpecMissingDefaultGroupSpecError, SpecRuntimeMismatchError } from "./CodegenError.mjs";
3
+ import { ConfectDirectory } from "./ConfectDirectory.mjs";
4
+ import { bundle, directlyImports } from "./Bundler.mjs";
5
+ import { removePathExtension } from "./utils.mjs";
6
+ import { Array, Effect, Layer, Option, Ref, String } from "effect";
7
+ import { GroupSpec, Registry } from "@confect/core";
8
+ import { FileSystem, Path } from "@effect/platform";
9
+ import * as GroupImpl from "@confect/server/GroupImpl";
10
+
11
+ //#region src/LeafModule.ts
12
+ const SPEC_SUFFIX = ".spec.ts";
13
+ const IMPL_SUFFIX = ".impl.ts";
14
+ const swapModuleSuffix = (relativePath, fromSuffix, toSuffix) => Effect.gen(function* () {
15
+ const path = yield* Path.Path;
16
+ const { dir, name, ext } = path.parse(relativePath);
17
+ if (ext !== ".ts" || !name.endsWith(fromSuffix.slice(0, -3))) return relativePath;
18
+ const nextName = `${name.slice(0, -fromSuffix.slice(0, -3).length)}${toSuffix.slice(0, -3)}`;
19
+ return dir.length > 0 ? path.join(dir, `${nextName}${ext}`) : `${nextName}${ext}`;
20
+ });
21
+ const isLeafSpecPath = (relativePath) => relativePath.endsWith(SPEC_SUFFIX);
22
+ const isLeafImplPath = (relativePath) => relativePath.endsWith(IMPL_SUFFIX);
23
+ const exportNameFromModulePath = (relativePath) => Effect.gen(function* () {
24
+ const { name, ext } = (yield* Path.Path).parse(relativePath);
25
+ if (ext !== ".ts") return name;
26
+ return name.endsWith(".spec") ? name.slice(0, -5) : name;
27
+ });
28
+ const groupPathFromRelativeModulePath = (relativePath) => Effect.gen(function* () {
29
+ const path = yield* Path.Path;
30
+ const { dir, name, ext } = path.parse(relativePath);
31
+ const stem = ext === ".ts" && name.endsWith(".spec") ? name.slice(0, -5) : name;
32
+ const dirSegments = Array.filter(String.split(dir, path.sep), String.isNonEmpty);
33
+ const pathSegments = Array.append(dirSegments, stem);
34
+ return {
35
+ pathSegments,
36
+ groupPathDot: Array.join(pathSegments, ".")
37
+ };
38
+ });
39
+ const specImportPathFromGenerated = (specRelativePath) => Effect.gen(function* () {
40
+ return `../${yield* removePathExtension(specRelativePath)}`;
41
+ });
42
+ const specPathForImpl = (implRelativePath) => swapModuleSuffix(implRelativePath, IMPL_SUFFIX, SPEC_SUFFIX);
43
+ const implPathForSpec = (specRelativePath) => swapModuleSuffix(specRelativePath, SPEC_SUFFIX, IMPL_SUFFIX);
44
+ const isNodeLeafModule = (relativePath) => relativePath.startsWith("node/") || relativePath.startsWith("node\\");
45
+ const toNodeRegistryLeaf = (leaf) => ({
46
+ ...leaf,
47
+ pathSegments: [leaf.exportName],
48
+ groupPathDot: leaf.exportName
49
+ });
50
+ const registeredFunctionsRelativePath = (leaf) => Effect.gen(function* () {
51
+ return (yield* Path.Path).join("registeredFunctions", ...leaf.pathSegments.slice(leaf.runtime === "Node" ? 1 : 0)) + ".ts";
52
+ });
53
+ const discoverLeafSpecFiles = Effect.gen(function* () {
54
+ const fs = yield* FileSystem.FileSystem;
55
+ const path = yield* Path.Path;
56
+ const confectDirectory = yield* ConfectDirectory.get;
57
+ const excludedDirs = new Set(["_generated", "tables"]);
58
+ const excludedFiles = new Set(["nodeSpec.ts", "spec.ts"]);
59
+ const allPaths = yield* fs.readDirectory(confectDirectory, { recursive: true });
60
+ return Array.filter(allPaths, (relativePath) => {
61
+ if (!isLeafSpecPath(relativePath)) return false;
62
+ if (excludedFiles.has(relativePath)) return false;
63
+ const segments = String.split(relativePath, path.sep);
64
+ return !Array.some(segments, (segment) => excludedDirs.has(segment));
65
+ });
66
+ });
67
+ const discoverLeafImplFiles = Effect.gen(function* () {
68
+ const fs = yield* FileSystem.FileSystem;
69
+ const path = yield* Path.Path;
70
+ const confectDirectory = yield* ConfectDirectory.get;
71
+ const excludedDirs = new Set(["_generated", "tables"]);
72
+ const allPaths = yield* fs.readDirectory(confectDirectory, { recursive: true });
73
+ return Array.filter(allPaths, (relativePath) => {
74
+ if (!isLeafImplPath(relativePath)) return false;
75
+ const segments = String.split(relativePath, path.sep);
76
+ return !Array.some(segments, (segment) => excludedDirs.has(segment));
77
+ });
78
+ });
79
+ const toLeafModule = (specRelativePath) => Effect.gen(function* () {
80
+ const exportName = yield* exportNameFromModulePath(specRelativePath);
81
+ const { pathSegments, groupPathDot } = yield* groupPathFromRelativeModulePath(specRelativePath);
82
+ const specImportPath = yield* specImportPathFromGenerated(specRelativePath);
83
+ const runtime = isNodeLeafModule(specRelativePath) ? "Node" : "Convex";
84
+ return {
85
+ relativePath: specRelativePath,
86
+ pathSegments,
87
+ groupPathDot,
88
+ exportName,
89
+ runtime,
90
+ registryGroupPathDot: runtime === "Node" ? exportName : groupPathDot,
91
+ specImportPath
92
+ };
93
+ });
94
+ const absoluteModulePath = (relativePath) => Effect.gen(function* () {
95
+ const confectDirectory = yield* ConfectDirectory.get;
96
+ return (yield* Path.Path).resolve(confectDirectory, relativePath);
97
+ });
98
+ /**
99
+ * Validate that the leaf's spec file default-exports a `GroupSpec` whose
100
+ * runtime matches the leaf's location (`Convex` for files outside
101
+ * `confect/node/`, `Node` for files inside it). Returns the validated
102
+ * `GroupSpec` so callers can avoid re-bundling for later inspection (e.g.
103
+ * parent/child name-collision checks at codegen time).
104
+ */
105
+ const validateSpec = (leaf) => Effect.gen(function* () {
106
+ const absolutePath = yield* absoluteModulePath(leaf.relativePath);
107
+ const { module } = yield* bundle(absolutePath).pipe(Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)));
108
+ const groupSpec = module.default;
109
+ if (!GroupSpec.isGroupSpec(groupSpec)) return yield* new SpecMissingDefaultGroupSpecError({ specPath: leaf.relativePath });
110
+ if (groupSpec.runtime !== leaf.runtime) return yield* new SpecRuntimeMismatchError({
111
+ specPath: leaf.relativePath,
112
+ expectedRuntime: leaf.runtime,
113
+ actualRuntime: groupSpec.runtime
114
+ });
115
+ return groupSpec;
116
+ });
117
+ /**
118
+ * Walk the built `Context` for a `Finalized` `GroupImpl` service value. The
119
+ * lookup is value-shaped (via `GroupImpl.isFinalizedGroupImpl`) so we don't
120
+ * need to know the group's path up front to construct a typed tag for it.
121
+ */
122
+ const findFinalizedGroupImpl = (context) => Array.findFirst(context.unsafeMap.values(), GroupImpl.isFinalizedGroupImpl);
123
+ /**
124
+ * Build the impl layer with a fresh `Registry` so each validation is
125
+ * isolated from prior validations' `FunctionImpl.make` writes. The CLI no
126
+ * longer reads the registry directly — `GroupImpl.finalize` snapshots the
127
+ * registered function names onto the produced `Finalized` `GroupImpl`
128
+ * service value — but a fresh `Ref` is still required because the default
129
+ * `Context.Reference` is cached globally and would otherwise accumulate
130
+ * items across impls.
131
+ */
132
+ const buildImplLayer = (implLayer) => Effect.gen(function* () {
133
+ const registry = Ref.unsafeMake({});
134
+ return yield* Layer.build(implLayer).pipe(Effect.provideService(Registry.Registry, registry));
135
+ }).pipe(Effect.scoped);
136
+ /**
137
+ * Validate that the leaf's sibling impl file imports the spec, default-exports
138
+ * a finalized `GroupImpl` layer, and provides a `FunctionImpl` for every
139
+ * function declared by the spec.
140
+ */
141
+ const validateImpl = (leaf) => Effect.gen(function* () {
142
+ const implRelativePath = yield* implPathForSpec(leaf.relativePath);
143
+ const implAbsolutePath = yield* absoluteModulePath(implRelativePath);
144
+ const specAbsolutePath = yield* absoluteModulePath(leaf.relativePath);
145
+ const bundled = yield* bundle(implAbsolutePath).pipe(Effect.mapError((error) => fromBundlerError(implRelativePath, error)));
146
+ if (!(yield* directlyImports(bundled, implAbsolutePath, specAbsolutePath))) return yield* new ImplMissingSpecImportError({
147
+ implPath: implRelativePath,
148
+ expectedSpecPath: leaf.relativePath
149
+ });
150
+ if (!Layer.isLayer(bundled.module.default)) return yield* new ImplMissingDefaultLayerError({ implPath: implRelativePath });
151
+ const { module: specModule } = yield* bundle(specAbsolutePath).pipe(Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)));
152
+ const groupSpec = specModule.default;
153
+ const expectedFunctionNames = Object.keys(groupSpec.functions);
154
+ const context = yield* buildImplLayer(bundled.module.default);
155
+ const finalizedGroupImpl = yield* Option.match(findFinalizedGroupImpl(context), {
156
+ onNone: () => new ImplNotFinalizedError({ implPath: implRelativePath }),
157
+ onSome: Effect.succeed
158
+ });
159
+ const registeredSet = new Set(finalizedGroupImpl.registeredFunctionNames);
160
+ const missing = expectedFunctionNames.filter((name) => !registeredSet.has(name));
161
+ if (missing.length > 0) return yield* new ImplMissingFunctionsError({
162
+ implPath: implRelativePath,
163
+ groupPath: finalizedGroupImpl.groupPath,
164
+ missingFunctionNames: missing
165
+ });
166
+ });
167
+
168
+ //#endregion
169
+ export { discoverLeafImplFiles, discoverLeafSpecFiles, implPathForSpec, isLeafImplPath, isLeafSpecPath, registeredFunctionsRelativePath, specPathForImpl, toLeafModule, toNodeRegistryLeaf, validateImpl, validateSpec };
170
+ //# sourceMappingURL=LeafModule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeafModule.mjs","names":["Bundler.bundle","Bundler.directlyImports"],"sources":["../src/LeafModule.ts"],"sourcesContent":["import { GroupSpec, Registry } from \"@confect/core\";\nimport * as GroupImpl from \"@confect/server/GroupImpl\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport type { Context } from \"effect\";\nimport { Array, Effect, Layer, Option, Ref, String } from \"effect\";\nimport { fromBundlerError } from \"./BuildError\";\nimport * as Bundler from \"./Bundler\";\nimport {\n ImplMissingDefaultLayerError,\n ImplMissingFunctionsError,\n ImplMissingSpecImportError,\n ImplNotFinalizedError,\n SpecMissingDefaultGroupSpecError,\n SpecRuntimeMismatchError,\n} from \"./CodegenError\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\nimport { removePathExtension } from \"./utils\";\n\nexport interface LeafModule {\n readonly relativePath: string;\n readonly pathSegments: readonly [string, ...string[]];\n readonly groupPathDot: string;\n readonly registryGroupPathDot: string;\n readonly exportName: string;\n readonly runtime: \"Convex\" | \"Node\";\n readonly specImportPath: string;\n}\n\nexport const SPEC_SUFFIX = \".spec.ts\";\nexport const IMPL_SUFFIX = \".impl.ts\";\n\nconst swapModuleSuffix = (\n relativePath: string,\n fromSuffix: string,\n toSuffix: string,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n if (ext !== \".ts\" || !name.endsWith(fromSuffix.slice(0, -\".ts\".length))) {\n return relativePath;\n }\n\n const stem = name.slice(0, -fromSuffix.slice(0, -\".ts\".length).length);\n const nextName = `${stem}${toSuffix.slice(0, -\".ts\".length)}`;\n return dir.length > 0\n ? path.join(dir, `${nextName}${ext}`)\n : `${nextName}${ext}`;\n });\n\nexport const isLeafSpecPath = (relativePath: string) =>\n relativePath.endsWith(SPEC_SUFFIX);\n\nexport const isLeafImplPath = (relativePath: string) =>\n relativePath.endsWith(IMPL_SUFFIX);\n\nexport const exportNameFromModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { name, ext } = path.parse(relativePath);\n if (ext !== \".ts\") {\n return name;\n }\n return name.endsWith(\".spec\") ? name.slice(0, -\".spec\".length) : name;\n });\n\nexport const groupPathFromRelativeModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n const stem =\n ext === \".ts\" && name.endsWith(\".spec\")\n ? name.slice(0, -\".spec\".length)\n : name;\n const dirSegments = Array.filter(\n String.split(dir, path.sep),\n String.isNonEmpty,\n );\n const pathSegments = Array.append(dirSegments, stem) as [\n string,\n ...string[],\n ];\n return {\n pathSegments,\n groupPathDot: Array.join(pathSegments, \".\"),\n };\n });\n\nexport const specImportPathFromGenerated = (specRelativePath: string) =>\n Effect.gen(function* () {\n const withoutExt = yield* removePathExtension(specRelativePath);\n return `../${withoutExt}`;\n });\n\nexport const specPathForImpl = (implRelativePath: string) =>\n swapModuleSuffix(implRelativePath, IMPL_SUFFIX, SPEC_SUFFIX);\n\nexport const implPathForSpec = (specRelativePath: string) =>\n swapModuleSuffix(specRelativePath, SPEC_SUFFIX, IMPL_SUFFIX);\n\nexport const isNodeLeafModule = (relativePath: string) =>\n relativePath.startsWith(\"node/\") || relativePath.startsWith(\"node\\\\\");\n\nexport const toNodeRegistryLeaf = (leaf: LeafModule): LeafModule => ({\n ...leaf,\n pathSegments: [leaf.exportName],\n groupPathDot: leaf.exportName,\n});\n\nexport const registeredFunctionsRelativePath = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n return (\n path.join(\n \"registeredFunctions\",\n ...leaf.pathSegments.slice(leaf.runtime === \"Node\" ? 1 : 0),\n ) + \".ts\"\n );\n });\n\nexport const discoverLeafSpecFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n const excludedFiles = new Set([\"nodeSpec.ts\", \"spec.ts\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafSpecPath(relativePath)) {\n return false;\n }\n\n if (excludedFiles.has(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const discoverLeafImplFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafImplPath(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const toLeafModule = (specRelativePath: string) =>\n Effect.gen(function* () {\n const exportName = yield* exportNameFromModulePath(specRelativePath);\n const { pathSegments, groupPathDot } =\n yield* groupPathFromRelativeModulePath(specRelativePath);\n const specImportPath = yield* specImportPathFromGenerated(specRelativePath);\n const runtime = isNodeLeafModule(specRelativePath) ? \"Node\" : \"Convex\";\n\n return {\n relativePath: specRelativePath,\n pathSegments,\n groupPathDot,\n exportName,\n runtime,\n registryGroupPathDot: runtime === \"Node\" ? exportName : groupPathDot,\n specImportPath,\n } satisfies LeafModule;\n });\n\nconst absoluteModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const confectDirectory = yield* ConfectDirectory.get;\n const path = yield* Path.Path;\n return path.resolve(confectDirectory, relativePath);\n });\n\n/**\n * Validate that the leaf's spec file default-exports a `GroupSpec` whose\n * runtime matches the leaf's location (`Convex` for files outside\n * `confect/node/`, `Node` for files inside it). Returns the validated\n * `GroupSpec` so callers can avoid re-bundling for later inspection (e.g.\n * parent/child name-collision checks at codegen time).\n */\nexport const validateSpec = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const absolutePath = yield* absoluteModulePath(leaf.relativePath);\n const { module } = yield* Bundler.bundle(absolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n\n const groupSpec = module.default;\n\n if (!GroupSpec.isGroupSpec(groupSpec)) {\n return yield* new SpecMissingDefaultGroupSpecError({\n specPath: leaf.relativePath,\n });\n }\n\n if (groupSpec.runtime !== leaf.runtime) {\n return yield* new SpecRuntimeMismatchError({\n specPath: leaf.relativePath,\n expectedRuntime: leaf.runtime,\n actualRuntime: groupSpec.runtime,\n });\n }\n\n return groupSpec;\n });\n\n/**\n * Walk the built `Context` for a `Finalized` `GroupImpl` service value. The\n * lookup is value-shaped (via `GroupImpl.isFinalizedGroupImpl`) so we don't\n * need to know the group's path up front to construct a typed tag for it.\n */\nconst findFinalizedGroupImpl = <S>(\n context: Context.Context<S>,\n): Option.Option<GroupImpl.AnyFinalized> =>\n Array.findFirst(context.unsafeMap.values(), GroupImpl.isFinalizedGroupImpl);\n\n/**\n * Build the impl layer with a fresh `Registry` so each validation is\n * isolated from prior validations' `FunctionImpl.make` writes. The CLI no\n * longer reads the registry directly — `GroupImpl.finalize` snapshots the\n * registered function names onto the produced `Finalized` `GroupImpl`\n * service value — but a fresh `Ref` is still required because the default\n * `Context.Reference` is cached globally and would otherwise accumulate\n * items across impls.\n */\nconst buildImplLayer = (implLayer: Layer.Layer<unknown>) =>\n Effect.gen(function* () {\n const registry = Ref.unsafeMake<Registry.RegistryItems>({});\n return yield* Layer.build(\n implLayer as Layer.Layer<unknown, never, never>,\n ).pipe(Effect.provideService(Registry.Registry, registry));\n }).pipe(Effect.scoped);\n\n/**\n * Validate that the leaf's sibling impl file imports the spec, default-exports\n * a finalized `GroupImpl` layer, and provides a `FunctionImpl` for every\n * function declared by the spec.\n */\nexport const validateImpl = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const implRelativePath = yield* implPathForSpec(leaf.relativePath);\n const implAbsolutePath = yield* absoluteModulePath(implRelativePath);\n const specAbsolutePath = yield* absoluteModulePath(leaf.relativePath);\n\n const bundled = yield* Bundler.bundle(implAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(implRelativePath, error)),\n );\n\n if (\n !(yield* Bundler.directlyImports(\n bundled,\n implAbsolutePath,\n specAbsolutePath,\n ))\n ) {\n return yield* new ImplMissingSpecImportError({\n implPath: implRelativePath,\n expectedSpecPath: leaf.relativePath,\n });\n }\n\n if (!Layer.isLayer(bundled.module.default)) {\n return yield* new ImplMissingDefaultLayerError({\n implPath: implRelativePath,\n });\n }\n\n const { module: specModule } = yield* Bundler.bundle(specAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n const groupSpec = specModule.default as GroupSpec.AnyWithProps;\n const expectedFunctionNames = Object.keys(groupSpec.functions);\n\n const context = yield* buildImplLayer(\n bundled.module.default as Layer.Layer<unknown>,\n );\n const finalizedGroupImpl = yield* Option.match(\n findFinalizedGroupImpl(context),\n {\n onNone: () => new ImplNotFinalizedError({ implPath: implRelativePath }),\n onSome: Effect.succeed,\n },\n );\n\n const registeredSet = new Set(finalizedGroupImpl.registeredFunctionNames);\n const missing = expectedFunctionNames.filter(\n (name) => !registeredSet.has(name),\n );\n\n if (missing.length > 0) {\n return yield* new ImplMissingFunctionsError({\n implPath: implRelativePath,\n groupPath: finalizedGroupImpl.groupPath,\n missingFunctionNames: missing,\n });\n }\n });\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { Array, Option, Order, Record, pipe } from "effect";
2
+
3
+ //#region src/SpecAssemblyNode.ts
4
+ const importBindingFromLeaf = (leaf) => ({
5
+ importPath: leaf.specImportPath,
6
+ exportName: leaf.exportName
7
+ });
8
+ const assemblyNodesAtDepth = (leaves, depth) => pipe(Array.groupBy(leaves, (leaf) => leaf.pathSegments[depth]), Record.toEntries, Array.sortBy(Order.mapInput(Order.string, ([segment]) => segment)), Array.map(([segment, groupLeaves]) => {
9
+ const terminal = Array.findFirst(groupLeaves, (leaf) => leaf.pathSegments.length === depth + 1);
10
+ const descendants = Array.filter(groupLeaves, (leaf) => leaf.pathSegments.length > depth + 1);
11
+ return {
12
+ segment,
13
+ importBinding: Option.map(terminal, importBindingFromLeaf),
14
+ children: assemblyNodesAtDepth(descendants, depth + 1)
15
+ };
16
+ }));
17
+ const assemblyNodesFromLeaves = (leaves) => assemblyNodesAtDepth(leaves, 0);
18
+ const partitionByRuntime = (leaves) => {
19
+ const [node, convex] = Array.partition(leaves, (leaf) => leaf.runtime === "Convex");
20
+ return {
21
+ convex,
22
+ node
23
+ };
24
+ };
25
+ const importBindingsForNode = (node) => pipe(node.children, Array.flatMap(importBindingsForNode), (childBindings) => Option.match(node.importBinding, {
26
+ onNone: () => childBindings,
27
+ onSome: (binding) => Array.prepend(childBindings, binding)
28
+ }));
29
+ const collectImportBindings = (nodes) => pipe(Array.flatMap(nodes, importBindingsForNode), (bindings) => Record.fromIterableBy(bindings, (binding) => binding.exportName), Record.toEntries, Array.sortBy(Order.mapInput(Order.string, ([exportName]) => exportName)), Array.map(([, binding]) => binding));
30
+
31
+ //#endregion
32
+ export { assemblyNodesFromLeaves, collectImportBindings, partitionByRuntime };
33
+ //# sourceMappingURL=SpecAssemblyNode.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpecAssemblyNode.mjs","names":[],"sources":["../src/SpecAssemblyNode.ts"],"sourcesContent":["import type { LeafModule } from \"./LeafModule\";\nimport { Array, Option, Order, pipe, Record } from \"effect\";\n\nexport interface SpecImportBinding {\n readonly importPath: string;\n readonly exportName: string;\n}\n\nexport interface SpecAssemblyNode {\n readonly segment: string;\n readonly importBinding: Option.Option<SpecImportBinding>;\n readonly children: ReadonlyArray<SpecAssemblyNode>;\n}\n\nconst importBindingFromLeaf = (leaf: LeafModule): SpecImportBinding => ({\n importPath: leaf.specImportPath,\n exportName: leaf.exportName,\n});\n\nconst assemblyNodesAtDepth = (\n leaves: ReadonlyArray<LeafModule>,\n depth: number,\n): ReadonlyArray<SpecAssemblyNode> =>\n pipe(\n Array.groupBy(leaves, (leaf) => leaf.pathSegments[depth]!),\n Record.toEntries,\n Array.sortBy(Order.mapInput(Order.string, ([segment]) => segment)),\n Array.map(([segment, groupLeaves]) => {\n const terminal = Array.findFirst(\n groupLeaves,\n (leaf) => leaf.pathSegments.length === depth + 1,\n );\n const descendants = Array.filter(\n groupLeaves,\n (leaf) => leaf.pathSegments.length > depth + 1,\n );\n return {\n segment,\n importBinding: Option.map(terminal, importBindingFromLeaf),\n children: assemblyNodesAtDepth(descendants, depth + 1),\n };\n }),\n );\n\nexport const assemblyNodesFromLeaves = (\n leaves: ReadonlyArray<LeafModule>,\n): ReadonlyArray<SpecAssemblyNode> => assemblyNodesAtDepth(leaves, 0);\n\nexport const partitionByRuntime = (\n leaves: ReadonlyArray<LeafModule>,\n): {\n readonly convex: ReadonlyArray<LeafModule>;\n readonly node: ReadonlyArray<LeafModule>;\n} => {\n const [node, convex] = Array.partition(\n leaves,\n (leaf) => leaf.runtime === \"Convex\",\n );\n return { convex, node };\n};\n\nconst importBindingsForNode = (\n node: SpecAssemblyNode,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(node.children, Array.flatMap(importBindingsForNode), (childBindings) =>\n Option.match(node.importBinding, {\n onNone: () => childBindings,\n onSome: (binding) => Array.prepend(childBindings, binding),\n }),\n );\n\nexport const collectImportBindings = (\n nodes: ReadonlyArray<SpecAssemblyNode>,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(\n Array.flatMap(nodes, importBindingsForNode),\n (bindings) =>\n Record.fromIterableBy(bindings, (binding) => binding.exportName),\n Record.toEntries,\n Array.sortBy(Order.mapInput(Order.string, ([exportName]) => exportName)),\n Array.map(([, binding]) => binding),\n );\n"],"mappings":";;;AAcA,MAAM,yBAAyB,UAAyC;CACtE,YAAY,KAAK;CACjB,YAAY,KAAK;CAClB;AAED,MAAM,wBACJ,QACA,UAEA,KACE,MAAM,QAAQ,SAAS,SAAS,KAAK,aAAa,OAAQ,EAC1D,OAAO,WACP,MAAM,OAAO,MAAM,SAAS,MAAM,SAAS,CAAC,aAAa,QAAQ,CAAC,EAClE,MAAM,KAAK,CAAC,SAAS,iBAAiB;CACpC,MAAM,WAAW,MAAM,UACrB,cACC,SAAS,KAAK,aAAa,WAAW,QAAQ,EAChD;CACD,MAAM,cAAc,MAAM,OACxB,cACC,SAAS,KAAK,aAAa,SAAS,QAAQ,EAC9C;AACD,QAAO;EACL;EACA,eAAe,OAAO,IAAI,UAAU,sBAAsB;EAC1D,UAAU,qBAAqB,aAAa,QAAQ,EAAE;EACvD;EACD,CACH;AAEH,MAAa,2BACX,WACoC,qBAAqB,QAAQ,EAAE;AAErE,MAAa,sBACX,WAIG;CACH,MAAM,CAAC,MAAM,UAAU,MAAM,UAC3B,SACC,SAAS,KAAK,YAAY,SAC5B;AACD,QAAO;EAAE;EAAQ;EAAM;;AAGzB,MAAM,yBACJ,SAEA,KAAK,KAAK,UAAU,MAAM,QAAQ,sBAAsB,GAAG,kBACzD,OAAO,MAAM,KAAK,eAAe;CAC/B,cAAc;CACd,SAAS,YAAY,MAAM,QAAQ,eAAe,QAAQ;CAC3D,CAAC,CACH;AAEH,MAAa,yBACX,UAEA,KACE,MAAM,QAAQ,OAAO,sBAAsB,GAC1C,aACC,OAAO,eAAe,WAAW,YAAY,QAAQ,WAAW,EAClE,OAAO,WACP,MAAM,OAAO,MAAM,SAAS,MAAM,SAAS,CAAC,gBAAgB,WAAW,CAAC,EACxE,MAAM,KAAK,GAAG,aAAa,QAAQ,CACpC"}