@confect/cli 9.0.0-next.4 → 9.0.0-next.6
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 +178 -0
- package/dist/Bundler.mjs +63 -72
- package/dist/Bundler.mjs.map +1 -1
- package/dist/CodegenError.mjs +22 -9
- package/dist/CodegenError.mjs.map +1 -1
- package/dist/LeafModule.mjs +2 -2
- package/dist/LeafModule.mjs.map +1 -1
- package/dist/SpecAssemblyNode.mjs +1 -19
- package/dist/SpecAssemblyNode.mjs.map +1 -1
- package/dist/TableModule.mjs +90 -0
- package/dist/TableModule.mjs.map +1 -0
- package/dist/confect/codegen.mjs +175 -78
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/confect/dev.mjs +15 -12
- package/dist/confect/dev.mjs.map +1 -1
- package/dist/log.mjs +4 -3
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/templates.mjs +123 -34
- package/dist/templates.mjs.map +1 -1
- package/package.json +42 -42
- package/src/Bundler.ts +67 -90
- package/src/CodegenError.ts +90 -28
- package/src/LeafModule.ts +1 -1
- package/src/SpecAssemblyNode.ts +0 -36
- package/src/TableModule.ts +153 -0
- package/src/confect/codegen.ts +323 -132
- package/src/confect/dev.ts +52 -10
- package/src/log.ts +4 -2
- package/src/templates.ts +200 -59
package/dist/LeafModule.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LeafModule.mjs","names":["Bundler.bundle","Bundler.directlyImports"],"sources":["../src/LeafModule.ts"],"sourcesContent":["import { GroupSpec, Registry } from \"@confect/core\";\nimport * as GroupImpl from \"@confect/server/GroupImpl\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport type { Context } from \"effect\";\nimport { Array, Effect, Layer, Option, Ref, String } from \"effect\";\nimport { fromBundlerError } from \"./BuildError\";\nimport * as Bundler from \"./Bundler\";\nimport {\n ImplMissingDefaultLayerError,\n ImplMissingFunctionsError,\n ImplMissingSpecImportError,\n ImplNotFinalizedError,\n SpecMissingDefaultGroupSpecError,\n SpecRuntimeMismatchError,\n} from \"./CodegenError\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\nimport { removePathExtension } from \"./utils\";\n\nexport interface LeafModule {\n readonly relativePath: string;\n readonly pathSegments: readonly [string, ...string[]];\n readonly groupPathDot: string;\n readonly registryGroupPathDot: string;\n readonly exportName: string;\n readonly runtime: \"Convex\" | \"Node\";\n readonly specImportPath: string;\n}\n\nexport const SPEC_SUFFIX = \".spec.ts\";\nexport const IMPL_SUFFIX = \".impl.ts\";\n\nconst swapModuleSuffix = (\n relativePath: string,\n fromSuffix: string,\n toSuffix: string,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n if (ext !== \".ts\" || !name.endsWith(fromSuffix.slice(0, -\".ts\".length))) {\n return relativePath;\n }\n\n const stem = name.slice(0, -fromSuffix.slice(0, -\".ts\".length).length);\n const nextName = `${stem}${toSuffix.slice(0, -\".ts\".length)}`;\n return dir.length > 0\n ? path.join(dir, `${nextName}${ext}`)\n : `${nextName}${ext}`;\n });\n\nexport const isLeafSpecPath = (relativePath: string) =>\n relativePath.endsWith(SPEC_SUFFIX);\n\nexport const isLeafImplPath = (relativePath: string) =>\n relativePath.endsWith(IMPL_SUFFIX);\n\nexport const exportNameFromModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { name, ext } = path.parse(relativePath);\n if (ext !== \".ts\") {\n return name;\n }\n return name.endsWith(\".spec\") ? name.slice(0, -\".spec\".length) : name;\n });\n\nexport const groupPathFromRelativeModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n const stem =\n ext === \".ts\" && name.endsWith(\".spec\")\n ? name.slice(0, -\".spec\".length)\n : name;\n const dirSegments = Array.filter(\n String.split(dir, path.sep),\n String.isNonEmpty,\n );\n const pathSegments = Array.append(dirSegments, stem) as [\n string,\n ...string[],\n ];\n return {\n pathSegments,\n groupPathDot: Array.join(pathSegments, \".\"),\n };\n });\n\nexport const specImportPathFromGenerated = (specRelativePath: string) =>\n Effect.gen(function* () {\n const withoutExt = yield* removePathExtension(specRelativePath);\n return `../${withoutExt}`;\n });\n\nexport const specPathForImpl = (implRelativePath: string) =>\n swapModuleSuffix(implRelativePath, IMPL_SUFFIX, SPEC_SUFFIX);\n\nexport const implPathForSpec = (specRelativePath: string) =>\n swapModuleSuffix(specRelativePath, SPEC_SUFFIX, IMPL_SUFFIX);\n\nexport const isNodeLeafModule = (relativePath: string) =>\n relativePath.startsWith(\"node/\") || relativePath.startsWith(\"node\\\\\");\n\nexport const toNodeRegistryLeaf = (leaf: LeafModule): LeafModule => ({\n ...leaf,\n pathSegments: [leaf.exportName],\n groupPathDot: leaf.exportName,\n});\n\nexport const registeredFunctionsRelativePath = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n return (\n path.join(\n \"registeredFunctions\",\n ...leaf.pathSegments.slice(leaf.runtime === \"Node\" ? 1 : 0),\n ) + \".ts\"\n );\n });\n\nexport const discoverLeafSpecFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n const excludedFiles = new Set([\"nodeSpec.ts\", \"spec.ts\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafSpecPath(relativePath)) {\n return false;\n }\n\n if (excludedFiles.has(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const discoverLeafImplFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafImplPath(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const toLeafModule = (specRelativePath: string) =>\n Effect.gen(function* () {\n const exportName = yield* exportNameFromModulePath(specRelativePath);\n const { pathSegments, groupPathDot } =\n yield* groupPathFromRelativeModulePath(specRelativePath);\n const specImportPath = yield* specImportPathFromGenerated(specRelativePath);\n const runtime = isNodeLeafModule(specRelativePath) ? \"Node\" : \"Convex\";\n\n return {\n relativePath: specRelativePath,\n pathSegments,\n groupPathDot,\n exportName,\n runtime,\n registryGroupPathDot: runtime === \"Node\" ? exportName : groupPathDot,\n specImportPath,\n } satisfies LeafModule;\n });\n\nconst absoluteModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const confectDirectory = yield* ConfectDirectory.get;\n const path = yield* Path.Path;\n return path.resolve(confectDirectory, relativePath);\n });\n\n/**\n * Validate that the leaf's spec file default-exports a `GroupSpec` whose\n * runtime matches the leaf's location (`Convex` for files outside\n * `confect/node/`, `Node` for files inside it). Returns the validated\n * `GroupSpec` so callers can avoid re-bundling for later inspection (e.g.\n * parent/child name-collision checks at codegen time).\n */\nexport const validateSpec = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const absolutePath = yield* absoluteModulePath(leaf.relativePath);\n const { module } = yield* Bundler.bundle(absolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n\n const groupSpec = module.default;\n\n if (!GroupSpec.isGroupSpec(groupSpec)) {\n return yield* new SpecMissingDefaultGroupSpecError({\n specPath: leaf.relativePath,\n });\n }\n\n if (groupSpec.runtime !== leaf.runtime) {\n return yield* new SpecRuntimeMismatchError({\n specPath: leaf.relativePath,\n expectedRuntime: leaf.runtime,\n actualRuntime: groupSpec.runtime,\n });\n }\n\n return groupSpec;\n });\n\n/**\n * Walk the built `Context` for a `Finalized` `GroupImpl` service value. The\n * lookup is value-shaped (via `GroupImpl.isFinalizedGroupImpl`) so we don't\n * need to know the group's path up front to construct a typed tag for it.\n */\nconst findFinalizedGroupImpl = <S>(\n context: Context.Context<S>,\n): Option.Option<GroupImpl.AnyFinalized> =>\n Array.findFirst(context.unsafeMap.values(), GroupImpl.isFinalizedGroupImpl);\n\n/**\n * Build the impl layer with a fresh `Registry` so each validation is\n * isolated from prior validations' `FunctionImpl.make` writes. The CLI no\n * longer reads the registry directly — `GroupImpl.finalize` snapshots the\n * registered function names onto the produced `Finalized` `GroupImpl`\n * service value — but a fresh `Ref` is still required because the default\n * `Context.Reference` is cached globally and would otherwise accumulate\n * items across impls.\n */\nconst buildImplLayer = (implLayer: Layer.Layer<unknown>) =>\n Effect.gen(function* () {\n const registry = Ref.unsafeMake<Registry.RegistryItems>({});\n return yield* Layer.build(\n implLayer as Layer.Layer<unknown, never, never>,\n ).pipe(Effect.provideService(Registry.Registry, registry));\n }).pipe(Effect.scoped);\n\n/**\n * Validate that the leaf's sibling impl file imports the spec, default-exports\n * a finalized `GroupImpl` layer, and provides a `FunctionImpl` for every\n * function declared by the spec.\n */\nexport const validateImpl = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const implRelativePath = yield* implPathForSpec(leaf.relativePath);\n const implAbsolutePath = yield* absoluteModulePath(implRelativePath);\n const specAbsolutePath = yield* absoluteModulePath(leaf.relativePath);\n\n const bundled = yield* Bundler.bundle(implAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(implRelativePath, error)),\n );\n\n if (\n !(yield* Bundler.directlyImports(\n bundled,\n implAbsolutePath,\n specAbsolutePath,\n ))\n ) {\n return yield* new ImplMissingSpecImportError({\n implPath: implRelativePath,\n expectedSpecPath: leaf.relativePath,\n });\n }\n\n if (!Layer.isLayer(bundled.module.default)) {\n return yield* new ImplMissingDefaultLayerError({\n implPath: implRelativePath,\n });\n }\n\n const { module: specModule } = yield* Bundler.bundle(specAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n const groupSpec = specModule.default as GroupSpec.AnyWithProps;\n const expectedFunctionNames = Object.keys(groupSpec.functions);\n\n const context = yield* buildImplLayer(\n bundled.module.default as Layer.Layer<unknown>,\n );\n const finalizedGroupImpl = yield* Option.match(\n findFinalizedGroupImpl(context),\n {\n onNone: () => new ImplNotFinalizedError({ implPath: implRelativePath }),\n onSome: Effect.succeed,\n },\n );\n\n const registeredSet = new Set(finalizedGroupImpl.registeredFunctionNames);\n const missing = expectedFunctionNames.filter(\n (name) => !registeredSet.has(name),\n );\n\n if (missing.length > 0) {\n return yield* new ImplMissingFunctionsError({\n implPath: implRelativePath,\n groupPath: finalizedGroupImpl.groupPath,\n missingFunctionNames: missing,\n });\n }\n });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LeafModule.mjs","names":["Bundler.bundle","Bundler.directlyImports"],"sources":["../src/LeafModule.ts"],"sourcesContent":["import { GroupSpec, Registry } from \"@confect/core\";\nimport * as GroupImpl from \"@confect/server/GroupImpl\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport type { Context } from \"effect\";\nimport { Array, Effect, Layer, Option, Ref, String } from \"effect\";\nimport { fromBundlerError } from \"./BuildError\";\nimport * as Bundler from \"./Bundler\";\nimport {\n ImplMissingDefaultLayerError,\n ImplMissingFunctionsError,\n ImplMissingSpecImportError,\n ImplNotFinalizedError,\n SpecMissingDefaultGroupSpecError,\n SpecRuntimeMismatchError,\n} from \"./CodegenError\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\nimport { removePathExtension } from \"./utils\";\n\nexport interface LeafModule {\n readonly relativePath: string;\n readonly pathSegments: readonly [string, ...string[]];\n readonly groupPathDot: string;\n readonly registryGroupPathDot: string;\n readonly exportName: string;\n readonly runtime: \"Convex\" | \"Node\";\n readonly specImportPath: string;\n}\n\nexport const SPEC_SUFFIX = \".spec.ts\";\nexport const IMPL_SUFFIX = \".impl.ts\";\n\nconst swapModuleSuffix = (\n relativePath: string,\n fromSuffix: string,\n toSuffix: string,\n) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n if (ext !== \".ts\" || !name.endsWith(fromSuffix.slice(0, -\".ts\".length))) {\n return relativePath;\n }\n\n const stem = name.slice(0, -fromSuffix.slice(0, -\".ts\".length).length);\n const nextName = `${stem}${toSuffix.slice(0, -\".ts\".length)}`;\n return dir.length > 0\n ? path.join(dir, `${nextName}${ext}`)\n : `${nextName}${ext}`;\n });\n\nexport const isLeafSpecPath = (relativePath: string) =>\n relativePath.endsWith(SPEC_SUFFIX);\n\nexport const isLeafImplPath = (relativePath: string) =>\n relativePath.endsWith(IMPL_SUFFIX);\n\nexport const exportNameFromModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { name, ext } = path.parse(relativePath);\n if (ext !== \".ts\") {\n return name;\n }\n return name.endsWith(\".spec\") ? name.slice(0, -\".spec\".length) : name;\n });\n\nexport const groupPathFromRelativeModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { dir, name, ext } = path.parse(relativePath);\n const stem =\n ext === \".ts\" && name.endsWith(\".spec\")\n ? name.slice(0, -\".spec\".length)\n : name;\n const dirSegments = Array.filter(\n String.split(dir, path.sep),\n String.isNonEmpty,\n );\n const pathSegments = Array.append(dirSegments, stem) as [\n string,\n ...string[],\n ];\n return {\n pathSegments,\n groupPathDot: Array.join(pathSegments, \".\"),\n };\n });\n\nexport const specImportPathFromGenerated = (specRelativePath: string) =>\n Effect.gen(function* () {\n const withoutExt = yield* removePathExtension(specRelativePath);\n return `../${withoutExt}`;\n });\n\nexport const specPathForImpl = (implRelativePath: string) =>\n swapModuleSuffix(implRelativePath, IMPL_SUFFIX, SPEC_SUFFIX);\n\nexport const implPathForSpec = (specRelativePath: string) =>\n swapModuleSuffix(specRelativePath, SPEC_SUFFIX, IMPL_SUFFIX);\n\nexport const isNodeLeafModule = (relativePath: string) =>\n relativePath.startsWith(\"node/\") || relativePath.startsWith(\"node\\\\\");\n\nexport const toNodeRegistryLeaf = (leaf: LeafModule): LeafModule => ({\n ...leaf,\n pathSegments: [leaf.exportName],\n groupPathDot: leaf.exportName,\n});\n\nexport const registeredFunctionsRelativePath = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n return (\n path.join(\n \"registeredFunctions\",\n ...leaf.pathSegments.slice(leaf.runtime === \"Node\" ? 1 : 0),\n ) + \".ts\"\n );\n });\n\nexport const discoverLeafSpecFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n const excludedFiles = new Set([\"nodeSpec.ts\", \"spec.ts\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafSpecPath(relativePath)) {\n return false;\n }\n\n if (excludedFiles.has(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const discoverLeafImplFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const excludedDirs = new Set([\"_generated\", \"tables\"]);\n\n const allPaths = yield* fs.readDirectory(confectDirectory, {\n recursive: true,\n });\n\n return Array.filter(allPaths, (relativePath) => {\n if (!isLeafImplPath(relativePath)) {\n return false;\n }\n\n const segments = String.split(relativePath, path.sep);\n return !Array.some(segments, (segment) => excludedDirs.has(segment));\n });\n});\n\nexport const toLeafModule = (specRelativePath: string) =>\n Effect.gen(function* () {\n const exportName = yield* exportNameFromModulePath(specRelativePath);\n const { pathSegments, groupPathDot } =\n yield* groupPathFromRelativeModulePath(specRelativePath);\n const specImportPath = yield* specImportPathFromGenerated(specRelativePath);\n const runtime = isNodeLeafModule(specRelativePath) ? \"Node\" : \"Convex\";\n\n return {\n relativePath: specRelativePath,\n pathSegments,\n groupPathDot,\n exportName,\n runtime,\n registryGroupPathDot: runtime === \"Node\" ? exportName : groupPathDot,\n specImportPath,\n } satisfies LeafModule;\n });\n\nconst absoluteModulePath = (relativePath: string) =>\n Effect.gen(function* () {\n const confectDirectory = yield* ConfectDirectory.get;\n const path = yield* Path.Path;\n return path.resolve(confectDirectory, relativePath);\n });\n\n/**\n * Validate that the leaf's spec file default-exports a `GroupSpec` whose\n * runtime matches the leaf's location (`Convex` for files outside\n * `confect/node/`, `Node` for files inside it). Returns the validated\n * `GroupSpec` so callers can avoid re-bundling for later inspection (e.g.\n * parent/child name-collision checks at codegen time).\n */\nexport const validateSpec = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const absolutePath = yield* absoluteModulePath(leaf.relativePath);\n const { module } = yield* Bundler.bundle(absolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n\n const groupSpec = module.default;\n\n if (!GroupSpec.isGroupSpec(groupSpec)) {\n return yield* new SpecMissingDefaultGroupSpecError({\n specPath: leaf.relativePath,\n });\n }\n\n if (groupSpec.runtime !== leaf.runtime) {\n return yield* new SpecRuntimeMismatchError({\n specPath: leaf.relativePath,\n expectedRuntime: leaf.runtime,\n actualRuntime: groupSpec.runtime,\n });\n }\n\n return groupSpec;\n });\n\n/**\n * Walk the built `Context` for a `Finalized` `GroupImpl` service value. The\n * lookup is value-shaped (via `GroupImpl.isFinalizedGroupImpl`) so we don't\n * need to know the group's path up front to construct a typed tag for it.\n */\nconst findFinalizedGroupImpl = <S>(\n context: Context.Context<S>,\n): Option.Option<GroupImpl.AnyFinalized> =>\n Array.findFirst(context.unsafeMap.values(), GroupImpl.isFinalizedGroupImpl);\n\n/**\n * Build the impl layer with a fresh `Registry` so each validation is\n * isolated from prior validations' `FunctionImpl.make` writes. The CLI no\n * longer reads the registry directly — `GroupImpl.finalize` snapshots the\n * registered function names onto the produced `Finalized` `GroupImpl`\n * service value — but a fresh `Ref` is still required because the default\n * `Context.Reference` is cached globally and would otherwise accumulate\n * items across impls.\n */\nconst buildImplLayer = (implLayer: Layer.Layer<unknown>) =>\n Effect.gen(function* () {\n const registry = Ref.unsafeMake<Registry.RegistryItems>({});\n return yield* Layer.build(\n implLayer as Layer.Layer<unknown, never, never>,\n ).pipe(Effect.provideService(Registry.Registry, registry));\n }).pipe(Effect.scoped);\n\n/**\n * Validate that the leaf's sibling impl file imports the spec, default-exports\n * a finalized `GroupImpl` layer, and provides a `FunctionImpl` for every\n * function declared by the spec.\n */\nexport const validateImpl = (leaf: LeafModule) =>\n Effect.gen(function* () {\n const implRelativePath = yield* implPathForSpec(leaf.relativePath);\n const implAbsolutePath = yield* absoluteModulePath(implRelativePath);\n const specAbsolutePath = yield* absoluteModulePath(leaf.relativePath);\n\n const bundled = yield* Bundler.bundle(implAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(implRelativePath, error)),\n );\n\n if (\n !(yield* Bundler.directlyImports(\n bundled,\n implAbsolutePath,\n specAbsolutePath,\n ))\n ) {\n return yield* new ImplMissingSpecImportError({\n implPath: implRelativePath,\n expectedSpecPath: leaf.relativePath,\n });\n }\n\n if (!Layer.isLayer(bundled.module.default)) {\n return yield* new ImplMissingDefaultLayerError({\n implPath: implRelativePath,\n });\n }\n\n const { module: specModule } = yield* Bundler.bundle(specAbsolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(leaf.relativePath, error)),\n );\n const groupSpec = specModule.default as GroupSpec.AnyWithProps;\n const expectedFunctionNames = Object.keys(groupSpec.functions);\n\n const context = yield* buildImplLayer(\n bundled.module.default as Layer.Layer<unknown>,\n );\n const finalizedGroupImpl = yield* Option.match(\n findFinalizedGroupImpl(context),\n {\n onNone: () => new ImplNotFinalizedError({ implPath: implRelativePath }),\n onSome: Effect.succeed,\n },\n );\n\n const registeredSet = new Set(finalizedGroupImpl.registeredFunctionNames);\n const missing = expectedFunctionNames.filter(\n (name) => !registeredSet.has(name),\n );\n\n if (missing.length > 0) {\n return yield* new ImplMissingFunctionsError({\n implPath: implRelativePath,\n groupPath: leaf.groupPathDot,\n missingFunctionNames: missing,\n });\n }\n });\n"],"mappings":"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"}
|
|
@@ -28,25 +28,7 @@ const importBindingsForNode = (node) => pipe(node.children, Array.flatMap(import
|
|
|
28
28
|
onSome: (binding) => Array.prepend(childBindings, binding)
|
|
29
29
|
}));
|
|
30
30
|
const collectImportBindings = (nodes) => pipe(Array.flatMap(nodes, importBindingsForNode), (bindings) => Record.fromIterableBy(bindings, (binding) => binding.importPath), Record.toEntries, Array.map(([, binding]) => binding), Array.sortBy(Order.mapInput(Order.string, (binding) => binding.localName)));
|
|
31
|
-
const leafPathsForNode = (node, ancestorSegments) => {
|
|
32
|
-
const segments = [...ancestorSegments, node.segment];
|
|
33
|
-
const childPaths = Array.flatMap(node.children, (child) => leafPathsForNode(child, segments));
|
|
34
|
-
return Option.match(node.importBinding, {
|
|
35
|
-
onNone: () => childPaths,
|
|
36
|
-
onSome: (binding) => Array.prepend(childPaths, {
|
|
37
|
-
binding,
|
|
38
|
-
dotPath: segments.join(".")
|
|
39
|
-
})
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Walk the assembly tree and produce one entry per leaf spec, pairing its
|
|
44
|
-
* import binding with the full dot-path codegen will register via
|
|
45
|
-
* `Spec.addPath`. Ordering matches `collectImportBindings` (sorted by the
|
|
46
|
-
* binding's local name) so the generated file is stable across runs.
|
|
47
|
-
*/
|
|
48
|
-
const collectLeafPaths = (nodes) => pipe(Array.flatMap(nodes, (node) => leafPathsForNode(node, [])), Array.sortBy(Order.mapInput(Order.string, (entry) => entry.binding.localName)));
|
|
49
31
|
|
|
50
32
|
//#endregion
|
|
51
|
-
export { assemblyNodesFromLeaves, collectImportBindings,
|
|
33
|
+
export { assemblyNodesFromLeaves, collectImportBindings, partitionByRuntime };
|
|
52
34
|
//# sourceMappingURL=SpecAssemblyNode.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpecAssemblyNode.mjs","names":[],"sources":["../src/SpecAssemblyNode.ts"],"sourcesContent":["import type { LeafModule } from \"./LeafModule\";\nimport { Array, Option, Order, pipe, Record } from \"effect\";\n\nexport interface SpecImportBinding {\n readonly importPath: string;\n readonly exportName: string;\n readonly localName: string;\n}\n\nexport interface SpecAssemblyNode {\n readonly segment: string;\n readonly importBinding: Option.Option<SpecImportBinding>;\n readonly children: ReadonlyArray<SpecAssemblyNode>;\n}\n\nconst importBindingFromLeaf = (leaf: LeafModule): SpecImportBinding => ({\n importPath: leaf.specImportPath,\n exportName: leaf.exportName,\n localName: leaf.pathSegments.join(\"_\"),\n});\n\nconst assemblyNodesAtDepth = (\n leaves: ReadonlyArray<LeafModule>,\n depth: number,\n): ReadonlyArray<SpecAssemblyNode> =>\n pipe(\n Array.groupBy(leaves, (leaf) => leaf.pathSegments[depth]!),\n Record.toEntries,\n Array.sortBy(Order.mapInput(Order.string, ([segment]) => segment)),\n Array.map(([segment, groupLeaves]) => {\n const terminal = Array.findFirst(\n groupLeaves,\n (leaf) => leaf.pathSegments.length === depth + 1,\n );\n const descendants = Array.filter(\n groupLeaves,\n (leaf) => leaf.pathSegments.length > depth + 1,\n );\n return {\n segment,\n importBinding: Option.map(terminal, importBindingFromLeaf),\n children: assemblyNodesAtDepth(descendants, depth + 1),\n };\n }),\n );\n\nexport const assemblyNodesFromLeaves = (\n leaves: ReadonlyArray<LeafModule>,\n): ReadonlyArray<SpecAssemblyNode> => assemblyNodesAtDepth(leaves, 0);\n\nexport const partitionByRuntime = (\n leaves: ReadonlyArray<LeafModule>,\n): {\n readonly convex: ReadonlyArray<LeafModule>;\n readonly node: ReadonlyArray<LeafModule>;\n} => {\n const [node, convex] = Array.partition(\n leaves,\n (leaf) => leaf.runtime === \"Convex\",\n );\n return { convex, node };\n};\n\nconst importBindingsForNode = (\n node: SpecAssemblyNode,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(node.children, Array.flatMap(importBindingsForNode), (childBindings) =>\n Option.match(node.importBinding, {\n onNone: () => childBindings,\n onSome: (binding) => Array.prepend(childBindings, binding),\n }),\n );\n\nexport const collectImportBindings = (\n nodes: ReadonlyArray<SpecAssemblyNode>,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(\n Array.flatMap(nodes, importBindingsForNode),\n (bindings) =>\n Record.fromIterableBy(bindings, (binding) => binding.importPath),\n Record.toEntries,\n Array.map(([, binding]) => binding),\n Array.sortBy(Order.mapInput(Order.string, (binding) => binding.localName)),\n );\n
|
|
1
|
+
{"version":3,"file":"SpecAssemblyNode.mjs","names":[],"sources":["../src/SpecAssemblyNode.ts"],"sourcesContent":["import type { LeafModule } from \"./LeafModule\";\nimport { Array, Option, Order, pipe, Record } from \"effect\";\n\nexport interface SpecImportBinding {\n readonly importPath: string;\n readonly exportName: string;\n readonly localName: string;\n}\n\nexport interface SpecAssemblyNode {\n readonly segment: string;\n readonly importBinding: Option.Option<SpecImportBinding>;\n readonly children: ReadonlyArray<SpecAssemblyNode>;\n}\n\nconst importBindingFromLeaf = (leaf: LeafModule): SpecImportBinding => ({\n importPath: leaf.specImportPath,\n exportName: leaf.exportName,\n localName: leaf.pathSegments.join(\"_\"),\n});\n\nconst assemblyNodesAtDepth = (\n leaves: ReadonlyArray<LeafModule>,\n depth: number,\n): ReadonlyArray<SpecAssemblyNode> =>\n pipe(\n Array.groupBy(leaves, (leaf) => leaf.pathSegments[depth]!),\n Record.toEntries,\n Array.sortBy(Order.mapInput(Order.string, ([segment]) => segment)),\n Array.map(([segment, groupLeaves]) => {\n const terminal = Array.findFirst(\n groupLeaves,\n (leaf) => leaf.pathSegments.length === depth + 1,\n );\n const descendants = Array.filter(\n groupLeaves,\n (leaf) => leaf.pathSegments.length > depth + 1,\n );\n return {\n segment,\n importBinding: Option.map(terminal, importBindingFromLeaf),\n children: assemblyNodesAtDepth(descendants, depth + 1),\n };\n }),\n );\n\nexport const assemblyNodesFromLeaves = (\n leaves: ReadonlyArray<LeafModule>,\n): ReadonlyArray<SpecAssemblyNode> => assemblyNodesAtDepth(leaves, 0);\n\nexport const partitionByRuntime = (\n leaves: ReadonlyArray<LeafModule>,\n): {\n readonly convex: ReadonlyArray<LeafModule>;\n readonly node: ReadonlyArray<LeafModule>;\n} => {\n const [node, convex] = Array.partition(\n leaves,\n (leaf) => leaf.runtime === \"Convex\",\n );\n return { convex, node };\n};\n\nconst importBindingsForNode = (\n node: SpecAssemblyNode,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(node.children, Array.flatMap(importBindingsForNode), (childBindings) =>\n Option.match(node.importBinding, {\n onNone: () => childBindings,\n onSome: (binding) => Array.prepend(childBindings, binding),\n }),\n );\n\nexport const collectImportBindings = (\n nodes: ReadonlyArray<SpecAssemblyNode>,\n): ReadonlyArray<SpecImportBinding> =>\n pipe(\n Array.flatMap(nodes, importBindingsForNode),\n (bindings) =>\n Record.fromIterableBy(bindings, (binding) => binding.importPath),\n Record.toEntries,\n Array.map(([, binding]) => binding),\n Array.sortBy(Order.mapInput(Order.string, (binding) => binding.localName)),\n );\n"],"mappings":";;;AAeA,MAAM,yBAAyB,UAAyC;CACtE,YAAY,KAAK;CACjB,YAAY,KAAK;CACjB,WAAW,KAAK,aAAa,KAAK,IAAI;CACvC;AAED,MAAM,wBACJ,QACA,UAEA,KACE,MAAM,QAAQ,SAAS,SAAS,KAAK,aAAa,OAAQ,EAC1D,OAAO,WACP,MAAM,OAAO,MAAM,SAAS,MAAM,SAAS,CAAC,aAAa,QAAQ,CAAC,EAClE,MAAM,KAAK,CAAC,SAAS,iBAAiB;CACpC,MAAM,WAAW,MAAM,UACrB,cACC,SAAS,KAAK,aAAa,WAAW,QAAQ,EAChD;CACD,MAAM,cAAc,MAAM,OACxB,cACC,SAAS,KAAK,aAAa,SAAS,QAAQ,EAC9C;AACD,QAAO;EACL;EACA,eAAe,OAAO,IAAI,UAAU,sBAAsB;EAC1D,UAAU,qBAAqB,aAAa,QAAQ,EAAE;EACvD;EACD,CACH;AAEH,MAAa,2BACX,WACoC,qBAAqB,QAAQ,EAAE;AAErE,MAAa,sBACX,WAIG;CACH,MAAM,CAAC,MAAM,UAAU,MAAM,UAC3B,SACC,SAAS,KAAK,YAAY,SAC5B;AACD,QAAO;EAAE;EAAQ;EAAM;;AAGzB,MAAM,yBACJ,SAEA,KAAK,KAAK,UAAU,MAAM,QAAQ,sBAAsB,GAAG,kBACzD,OAAO,MAAM,KAAK,eAAe;CAC/B,cAAc;CACd,SAAS,YAAY,MAAM,QAAQ,eAAe,QAAQ;CAC3D,CAAC,CACH;AAEH,MAAa,yBACX,UAEA,KACE,MAAM,QAAQ,OAAO,sBAAsB,GAC1C,aACC,OAAO,eAAe,WAAW,YAAY,QAAQ,WAAW,EAClE,OAAO,WACP,MAAM,KAAK,GAAG,aAAa,QAAQ,EACnC,MAAM,OAAO,MAAM,SAAS,MAAM,SAAS,YAAY,QAAQ,UAAU,CAAC,CAC3E"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { fromBundlerError } from "./BuildError.mjs";
|
|
2
|
+
import { bundle } from "./Bundler.mjs";
|
|
3
|
+
import { DuplicateTableNameError, InvalidTableDefaultExportError, InvalidTableFilenameError } from "./CodegenError.mjs";
|
|
4
|
+
import { ConfectDirectory } from "./ConfectDirectory.mjs";
|
|
5
|
+
import { Array, Effect, Order, pipe } from "effect";
|
|
6
|
+
import { Identifier } from "@confect/core";
|
|
7
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
8
|
+
import * as Table from "@confect/server/Table";
|
|
9
|
+
|
|
10
|
+
//#region src/TableModule.ts
|
|
11
|
+
const TABLES_DIRNAME = "tables";
|
|
12
|
+
const tableNameFromRelativePath = (relativePath) => Effect.gen(function* () {
|
|
13
|
+
const { name } = (yield* Path.Path).parse(relativePath);
|
|
14
|
+
return name;
|
|
15
|
+
});
|
|
16
|
+
const listTableFiles = Effect.gen(function* () {
|
|
17
|
+
const fs = yield* FileSystem.FileSystem;
|
|
18
|
+
const path = yield* Path.Path;
|
|
19
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
20
|
+
const tablesDirectory = path.join(confectDirectory, TABLES_DIRNAME);
|
|
21
|
+
if (!(yield* fs.exists(tablesDirectory))) return [];
|
|
22
|
+
return pipe(yield* fs.readDirectory(tablesDirectory, { recursive: true }), Array.filter((p) => p.endsWith(".ts") && !p.endsWith(".test.ts")), Array.map((p) => path.join(TABLES_DIRNAME, p)));
|
|
23
|
+
});
|
|
24
|
+
const byTableName = Order.mapInput(Order.string, (tableModule) => tableModule.tableName);
|
|
25
|
+
/**
|
|
26
|
+
* Discover every `confect/tables/**\/*.ts` module by listing the directory.
|
|
27
|
+
* Validates that each filename is a legal table identifier — the table name is
|
|
28
|
+
* derived from the file basename, so the filename must be a valid JavaScript
|
|
29
|
+
* identifier with no leading underscore (Convex reserves `_<name>` for system
|
|
30
|
+
* tables).
|
|
31
|
+
*
|
|
32
|
+
* This step does *not* bundle the table modules. It runs early in the codegen
|
|
33
|
+
* pipeline so the `_generated/id.ts` constructor can be emitted *before* any
|
|
34
|
+
* user-authored table is bundled (those modules import from `_generated/id.ts`
|
|
35
|
+
* for cross-table refs).
|
|
36
|
+
*
|
|
37
|
+
* A missing `confect/tables/` directory is allowed and produces an empty list.
|
|
38
|
+
*
|
|
39
|
+
* Fails with {@link InvalidTableFilenameError} if any filename is not a valid
|
|
40
|
+
* table identifier, or {@link DuplicateTableNameError} if two files resolve to
|
|
41
|
+
* the same table name (the directory is scanned recursively but names are
|
|
42
|
+
* derived from the basename alone, so `tables/a/notes.ts` and
|
|
43
|
+
* `tables/b/notes.ts` would collide).
|
|
44
|
+
*/
|
|
45
|
+
const discover = Effect.gen(function* () {
|
|
46
|
+
const relativePaths = yield* listTableFiles;
|
|
47
|
+
const sorted = pipe(yield* Effect.forEach(relativePaths, (relativePath) => Effect.gen(function* () {
|
|
48
|
+
const tableName = yield* tableNameFromRelativePath(relativePath);
|
|
49
|
+
yield* Effect.try({
|
|
50
|
+
try: () => Identifier.validateConfectTableIdentifier(tableName),
|
|
51
|
+
catch: (e) => new InvalidTableFilenameError({
|
|
52
|
+
tablePath: relativePath,
|
|
53
|
+
reason: e instanceof Error ? e.message : String(e)
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
relativePath,
|
|
58
|
+
tableName
|
|
59
|
+
};
|
|
60
|
+
}), { concurrency: "unbounded" }), Array.sortBy(byTableName));
|
|
61
|
+
const collisions = Object.entries(Array.groupBy(sorted, (tableModule) => tableModule.tableName)).filter(([, group]) => group.length > 1).map(([tableName, group]) => ({
|
|
62
|
+
tableName,
|
|
63
|
+
tablePaths: Array.map(group, (tableModule) => tableModule.relativePath)
|
|
64
|
+
}));
|
|
65
|
+
if (collisions.length > 0) return yield* new DuplicateTableNameError({ collisions });
|
|
66
|
+
return sorted;
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* Bundle every discovered table module and verify that its default export is
|
|
70
|
+
* an {@link Table.UnnamedTable} (the result of `Table.make(...)` before a
|
|
71
|
+
* name has been bound). Fails with {@link InvalidTableDefaultExportError} if
|
|
72
|
+
* any module's default export is missing or has the wrong shape.
|
|
73
|
+
*
|
|
74
|
+
* Must run *after* `_generated/id.ts` has been emitted, because user-authored
|
|
75
|
+
* table modules typically `import { Id } from "../_generated/id"` for
|
|
76
|
+
* cross-table references.
|
|
77
|
+
*/
|
|
78
|
+
const validate = (tableModules) => Effect.gen(function* () {
|
|
79
|
+
const path = yield* Path.Path;
|
|
80
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
81
|
+
yield* Effect.forEach(tableModules, ({ relativePath }) => Effect.gen(function* () {
|
|
82
|
+
const absolutePath = path.resolve(confectDirectory, relativePath);
|
|
83
|
+
const { module } = yield* bundle(absolutePath).pipe(Effect.mapError((error) => fromBundlerError(relativePath, error)));
|
|
84
|
+
if (!Table.isUnnamedTable(module.default)) return yield* new InvalidTableDefaultExportError({ tablePath: relativePath });
|
|
85
|
+
}), { concurrency: "unbounded" });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
export { TABLES_DIRNAME, discover, validate };
|
|
90
|
+
//# sourceMappingURL=TableModule.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableModule.mjs","names":["Bundler.bundle"],"sources":["../src/TableModule.ts"],"sourcesContent":["import { Identifier } from \"@confect/core\";\nimport * as Table from \"@confect/server/Table\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport { Array, Effect, Order, pipe } from \"effect\";\nimport { fromBundlerError } from \"./BuildError\";\nimport * as Bundler from \"./Bundler\";\nimport {\n DuplicateTableNameError,\n InvalidTableDefaultExportError,\n InvalidTableFilenameError,\n} from \"./CodegenError\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\n\nexport const TABLES_DIRNAME = \"tables\";\n\n/**\n * Discovered metadata for a single user-authored table module under\n * `confect/tables/`.\n *\n * - `relativePath` — path from `confect/` to the file (e.g. `tables/notes.ts`).\n * - `tableName` — the file basename (e.g. `notes`). This is also the import\n * binding used in generated files, and the table name surfaced to Convex.\n */\nexport interface TableModule {\n readonly relativePath: string;\n readonly tableName: string;\n}\n\nconst tableNameFromRelativePath = (relativePath: string) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const { name } = path.parse(relativePath);\n return name;\n });\n\nconst listTableFiles = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n const tablesDirectory = path.join(confectDirectory, TABLES_DIRNAME);\n\n if (!(yield* fs.exists(tablesDirectory))) {\n return [] as ReadonlyArray<string>;\n }\n\n const allPaths = yield* fs.readDirectory(tablesDirectory, {\n recursive: true,\n });\n\n return pipe(\n allPaths,\n Array.filter((p) => p.endsWith(\".ts\") && !p.endsWith(\".test.ts\")),\n Array.map((p) => path.join(TABLES_DIRNAME, p)),\n );\n});\n\nconst byTableName = Order.mapInput(\n Order.string,\n (tableModule: TableModule) => tableModule.tableName,\n);\n\n/**\n * Discover every `confect/tables/**\\/*.ts` module by listing the directory.\n * Validates that each filename is a legal table identifier — the table name is\n * derived from the file basename, so the filename must be a valid JavaScript\n * identifier with no leading underscore (Convex reserves `_<name>` for system\n * tables).\n *\n * This step does *not* bundle the table modules. It runs early in the codegen\n * pipeline so the `_generated/id.ts` constructor can be emitted *before* any\n * user-authored table is bundled (those modules import from `_generated/id.ts`\n * for cross-table refs).\n *\n * A missing `confect/tables/` directory is allowed and produces an empty list.\n *\n * Fails with {@link InvalidTableFilenameError} if any filename is not a valid\n * table identifier, or {@link DuplicateTableNameError} if two files resolve to\n * the same table name (the directory is scanned recursively but names are\n * derived from the basename alone, so `tables/a/notes.ts` and\n * `tables/b/notes.ts` would collide).\n */\nexport const discover = Effect.gen(function* () {\n const relativePaths = yield* listTableFiles;\n\n const tableModules = yield* Effect.forEach(\n relativePaths,\n (relativePath) =>\n Effect.gen(function* () {\n const tableName = yield* tableNameFromRelativePath(relativePath);\n yield* Effect.try({\n try: () => Identifier.validateConfectTableIdentifier(tableName),\n catch: (e) =>\n new InvalidTableFilenameError({\n tablePath: relativePath,\n reason: e instanceof Error ? e.message : String(e),\n }),\n });\n return { relativePath, tableName } satisfies TableModule;\n }),\n { concurrency: \"unbounded\" },\n );\n\n const sorted = pipe(tableModules, Array.sortBy(byTableName));\n\n const collisions = Object.entries(\n Array.groupBy(sorted, (tableModule) => tableModule.tableName),\n )\n .filter(([, group]) => group.length > 1)\n .map(([tableName, group]) => ({\n tableName,\n tablePaths: Array.map(group, (tableModule) => tableModule.relativePath),\n }));\n\n if (collisions.length > 0) {\n return yield* new DuplicateTableNameError({ collisions });\n }\n\n return sorted;\n});\n\n/**\n * Bundle every discovered table module and verify that its default export is\n * an {@link Table.UnnamedTable} (the result of `Table.make(...)` before a\n * name has been bound). Fails with {@link InvalidTableDefaultExportError} if\n * any module's default export is missing or has the wrong shape.\n *\n * Must run *after* `_generated/id.ts` has been emitted, because user-authored\n * table modules typically `import { Id } from \"../_generated/id\"` for\n * cross-table references.\n */\nexport const validate = (tableModules: ReadonlyArray<TableModule>) =>\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n yield* Effect.forEach(\n tableModules,\n ({ relativePath }) =>\n Effect.gen(function* () {\n const absolutePath = path.resolve(confectDirectory, relativePath);\n const { module } = yield* Bundler.bundle(absolutePath).pipe(\n Effect.mapError((error) => fromBundlerError(relativePath, error)),\n );\n\n if (!Table.isUnnamedTable(module.default)) {\n return yield* new InvalidTableDefaultExportError({\n tablePath: relativePath,\n });\n }\n }),\n { concurrency: \"unbounded\" },\n );\n });\n"],"mappings":";;;;;;;;;;AAaA,MAAa,iBAAiB;AAe9B,MAAM,6BAA6B,iBACjC,OAAO,IAAI,aAAa;CAEtB,MAAM,EAAE,UADK,OAAO,KAAK,MACH,MAAM,aAAa;AACzC,QAAO;EACP;AAEJ,MAAM,iBAAiB,OAAO,IAAI,aAAa;CAC7C,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,mBAAmB,OAAO,iBAAiB;CACjD,MAAM,kBAAkB,KAAK,KAAK,kBAAkB,eAAe;AAEnE,KAAI,EAAE,OAAO,GAAG,OAAO,gBAAgB,EACrC,QAAO,EAAE;AAOX,QAAO,KAJU,OAAO,GAAG,cAAc,iBAAiB,EACxD,WAAW,MACZ,CAAC,EAIA,MAAM,QAAQ,MAAM,EAAE,SAAS,MAAM,IAAI,CAAC,EAAE,SAAS,WAAW,CAAC,EACjE,MAAM,KAAK,MAAM,KAAK,KAAK,gBAAgB,EAAE,CAAC,CAC/C;EACD;AAEF,MAAM,cAAc,MAAM,SACxB,MAAM,SACL,gBAA6B,YAAY,UAC3C;;;;;;;;;;;;;;;;;;;;;AAsBD,MAAa,WAAW,OAAO,IAAI,aAAa;CAC9C,MAAM,gBAAgB,OAAO;CAoB7B,MAAM,SAAS,KAlBM,OAAO,OAAO,QACjC,gBACC,iBACC,OAAO,IAAI,aAAa;EACtB,MAAM,YAAY,OAAO,0BAA0B,aAAa;AAChE,SAAO,OAAO,IAAI;GAChB,WAAW,WAAW,+BAA+B,UAAU;GAC/D,QAAQ,MACN,IAAI,0BAA0B;IAC5B,WAAW;IACX,QAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACnD,CAAC;GACL,CAAC;AACF,SAAO;GAAE;GAAc;GAAW;GAClC,EACJ,EAAE,aAAa,aAAa,CAC7B,EAEiC,MAAM,OAAO,YAAY,CAAC;CAE5D,MAAM,aAAa,OAAO,QACxB,MAAM,QAAQ,SAAS,gBAAgB,YAAY,UAAU,CAC9D,CACE,QAAQ,GAAG,WAAW,MAAM,SAAS,EAAE,CACvC,KAAK,CAAC,WAAW,YAAY;EAC5B;EACA,YAAY,MAAM,IAAI,QAAQ,gBAAgB,YAAY,aAAa;EACxE,EAAE;AAEL,KAAI,WAAW,SAAS,EACtB,QAAO,OAAO,IAAI,wBAAwB,EAAE,YAAY,CAAC;AAG3D,QAAO;EACP;;;;;;;;;;;AAYF,MAAa,YAAY,iBACvB,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,mBAAmB,OAAO,iBAAiB;AAEjD,QAAO,OAAO,QACZ,eACC,EAAE,mBACD,OAAO,IAAI,aAAa;EACtB,MAAM,eAAe,KAAK,QAAQ,kBAAkB,aAAa;EACjE,MAAM,EAAE,WAAW,OAAOA,OAAe,aAAa,CAAC,KACrD,OAAO,UAAU,UAAU,iBAAiB,cAAc,MAAM,CAAC,CAClE;AAED,MAAI,CAAC,MAAM,eAAe,OAAO,QAAQ,CACvC,QAAO,OAAO,IAAI,+BAA+B,EAC/C,WAAW,cACZ,CAAC;GAEJ,EACJ,EAAE,aAAa,aAAa,CAC7B;EACD"}
|
package/dist/confect/codegen.mjs
CHANGED
|
@@ -1,35 +1,40 @@
|
|
|
1
|
-
import { logFileAdded, logFileModified, logFileRemoved, logPending, logSuccess } from "../log.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { logFileAdded, logFileModified, logFileRemoved, logPending, logSuccess, logWarn } from "../log.mjs";
|
|
2
|
+
import { bundle } from "../Bundler.mjs";
|
|
3
|
+
import { LegacySchemaFileError, MissingImplFileError, MissingSpecFileError, ParentChildNameCollisionError, tapAndLog } from "../CodegenError.mjs";
|
|
4
4
|
import { ConvexDirectory } from "../ConvexDirectory.mjs";
|
|
5
5
|
import { ConfectDirectory } from "../ConfectDirectory.mjs";
|
|
6
6
|
import { FunctionPaths, make } from "../FunctionPaths.mjs";
|
|
7
|
-
import { bundle } from "../Bundler.mjs";
|
|
8
7
|
import { assemblyNodesFromLeaves, partitionByRuntime } from "../SpecAssemblyNode.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import { WriteTracker, generateAuthConfig, generateCrons, generateFunctions, generateHttp,
|
|
8
|
+
import { assembledSpec, convexSchema, id, refs, registeredFunctionsForGroup, runtimeSchema, schema, services, tableWrapper } from "../templates.mjs";
|
|
9
|
+
import { WriteTracker, generateAuthConfig, generateCrons, generateFunctions, generateHttp, removePathIfExists, toModuleImportPath, touchConvexSchema, writeFileStringAndLog } from "../utils.mjs";
|
|
11
10
|
import { discoverLeafImplFiles, discoverLeafSpecFiles, implPathForSpec, registeredFunctionsRelativePath, specPathForImpl, toLeafModule, toNodeRegistryLeaf, validateImpl, validateSpec } from "../LeafModule.mjs";
|
|
11
|
+
import { TABLES_DIRNAME, discover, validate } from "../TableModule.mjs";
|
|
12
12
|
import { Array, Effect, Either, HashSet, Match, Option, Ref } from "effect";
|
|
13
13
|
import { Command } from "@effect/cli";
|
|
14
14
|
import { Spec } from "@confect/core";
|
|
15
|
-
import * as DatabaseSchema from "@confect/server/DatabaseSchema";
|
|
16
15
|
import { FileSystem, Path } from "@effect/platform";
|
|
17
16
|
|
|
18
17
|
//#region src/confect/codegen.ts
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
const GENERATED_DIRNAME = "_generated";
|
|
19
|
+
const GENERATED_SPEC_PATH = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "spec.ts"));
|
|
20
|
+
const GENERATED_NODE_SPEC_PATH = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "nodeSpec.ts"));
|
|
21
|
+
const GENERATED_SCHEMA_PATH = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "schema.ts"));
|
|
22
|
+
const GENERATED_CONVEX_SCHEMA_PATH = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "convexSchema.ts"));
|
|
23
|
+
const GENERATED_ID_PATH = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "id.ts"));
|
|
24
|
+
const GENERATED_TABLES_DIRNAME = Effect.andThen(Path.Path, (path) => path.join(GENERATED_DIRNAME, "tables"));
|
|
25
|
+
const LEGACY_PATHS = Effect.gen(function* () {
|
|
26
|
+
const path = yield* Path.Path;
|
|
27
|
+
return [
|
|
28
|
+
"spec.ts",
|
|
29
|
+
"nodeSpec.ts",
|
|
30
|
+
"impl.ts",
|
|
31
|
+
"nodeImpl.ts",
|
|
32
|
+
path.join(GENERATED_DIRNAME, "registeredFunctions.ts"),
|
|
33
|
+
path.join(GENERATED_DIRNAME, "nodeRegisteredFunctions.ts"),
|
|
34
|
+
path.join(GENERATED_DIRNAME, "impl.ts"),
|
|
35
|
+
path.join(GENERATED_DIRNAME, "nodeImpl.ts")
|
|
36
|
+
];
|
|
37
|
+
});
|
|
33
38
|
const codegen = Command.make("codegen", {}, () => Effect.gen(function* () {
|
|
34
39
|
yield* logPending("Performing initial sync…");
|
|
35
40
|
yield* codegenHandler.pipe(Effect.asVoid, Effect.tap(() => logSuccess("Generated files are up-to-date")), tapAndLog);
|
|
@@ -43,23 +48,31 @@ const codegenHandler = Effect.gen(function* () {
|
|
|
43
48
|
});
|
|
44
49
|
const runCodegen = Effect.gen(function* () {
|
|
45
50
|
yield* generateConfectGeneratedDirectory;
|
|
46
|
-
yield*
|
|
51
|
+
yield* rejectLegacySchemaFile;
|
|
52
|
+
const tableModules = yield* discover;
|
|
53
|
+
yield* warnIfNoTables(tableModules);
|
|
54
|
+
yield* generateIdConstructor(tableModules);
|
|
55
|
+
yield* validate(tableModules);
|
|
56
|
+
yield* generateTableWrappers(tableModules);
|
|
57
|
+
yield* removeObsoleteTableWrappers(tableModules);
|
|
58
|
+
yield* generateRuntimeSchema(tableModules);
|
|
47
59
|
const { leaves, groupSpecsByRelativePath } = yield* loadAndValidateLeafModules;
|
|
48
60
|
yield* removeLegacyFiles;
|
|
49
61
|
yield* validateNoParentChildNameCollisions(leaves, groupSpecsByRelativePath);
|
|
50
62
|
yield* generateAssembledSpecs(leaves);
|
|
51
|
-
yield* validateImplModules(leaves);
|
|
52
|
-
yield* generateGroupRegisteredFunctions(leaves);
|
|
53
|
-
yield* removeObsoleteRegisteredFunctions(leaves);
|
|
54
63
|
yield* Effect.all([
|
|
55
|
-
|
|
64
|
+
removeGeneratedApi,
|
|
56
65
|
generateRefs,
|
|
57
|
-
|
|
58
|
-
generateServices
|
|
66
|
+
removeGeneratedNodeApi,
|
|
67
|
+
generateServices,
|
|
68
|
+
generateConvexSchema(tableModules)
|
|
59
69
|
], { concurrency: "unbounded" });
|
|
70
|
+
yield* validateImplModules(leaves);
|
|
71
|
+
yield* generateGroupRegisteredFunctions(leaves);
|
|
72
|
+
yield* removeObsoleteRegisteredFunctions(leaves);
|
|
60
73
|
const [functionPaths] = yield* Effect.all([
|
|
61
74
|
generateFunctionModules,
|
|
62
|
-
|
|
75
|
+
generateConvexSchemaReexport,
|
|
63
76
|
logGenerated(generateHttp),
|
|
64
77
|
logGenerated(generateCrons),
|
|
65
78
|
logGenerated(generateAuthConfig)
|
|
@@ -184,7 +197,8 @@ const removeLegacyFiles = Effect.gen(function* () {
|
|
|
184
197
|
const fs = yield* FileSystem.FileSystem;
|
|
185
198
|
const path = yield* Path.Path;
|
|
186
199
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
187
|
-
|
|
200
|
+
const legacyPaths = yield* LEGACY_PATHS;
|
|
201
|
+
yield* Effect.forEach(legacyPaths, (relativePath) => Effect.gen(function* () {
|
|
188
202
|
const absolutePath = path.join(confectDirectory, relativePath);
|
|
189
203
|
if (yield* fs.exists(absolutePath)) {
|
|
190
204
|
yield* removePathIfExists(absolutePath);
|
|
@@ -195,6 +209,8 @@ const removeLegacyFiles = Effect.gen(function* () {
|
|
|
195
209
|
const generateAssembledSpecs = (leaves) => Effect.gen(function* () {
|
|
196
210
|
const path = yield* Path.Path;
|
|
197
211
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
212
|
+
const generatedSpecPath = yield* GENERATED_SPEC_PATH;
|
|
213
|
+
const generatedNodeSpecPath = yield* GENERATED_NODE_SPEC_PATH;
|
|
198
214
|
const { convex, node } = partitionByRuntime(leaves);
|
|
199
215
|
if (convex.length > 0) {
|
|
200
216
|
const nodes = assemblyNodesFromLeaves(convex);
|
|
@@ -202,7 +218,7 @@ const generateAssembledSpecs = (leaves) => Effect.gen(function* () {
|
|
|
202
218
|
nodes,
|
|
203
219
|
runtime: "Convex"
|
|
204
220
|
});
|
|
205
|
-
yield* writeFileStringAndLog(path.join(confectDirectory,
|
|
221
|
+
yield* writeFileStringAndLog(path.join(confectDirectory, generatedSpecPath), specContents);
|
|
206
222
|
}
|
|
207
223
|
if (node.length > 0) {
|
|
208
224
|
const nodes = assemblyNodesFromLeaves(Array.map(node, toNodeRegistryLeaf));
|
|
@@ -210,7 +226,7 @@ const generateAssembledSpecs = (leaves) => Effect.gen(function* () {
|
|
|
210
226
|
nodes,
|
|
211
227
|
runtime: "Node"
|
|
212
228
|
});
|
|
213
|
-
yield* writeFileStringAndLog(path.join(confectDirectory,
|
|
229
|
+
yield* writeFileStringAndLog(path.join(confectDirectory, generatedNodeSpecPath), nodeSpecContents);
|
|
214
230
|
}
|
|
215
231
|
});
|
|
216
232
|
const validateImplModules = (leaves) => Effect.forEach(leaves, validateImpl);
|
|
@@ -224,12 +240,12 @@ const generateGroupRegisteredFunctions = (leaves) => Effect.gen(function* () {
|
|
|
224
240
|
const fs = yield* FileSystem.FileSystem;
|
|
225
241
|
if (!(yield* fs.exists(registryDir))) yield* fs.makeDirectory(registryDir, { recursive: true });
|
|
226
242
|
const implRelativePath = yield* implPathForSpec(leaf.relativePath);
|
|
227
|
-
const
|
|
228
|
-
const
|
|
243
|
+
const schemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, "_generated", "schema.ts")));
|
|
244
|
+
const specImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, leaf.relativePath)));
|
|
229
245
|
const implImportPath = yield* toModuleImportPath(path.relative(path.dirname(registryPath), path.join(confectDirectory, implRelativePath)));
|
|
230
246
|
yield* writeFileStringAndLog(registryPath, yield* registeredFunctionsForGroup({
|
|
231
|
-
|
|
232
|
-
|
|
247
|
+
schemaImportPath,
|
|
248
|
+
specImportPath,
|
|
233
249
|
implImportPath,
|
|
234
250
|
layerExportName: leaf.exportName,
|
|
235
251
|
useNode: leaf.runtime === "Node"
|
|
@@ -260,12 +276,14 @@ const removeObsoleteRegisteredFunctions = (leaves) => Effect.gen(function* () {
|
|
|
260
276
|
const getGeneratedSpecPath = Effect.gen(function* () {
|
|
261
277
|
const path = yield* Path.Path;
|
|
262
278
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
263
|
-
|
|
279
|
+
const generatedSpecPath = yield* GENERATED_SPEC_PATH;
|
|
280
|
+
return path.join(confectDirectory, generatedSpecPath);
|
|
264
281
|
});
|
|
265
282
|
const getGeneratedNodeSpecPath = Effect.gen(function* () {
|
|
266
283
|
const path = yield* Path.Path;
|
|
267
284
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
268
|
-
|
|
285
|
+
const generatedNodeSpecPath = yield* GENERATED_NODE_SPEC_PATH;
|
|
286
|
+
return path.join(confectDirectory, generatedNodeSpecPath);
|
|
269
287
|
});
|
|
270
288
|
const loadGeneratedSpec = Effect.gen(function* () {
|
|
271
289
|
const specPath = yield* getGeneratedSpecPath;
|
|
@@ -301,39 +319,22 @@ const loadPreviousFunctionPaths = Effect.gen(function* () {
|
|
|
301
319
|
})
|
|
302
320
|
});
|
|
303
321
|
});
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const schemaImportPath = yield* toModuleImportPath(path.relative(apiDir, path.join(confectDirectory, "schema.ts")));
|
|
310
|
-
const specImportPath = yield* toModuleImportPath(path.relative(apiDir, path.join(confectDirectory, GENERATED_SPEC_PATH)));
|
|
311
|
-
yield* writeFileStringAndLog(apiPath, yield* api({
|
|
312
|
-
schemaImportPath,
|
|
313
|
-
specImportPath
|
|
314
|
-
}));
|
|
315
|
-
});
|
|
316
|
-
const generateNodeApi = Effect.gen(function* () {
|
|
322
|
+
/**
|
|
323
|
+
* Remove a now-obsolete `_generated/<name>.ts` if present (and log it), for
|
|
324
|
+
* projects upgrading from a version that still emitted it.
|
|
325
|
+
*/
|
|
326
|
+
const removeObsoleteGeneratedFile = (fileName) => Effect.gen(function* () {
|
|
317
327
|
const fs = yield* FileSystem.FileSystem;
|
|
318
328
|
const path = yield* Path.Path;
|
|
319
329
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
yield* removePathIfExists(nodeApiPath);
|
|
325
|
-
yield* logFileRemoved(nodeApiPath);
|
|
326
|
-
}
|
|
327
|
-
return;
|
|
330
|
+
const filePath = path.join(confectDirectory, "_generated", fileName);
|
|
331
|
+
if (yield* fs.exists(filePath)) {
|
|
332
|
+
yield* removePathIfExists(filePath);
|
|
333
|
+
yield* logFileRemoved(filePath);
|
|
328
334
|
}
|
|
329
|
-
const nodeApiDir = path.dirname(nodeApiPath);
|
|
330
|
-
const schemaImportPath = yield* toModuleImportPath(path.relative(nodeApiDir, path.join(confectDirectory, "schema.ts")));
|
|
331
|
-
const nodeSpecImportPath = yield* toModuleImportPath(path.relative(nodeApiDir, nodeSpecPath));
|
|
332
|
-
yield* writeFileStringAndLog(nodeApiPath, yield* nodeApi({
|
|
333
|
-
schemaImportPath,
|
|
334
|
-
nodeSpecImportPath
|
|
335
|
-
}));
|
|
336
335
|
});
|
|
336
|
+
const removeGeneratedApi = removeObsoleteGeneratedFile("api.ts");
|
|
337
|
+
const removeGeneratedNodeApi = removeObsoleteGeneratedFile("nodeApi.ts");
|
|
337
338
|
const generateFunctionModules = Effect.gen(function* () {
|
|
338
339
|
const spec = yield* loadGeneratedSpec;
|
|
339
340
|
const nodeSpecOption = yield* loadGeneratedNodeSpec;
|
|
@@ -342,32 +343,127 @@ const generateFunctionModules = Effect.gen(function* () {
|
|
|
342
343
|
onSome: (nodeSpec) => Spec.merge(spec, nodeSpec)
|
|
343
344
|
}));
|
|
344
345
|
});
|
|
345
|
-
|
|
346
|
+
/**
|
|
347
|
+
* The user-authored `confect/schema.ts` is no longer supported: codegen now
|
|
348
|
+
* owns both `_generated/schema.ts` (runtime) and `_generated/convexSchema.ts`
|
|
349
|
+
* (deploy), derived from a single scan of `confect/tables/*.ts`. Detect a
|
|
350
|
+
* stray file and fail with a clear migration message — leaving it in place
|
|
351
|
+
* would silently shadow the codegen-owned `_generated/schema.ts` /
|
|
352
|
+
* `_generated/convexSchema.ts`.
|
|
353
|
+
*/
|
|
354
|
+
const rejectLegacySchemaFile = Effect.gen(function* () {
|
|
346
355
|
const fs = yield* FileSystem.FileSystem;
|
|
347
356
|
const path = yield* Path.Path;
|
|
348
357
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
349
|
-
const
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
358
|
+
const legacyPath = path.join(confectDirectory, "schema.ts");
|
|
359
|
+
if (yield* fs.exists(legacyPath)) return yield* new LegacySchemaFileError({ schemaPath: "schema.ts" });
|
|
360
|
+
});
|
|
361
|
+
/**
|
|
362
|
+
* Surface a yellow `⚠` warning when codegen sees no tables — either the
|
|
363
|
+
* `confect/tables/` directory is missing or it contains no `.ts` files.
|
|
364
|
+
* Generation still succeeds (emitting an empty `DatabaseSchema` and
|
|
365
|
+
* `defineSchema({})`), since action-only / table-free Confect backends
|
|
366
|
+
* are legal — but the warning catches the much more common case of a
|
|
367
|
+
* typoed directory or files placed under the wrong root.
|
|
368
|
+
*/
|
|
369
|
+
const warnIfNoTables = (tableModules) => tableModules.length === 0 ? logWarn(`No tables discovered in \`confect/${TABLES_DIRNAME}/\`. Generating an empty schema; add a \`Table.make(...)\` module under that directory unless this backend is intentionally tables-free.`) : Effect.void;
|
|
370
|
+
const tableModuleBindings = (tableModules, generatedFilePath) => Effect.gen(function* () {
|
|
371
|
+
const path = yield* Path.Path;
|
|
372
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
373
|
+
const generatedDir = path.dirname(generatedFilePath);
|
|
374
|
+
const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
|
|
375
|
+
return yield* Effect.forEach(tableModules, (tm) => Effect.gen(function* () {
|
|
376
|
+
const wrapperAbsolutePath = path.join(confectDirectory, generatedTablesDirname, `${tm.tableName}.ts`);
|
|
377
|
+
return {
|
|
378
|
+
importPath: yield* toModuleImportPath(path.relative(generatedDir, wrapperAbsolutePath)),
|
|
379
|
+
tableName: tm.tableName
|
|
380
|
+
};
|
|
354
381
|
}));
|
|
355
382
|
});
|
|
356
|
-
const
|
|
383
|
+
const generateIdConstructor = (tableModules) => Effect.gen(function* () {
|
|
384
|
+
const path = yield* Path.Path;
|
|
385
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
386
|
+
const generatedIdPath = yield* GENERATED_ID_PATH;
|
|
387
|
+
const idPath = path.join(confectDirectory, generatedIdPath);
|
|
388
|
+
const tableNames = tableModules.map((tm) => tm.tableName);
|
|
389
|
+
yield* writeFileStringAndLog(idPath, yield* id({ tableNames }));
|
|
390
|
+
});
|
|
391
|
+
const generateTableWrappers = (tableModules) => Effect.gen(function* () {
|
|
392
|
+
const fs = yield* FileSystem.FileSystem;
|
|
393
|
+
const path = yield* Path.Path;
|
|
394
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
395
|
+
const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
|
|
396
|
+
const wrappersDir = path.join(confectDirectory, generatedTablesDirname);
|
|
397
|
+
if (!(yield* fs.exists(wrappersDir))) yield* fs.makeDirectory(wrappersDir, { recursive: true });
|
|
398
|
+
yield* Effect.forEach(tableModules, (tm) => Effect.gen(function* () {
|
|
399
|
+
const wrapperPath = path.join(confectDirectory, generatedTablesDirname, `${tm.tableName}.ts`);
|
|
400
|
+
const unnamedAbsolutePath = path.join(confectDirectory, tm.relativePath);
|
|
401
|
+
const unnamedImportPath = yield* toModuleImportPath(path.relative(path.dirname(wrapperPath), unnamedAbsolutePath));
|
|
402
|
+
yield* writeFileStringAndLog(wrapperPath, yield* tableWrapper({
|
|
403
|
+
tableName: tm.tableName,
|
|
404
|
+
unnamedImportPath
|
|
405
|
+
}));
|
|
406
|
+
}), { concurrency: "unbounded" });
|
|
407
|
+
});
|
|
408
|
+
/**
|
|
409
|
+
* Remove any stale `_generated/tables/*.ts` wrapper whose source table
|
|
410
|
+
* has been deleted or renamed. Mirrors `removeObsoleteRegisteredFunctions`
|
|
411
|
+
* for the wrapper directory.
|
|
412
|
+
*/
|
|
413
|
+
const removeObsoleteTableWrappers = (tableModules) => Effect.gen(function* () {
|
|
414
|
+
const fs = yield* FileSystem.FileSystem;
|
|
415
|
+
const path = yield* Path.Path;
|
|
416
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
417
|
+
const generatedTablesDirname = yield* GENERATED_TABLES_DIRNAME;
|
|
418
|
+
const wrappersDir = path.join(confectDirectory, generatedTablesDirname);
|
|
419
|
+
if (!(yield* fs.exists(wrappersDir))) return;
|
|
420
|
+
const expected = new Set(tableModules.map((tm) => `${tm.tableName}.ts`));
|
|
421
|
+
const existing = yield* fs.readDirectory(wrappersDir, { recursive: true });
|
|
422
|
+
yield* Effect.forEach(existing, (entry) => {
|
|
423
|
+
if (path.extname(entry) !== ".ts") return Effect.void;
|
|
424
|
+
if (!expected.has(entry)) return Effect.gen(function* () {
|
|
425
|
+
const absolutePath = path.join(wrappersDir, entry);
|
|
426
|
+
if (yield* fs.exists(absolutePath)) {
|
|
427
|
+
yield* removePathIfExists(absolutePath);
|
|
428
|
+
yield* logFileRemoved(absolutePath);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
return Effect.void;
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
const generateRuntimeSchema = (tableModules) => Effect.gen(function* () {
|
|
435
|
+
const path = yield* Path.Path;
|
|
436
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
437
|
+
const generatedSchemaPath = yield* GENERATED_SCHEMA_PATH;
|
|
438
|
+
const schemaPath = path.join(confectDirectory, generatedSchemaPath);
|
|
439
|
+
const bindings = yield* tableModuleBindings(tableModules, schemaPath);
|
|
440
|
+
yield* writeFileStringAndLog(schemaPath, yield* runtimeSchema({ tableModules: bindings }));
|
|
441
|
+
});
|
|
442
|
+
const generateConvexSchema = (tableModules) => Effect.gen(function* () {
|
|
443
|
+
const path = yield* Path.Path;
|
|
444
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
445
|
+
const generatedConvexSchemaPath = yield* GENERATED_CONVEX_SCHEMA_PATH;
|
|
446
|
+
const convexSchemaPath = path.join(confectDirectory, generatedConvexSchemaPath);
|
|
447
|
+
const bindings = yield* tableModuleBindings(tableModules, convexSchemaPath);
|
|
448
|
+
yield* writeFileStringAndLog(convexSchemaPath, yield* convexSchema({ tableModules: bindings }));
|
|
449
|
+
});
|
|
450
|
+
const generateConvexSchemaReexport = Effect.gen(function* () {
|
|
357
451
|
const path = yield* Path.Path;
|
|
358
452
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
359
453
|
const convexDirectory = yield* ConvexDirectory.get;
|
|
360
|
-
const
|
|
454
|
+
const generatedConvexSchemaRelativePath = yield* GENERATED_CONVEX_SCHEMA_PATH;
|
|
361
455
|
const convexSchemaPath = path.join(convexDirectory, "schema.ts");
|
|
362
|
-
const
|
|
363
|
-
yield*
|
|
456
|
+
const generatedConvexSchemaPath = path.join(confectDirectory, generatedConvexSchemaRelativePath);
|
|
457
|
+
const convexSchemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(convexSchemaPath), generatedConvexSchemaPath));
|
|
458
|
+
yield* writeFileStringAndLog(convexSchemaPath, yield* schema({ convexSchemaImportPath }));
|
|
364
459
|
});
|
|
365
460
|
const generateServices = Effect.gen(function* () {
|
|
366
461
|
const path = yield* Path.Path;
|
|
367
462
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
368
463
|
const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
|
|
369
464
|
const servicesPath = path.join(confectGeneratedDirectory, "services.ts");
|
|
370
|
-
const
|
|
465
|
+
const generatedSchemaPath = yield* GENERATED_SCHEMA_PATH;
|
|
466
|
+
const schemaImportPath = yield* toModuleImportPath(path.relative(path.dirname(servicesPath), path.join(confectDirectory, generatedSchemaPath)));
|
|
371
467
|
yield* writeFileStringAndLog(servicesPath, yield* services({ schemaImportPath }));
|
|
372
468
|
});
|
|
373
469
|
const generateRefs = Effect.gen(function* () {
|
|
@@ -377,7 +473,8 @@ const generateRefs = Effect.gen(function* () {
|
|
|
377
473
|
const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
|
|
378
474
|
const refsPath = path.join(confectGeneratedDirectory, "refs.ts");
|
|
379
475
|
const refsDir = path.dirname(refsPath);
|
|
380
|
-
const
|
|
476
|
+
const generatedSpecPath = yield* GENERATED_SPEC_PATH;
|
|
477
|
+
const specImportPath = yield* toModuleImportPath(path.relative(refsDir, path.join(confectDirectory, generatedSpecPath)));
|
|
381
478
|
const nodeSpecPath = yield* getGeneratedNodeSpecPath;
|
|
382
479
|
const nodeSpecImportPath = (yield* fs.exists(nodeSpecPath)) ? Option.some(yield* toModuleImportPath(path.relative(refsDir, nodeSpecPath))) : Option.none();
|
|
383
480
|
yield* writeFileStringAndLog(refsPath, yield* refs({
|