@confect/cli 1.0.0-next.3 → 1.0.0-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @confect/cli
2
2
 
3
+ ## 1.0.0-next.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 46109fb: Support Node actions
8
+ - Updated dependencies [46109fb]
9
+ - @confect/server@1.0.0-next.4
10
+ - @confect/core@1.0.0-next.4
11
+
3
12
  ## 1.0.0-next.3
4
13
 
5
14
  ### Patch Changes
@@ -1,7 +1,7 @@
1
- import { logCompleted, logFileAdded, logFileModified } from "../log.mjs";
1
+ import { logFileAdded, logFileModified, logFileRemoved, logPending, logSuccess } from "../log.mjs";
2
2
  import { ConvexDirectory } from "../services/ConvexDirectory.mjs";
3
3
  import { ConfectDirectory } from "../services/ConfectDirectory.mjs";
4
- import { api, refs, registeredFunctions, schema, services } from "../templates.mjs";
4
+ import { api, nodeApi, nodeRegisteredFunctions, refs, registeredFunctions, schema, services } from "../templates.mjs";
5
5
  import { generateAuthConfig, generateConvexConfig, generateCrons, generateFunctions, generateHttp, removePathExtension, writeFileStringAndLog } from "../utils.mjs";
6
6
  import { Effect, Match, Option } from "effect";
7
7
  import { Command } from "@effect/cli";
@@ -11,9 +11,25 @@ import { FileSystem, Path } from "@effect/platform";
11
11
  import * as tsx from "tsx/esm/api";
12
12
 
13
13
  //#region src/confect/codegen.ts
14
+ const getNodeSpecPath = Effect.gen(function* () {
15
+ const path = yield* Path.Path;
16
+ const confectDirectory = yield* ConfectDirectory.get;
17
+ return path.join(confectDirectory, "nodeSpec.ts");
18
+ });
19
+ const loadNodeSpec = Effect.gen(function* () {
20
+ const fs = yield* FileSystem.FileSystem;
21
+ const path = yield* Path.Path;
22
+ const nodeSpecPath = yield* getNodeSpecPath;
23
+ if (!(yield* fs.exists(nodeSpecPath))) return Option.none();
24
+ const nodeSpecPathUrl = yield* path.toFileUrl(nodeSpecPath);
25
+ const nodeSpec = (yield* Effect.promise(() => tsx.tsImport(nodeSpecPathUrl.href, import.meta.url))).default;
26
+ if (!Spec.isNodeSpec(nodeSpec)) return yield* Effect.die("nodeSpec.ts does not export a valid Node Spec");
27
+ return Option.some(nodeSpec);
28
+ });
14
29
  const codegen = Command.make("codegen", {}, () => Effect.gen(function* () {
30
+ yield* logPending("Performing initial sync…");
15
31
  yield* codegenHandler;
16
- yield* logCompleted("Generated files are up-to-date");
32
+ yield* logSuccess("Generated files are up-to-date");
17
33
  })).pipe(Command.withDescription("Generate `confect/_generated` files and the contents of the `convex` directory (except `tsconfig.json`)"));
18
34
  const codegenHandler = Effect.gen(function* () {
19
35
  yield* generateConfectGeneratedDirectory;
@@ -21,6 +37,8 @@ const codegenHandler = Effect.gen(function* () {
21
37
  generateApi,
22
38
  generateRefs,
23
39
  generateRegisteredFunctions,
40
+ generateNodeApi,
41
+ generateNodeRegisteredFunctions,
24
42
  generateServices
25
43
  ], { concurrency: "unbounded" });
26
44
  const [functionPaths] = yield* Effect.all([
@@ -46,21 +64,50 @@ const generateApi = Effect.gen(function* () {
46
64
  const path = yield* Path.Path;
47
65
  const confectDirectory = yield* ConfectDirectory.get;
48
66
  const apiPath = path.join(confectDirectory, "_generated", "api.ts");
49
- const schemaImportPath = yield* removePathExtension(path.relative(path.dirname(apiPath), path.join(confectDirectory, "schema.ts")));
50
- const specImportPath = yield* removePathExtension(path.relative(path.dirname(apiPath), path.join(confectDirectory, "spec.ts")));
67
+ const apiDir = path.dirname(apiPath);
68
+ const schemaImportPath = yield* removePathExtension(path.relative(apiDir, path.join(confectDirectory, "schema.ts")));
69
+ const specImportPath = yield* removePathExtension(path.relative(apiDir, path.join(confectDirectory, "spec.ts")));
51
70
  yield* writeFileStringAndLog(apiPath, yield* api({
52
71
  schemaImportPath,
53
72
  specImportPath
54
73
  }));
55
74
  });
75
+ const generateNodeApi = Effect.gen(function* () {
76
+ const fs = yield* FileSystem.FileSystem;
77
+ const path = yield* Path.Path;
78
+ const confectDirectory = yield* ConfectDirectory.get;
79
+ const nodeSpecPath = yield* getNodeSpecPath;
80
+ const nodeApiPath = path.join(confectDirectory, "_generated", "nodeApi.ts");
81
+ if (!(yield* fs.exists(nodeSpecPath))) {
82
+ if (yield* fs.exists(nodeApiPath)) {
83
+ yield* fs.remove(nodeApiPath);
84
+ yield* logFileRemoved(nodeApiPath);
85
+ }
86
+ return;
87
+ }
88
+ const nodeApiDir = path.dirname(nodeApiPath);
89
+ const schemaImportPath = yield* removePathExtension(path.relative(nodeApiDir, path.join(confectDirectory, "schema.ts")));
90
+ const nodeSpecImportPath = yield* removePathExtension(path.relative(nodeApiDir, nodeSpecPath));
91
+ yield* writeFileStringAndLog(nodeApiPath, yield* nodeApi({
92
+ schemaImportPath,
93
+ nodeSpecImportPath
94
+ }));
95
+ });
56
96
  const generateFunctionModules = Effect.gen(function* () {
97
+ const fs = yield* FileSystem.FileSystem;
57
98
  const path = yield* Path.Path;
58
99
  const confectDirectory = yield* ConfectDirectory.get;
59
100
  const specPath = path.join(confectDirectory, "spec.ts");
60
101
  const specPathUrl = yield* path.toFileUrl(specPath);
61
102
  const spec = (yield* Effect.promise(() => tsx.tsImport(specPathUrl.href, import.meta.url))).default;
62
- if (!Spec.isSpec(spec)) return yield* Effect.die("spec.ts does not export a valid Spec");
63
- return yield* generateFunctions(spec);
103
+ if (!Spec.isConvexSpec(spec)) return yield* Effect.die("spec.ts does not export a valid Convex Spec");
104
+ const nodeImplPath = path.join(confectDirectory, "nodeImpl.ts");
105
+ const nodeImplExists = yield* fs.exists(nodeImplPath);
106
+ const nodeSpecOption = yield* loadNodeSpec;
107
+ return yield* generateFunctions(Option.match(nodeSpecOption, {
108
+ onNone: () => spec,
109
+ onSome: (nodeSpec) => nodeImplExists ? Spec.merge(spec, nodeSpec) : spec
110
+ }));
64
111
  });
65
112
  const generateSchema = Effect.gen(function* () {
66
113
  const path = yield* Path.Path;
@@ -92,14 +139,39 @@ const generateRegisteredFunctions = Effect.gen(function* () {
92
139
  const implImportPath = yield* removePathExtension(path.relative(path.dirname(registeredFunctionsPath), path.join(confectDirectory, "impl.ts")));
93
140
  yield* writeFileStringAndLog(registeredFunctionsPath, yield* registeredFunctions({ implImportPath }));
94
141
  });
142
+ const generateNodeRegisteredFunctions = Effect.gen(function* () {
143
+ const fs = yield* FileSystem.FileSystem;
144
+ const path = yield* Path.Path;
145
+ const confectDirectory = yield* ConfectDirectory.get;
146
+ const nodeImplPath = path.join(confectDirectory, "nodeImpl.ts");
147
+ const nodeSpecPath = yield* getNodeSpecPath;
148
+ const nodeRegisteredFunctionsPath = path.join(confectDirectory, "_generated", "nodeRegisteredFunctions.ts");
149
+ const nodeImplExists = yield* fs.exists(nodeImplPath);
150
+ const nodeSpecExists = yield* fs.exists(nodeSpecPath);
151
+ if (!nodeImplExists || !nodeSpecExists) {
152
+ if (yield* fs.exists(nodeRegisteredFunctionsPath)) {
153
+ yield* fs.remove(nodeRegisteredFunctionsPath);
154
+ yield* logFileRemoved(nodeRegisteredFunctionsPath);
155
+ }
156
+ return;
157
+ }
158
+ const nodeImplImportPath = yield* removePathExtension(path.relative(path.dirname(nodeRegisteredFunctionsPath), nodeImplPath));
159
+ yield* writeFileStringAndLog(nodeRegisteredFunctionsPath, yield* nodeRegisteredFunctions({ nodeImplImportPath }));
160
+ });
95
161
  const generateRefs = Effect.gen(function* () {
162
+ const fs = yield* FileSystem.FileSystem;
96
163
  const path = yield* Path.Path;
97
164
  const confectDirectory = yield* ConfectDirectory.get;
98
165
  const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
99
- const confectSpecPath = path.join(confectDirectory, "spec.ts");
100
166
  const refsPath = path.join(confectGeneratedDirectory, "refs.ts");
101
- const importPathWithoutExt = yield* removePathExtension(path.relative(path.dirname(refsPath), confectSpecPath));
102
- yield* writeFileStringAndLog(refsPath, yield* refs({ specImportPath: importPathWithoutExt }));
167
+ const refsDir = path.dirname(refsPath);
168
+ const specImportPath = yield* removePathExtension(path.relative(refsDir, path.join(confectDirectory, "spec.ts")));
169
+ const nodeSpecPath = yield* getNodeSpecPath;
170
+ const nodeSpecImportPath = (yield* fs.exists(nodeSpecPath)) ? yield* removePathExtension(path.relative(refsDir, nodeSpecPath)) : null;
171
+ yield* writeFileStringAndLog(refsPath, yield* refs({
172
+ specImportPath,
173
+ ...nodeSpecImportPath === null ? {} : { nodeSpecImportPath }
174
+ }));
103
175
  });
104
176
  const logGenerated = (effect) => effect.pipe(Effect.tap(Option.match({
105
177
  onNone: () => Effect.void,
@@ -107,5 +179,5 @@ const logGenerated = (effect) => effect.pipe(Effect.tap(Option.match({
107
179
  })));
108
180
 
109
181
  //#endregion
110
- export { codegen, codegenHandler };
182
+ export { codegen, codegenHandler, generateNodeApi, generateNodeRegisteredFunctions };
111
183
  //# sourceMappingURL=codegen.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.mjs","names":["templates.api","templates.schema","templates.services","templates.registeredFunctions","templates.refs"],"sources":["../../src/confect/codegen.ts"],"sourcesContent":["import { Spec } from \"@confect/core\";\nimport { DatabaseSchema } from \"@confect/server\";\nimport { Command } from \"@effect/cli\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport { Effect, Match, Option } from \"effect\";\nimport * as tsx from \"tsx/esm/api\";\nimport { logCompleted, logFileAdded, logFileModified } from \"../log\";\nimport { ConfectDirectory } from \"../services/ConfectDirectory\";\nimport { ConvexDirectory } from \"../services/ConvexDirectory\";\nimport * as templates from \"../templates\";\nimport {\n generateAuthConfig,\n generateConvexConfig,\n generateCrons,\n generateFunctions,\n generateHttp,\n removePathExtension,\n writeFileStringAndLog,\n} from \"../utils\";\n\nexport const codegen = Command.make(\"codegen\", {}, () =>\n Effect.gen(function* () {\n yield* codegenHandler;\n yield* logCompleted(\"Generated files are up-to-date\");\n }),\n).pipe(\n Command.withDescription(\n \"Generate `confect/_generated` files and the contents of the `convex` directory (except `tsconfig.json`)\",\n ),\n);\n\nexport const codegenHandler = Effect.gen(function* () {\n yield* generateConfectGeneratedDirectory;\n yield* Effect.all(\n [generateApi, generateRefs, generateRegisteredFunctions, generateServices],\n { concurrency: \"unbounded\" },\n );\n const [functionPaths] = yield* Effect.all(\n [\n generateFunctionModules,\n generateSchema,\n logGenerated(generateHttp),\n logGenerated(generateConvexConfig),\n logGenerated(generateCrons),\n logGenerated(generateAuthConfig),\n ],\n { concurrency: \"unbounded\" },\n );\n return functionPaths;\n});\n\nconst generateConfectGeneratedDirectory = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n if (!(yield* fs.exists(path.join(confectDirectory, \"_generated\")))) {\n yield* fs.makeDirectory(path.join(confectDirectory, \"_generated\"), {\n recursive: true,\n });\n yield* logFileAdded(path.join(confectDirectory, \"_generated\") + \"/\");\n }\n});\n\nconst generateApi = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const apiPath = path.join(confectDirectory, \"_generated\", \"api.ts\");\n\n const schemaImportPath = yield* removePathExtension(\n path.relative(\n path.dirname(apiPath),\n path.join(confectDirectory, \"schema.ts\"),\n ),\n );\n\n const specImportPath = yield* removePathExtension(\n path.relative(\n path.dirname(apiPath),\n path.join(confectDirectory, \"spec.ts\"),\n ),\n );\n\n const apiContents = yield* templates.api({\n schemaImportPath,\n specImportPath,\n });\n\n yield* writeFileStringAndLog(apiPath, apiContents);\n});\n\nconst generateFunctionModules = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const specPath = path.join(confectDirectory, \"spec.ts\");\n const specPathUrl = yield* path.toFileUrl(specPath);\n\n const specModule = yield* Effect.promise(() =>\n tsx.tsImport(specPathUrl.href, import.meta.url),\n );\n const spec = specModule.default;\n\n if (!Spec.isSpec(spec)) {\n return yield* Effect.die(\"spec.ts does not export a valid Spec\");\n }\n\n return yield* generateFunctions(spec);\n});\n\nconst generateSchema = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n const convexDirectory = yield* ConvexDirectory.get;\n\n const confectSchemaPath = path.join(confectDirectory, \"schema.ts\");\n const confectSchemaUrl = yield* path.toFileUrl(confectSchemaPath);\n\n yield* Effect.promise(() =>\n tsx.tsImport(confectSchemaUrl.href, import.meta.url),\n ).pipe(\n Effect.andThen((schemaModule) => {\n const defaultExport = schemaModule.default;\n\n return DatabaseSchema.isSchema(defaultExport)\n ? Effect.succeed(defaultExport)\n : Effect.die(\"Invalid schema module\");\n }),\n );\n\n const convexSchemaPath = path.join(convexDirectory, \"schema.ts\");\n\n const relativeImportPath = path.relative(\n path.dirname(convexSchemaPath),\n confectSchemaPath,\n );\n const importPathWithoutExt = yield* removePathExtension(relativeImportPath);\n const schemaContents = yield* templates.schema({\n schemaImportPath: importPathWithoutExt,\n });\n\n yield* writeFileStringAndLog(convexSchemaPath, schemaContents);\n});\n\nconst generateServices = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const confectGeneratedDirectory = path.join(confectDirectory, \"_generated\");\n\n const servicesPath = path.join(confectGeneratedDirectory, \"services.ts\");\n const schemaImportPath = path.relative(\n path.dirname(servicesPath),\n path.join(confectDirectory, \"schema\"),\n );\n\n const servicesContentsString = yield* templates.services({\n schemaImportPath,\n });\n\n yield* writeFileStringAndLog(servicesPath, servicesContentsString);\n});\n\nconst generateRegisteredFunctions = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const confectGeneratedDirectory = path.join(confectDirectory, \"_generated\");\n\n const registeredFunctionsPath = path.join(\n confectGeneratedDirectory,\n \"registeredFunctions.ts\",\n );\n const implImportPath = yield* removePathExtension(\n path.relative(\n path.dirname(registeredFunctionsPath),\n path.join(confectDirectory, \"impl.ts\"),\n ),\n );\n\n const registeredFunctionsContents = yield* templates.registeredFunctions({\n implImportPath,\n });\n\n yield* writeFileStringAndLog(\n registeredFunctionsPath,\n registeredFunctionsContents,\n );\n});\n\nconst generateRefs = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const confectGeneratedDirectory = path.join(confectDirectory, \"_generated\");\n\n const confectSpecPath = path.join(confectDirectory, \"spec.ts\");\n const refsPath = path.join(confectGeneratedDirectory, \"refs.ts\");\n\n const relativeImportPath = path.relative(\n path.dirname(refsPath),\n confectSpecPath,\n );\n const importPathWithoutExt = yield* removePathExtension(relativeImportPath);\n\n const refsContents = yield* templates.refs({\n specImportPath: importPathWithoutExt,\n });\n\n yield* writeFileStringAndLog(refsPath, refsContents);\n});\n\nconst logGenerated = (effect: typeof generateHttp) =>\n effect.pipe(\n Effect.tap(\n Option.match({\n onNone: () => Effect.void,\n onSome: ({ change, convexFilePath }) =>\n Match.value(change).pipe(\n Match.when(\"Added\", () => logFileAdded(convexFilePath)),\n Match.when(\"Modified\", () => logFileModified(convexFilePath)),\n Match.when(\"Unchanged\", () => Effect.void),\n Match.exhaustive,\n ),\n }),\n ),\n );\n"],"mappings":"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"}
1
+ {"version":3,"file":"codegen.mjs","names":["templates.api","templates.nodeApi","templates.schema","templates.services","templates.registeredFunctions","templates.nodeRegisteredFunctions","templates.refs"],"sources":["../../src/confect/codegen.ts"],"sourcesContent":["import { Spec } from \"@confect/core\";\nimport { DatabaseSchema } from \"@confect/server\";\nimport { Command } from \"@effect/cli\";\nimport { FileSystem, Path } from \"@effect/platform\";\nimport { Effect, Match, Option } from \"effect\";\nimport * as tsx from \"tsx/esm/api\";\nimport {\n logFileAdded,\n logFileModified,\n logFileRemoved,\n logPending,\n logSuccess,\n} from \"../log\";\nimport { ConfectDirectory } from \"../services/ConfectDirectory\";\nimport { ConvexDirectory } from \"../services/ConvexDirectory\";\nimport * as templates from \"../templates\";\nimport {\n generateAuthConfig,\n generateConvexConfig,\n generateCrons,\n generateFunctions,\n generateHttp,\n removePathExtension,\n writeFileStringAndLog,\n} from \"../utils\";\n\nconst getNodeSpecPath = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n return path.join(confectDirectory, \"nodeSpec.ts\");\n});\n\nconst loadNodeSpec = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const nodeSpecPath = yield* getNodeSpecPath;\n\n if (!(yield* fs.exists(nodeSpecPath))) {\n return Option.none<Spec.AnyWithPropsWithRuntime<\"Node\">>();\n }\n\n const nodeSpecPathUrl = yield* path.toFileUrl(nodeSpecPath);\n const nodeSpecModule = yield* Effect.promise(() =>\n tsx.tsImport(nodeSpecPathUrl.href, import.meta.url),\n );\n const nodeSpec = nodeSpecModule.default;\n\n if (!Spec.isNodeSpec(nodeSpec)) {\n return yield* Effect.die(\"nodeSpec.ts does not export a valid Node Spec\");\n }\n\n return Option.some(nodeSpec);\n});\n\nexport const codegen = Command.make(\"codegen\", {}, () =>\n Effect.gen(function* () {\n yield* logPending(\"Performing initial sync…\");\n yield* codegenHandler;\n yield* logSuccess(\"Generated files are up-to-date\");\n }),\n).pipe(\n Command.withDescription(\n \"Generate `confect/_generated` files and the contents of the `convex` directory (except `tsconfig.json`)\",\n ),\n);\n\nexport const codegenHandler = Effect.gen(function* () {\n yield* generateConfectGeneratedDirectory;\n yield* Effect.all(\n [\n generateApi,\n generateRefs,\n generateRegisteredFunctions,\n generateNodeApi,\n generateNodeRegisteredFunctions,\n generateServices,\n ],\n { concurrency: \"unbounded\" },\n );\n const [functionPaths] = yield* Effect.all(\n [\n generateFunctionModules,\n generateSchema,\n logGenerated(generateHttp),\n logGenerated(generateConvexConfig),\n logGenerated(generateCrons),\n logGenerated(generateAuthConfig),\n ],\n { concurrency: \"unbounded\" },\n );\n return functionPaths;\n});\n\nconst generateConfectGeneratedDirectory = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n if (!(yield* fs.exists(path.join(confectDirectory, \"_generated\")))) {\n yield* fs.makeDirectory(path.join(confectDirectory, \"_generated\"), {\n recursive: true,\n });\n yield* logFileAdded(path.join(confectDirectory, \"_generated\") + \"/\");\n }\n});\n\nconst generateApi = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const apiPath = path.join(confectDirectory, \"_generated\", \"api.ts\");\n const apiDir = path.dirname(apiPath);\n\n const schemaImportPath = yield* removePathExtension(\n path.relative(apiDir, path.join(confectDirectory, \"schema.ts\")),\n );\n\n const specImportPath = yield* removePathExtension(\n path.relative(apiDir, path.join(confectDirectory, \"spec.ts\")),\n );\n\n const apiContents = yield* templates.api({\n schemaImportPath,\n specImportPath,\n });\n\n yield* writeFileStringAndLog(apiPath, apiContents);\n});\n\nexport const generateNodeApi = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const nodeSpecPath = yield* getNodeSpecPath;\n const nodeApiPath = path.join(confectDirectory, \"_generated\", \"nodeApi.ts\");\n\n if (!(yield* fs.exists(nodeSpecPath))) {\n if (yield* fs.exists(nodeApiPath)) {\n yield* fs.remove(nodeApiPath);\n yield* logFileRemoved(nodeApiPath);\n }\n return;\n }\n\n const nodeApiDir = path.dirname(nodeApiPath);\n\n const schemaImportPath = yield* removePathExtension(\n path.relative(nodeApiDir, path.join(confectDirectory, \"schema.ts\")),\n );\n\n const nodeSpecImportPath = yield* removePathExtension(\n path.relative(nodeApiDir, nodeSpecPath),\n );\n\n const nodeApiContents = yield* templates.nodeApi({\n schemaImportPath,\n nodeSpecImportPath,\n });\n\n yield* writeFileStringAndLog(nodeApiPath, nodeApiContents);\n});\n\nconst generateFunctionModules = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const specPath = path.join(confectDirectory, \"spec.ts\");\n const specPathUrl = yield* path.toFileUrl(specPath);\n\n const specModule = yield* Effect.promise(() =>\n tsx.tsImport(specPathUrl.href, import.meta.url),\n );\n const spec = specModule.default;\n\n if (!Spec.isConvexSpec(spec)) {\n return yield* Effect.die(\"spec.ts does not export a valid Convex Spec\");\n }\n\n const nodeImplPath = path.join(confectDirectory, \"nodeImpl.ts\");\n const nodeImplExists = yield* fs.exists(nodeImplPath);\n const nodeSpecOption = yield* loadNodeSpec;\n\n const mergedSpec = Option.match(nodeSpecOption, {\n onNone: () => spec,\n onSome: (nodeSpec) => (nodeImplExists ? Spec.merge(spec, nodeSpec) : spec),\n });\n\n return yield* generateFunctions(mergedSpec);\n});\n\nconst generateSchema = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n const convexDirectory = yield* ConvexDirectory.get;\n\n const confectSchemaPath = path.join(confectDirectory, \"schema.ts\");\n const confectSchemaUrl = yield* path.toFileUrl(confectSchemaPath);\n\n yield* Effect.promise(() =>\n tsx.tsImport(confectSchemaUrl.href, import.meta.url),\n ).pipe(\n Effect.andThen((schemaModule) => {\n const defaultExport = schemaModule.default;\n\n return DatabaseSchema.isSchema(defaultExport)\n ? Effect.succeed(defaultExport)\n : Effect.die(\"Invalid schema module\");\n }),\n );\n\n const convexSchemaPath = path.join(convexDirectory, \"schema.ts\");\n\n const relativeImportPath = path.relative(\n path.dirname(convexSchemaPath),\n confectSchemaPath,\n );\n const importPathWithoutExt = yield* removePathExtension(relativeImportPath);\n const schemaContents = yield* templates.schema({\n schemaImportPath: importPathWithoutExt,\n });\n\n yield* writeFileStringAndLog(convexSchemaPath, schemaContents);\n});\n\nconst generateServices = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const confectGeneratedDirectory = path.join(confectDirectory, \"_generated\");\n\n const servicesPath = path.join(confectGeneratedDirectory, \"services.ts\");\n const schemaImportPath = path.relative(\n path.dirname(servicesPath),\n path.join(confectDirectory, \"schema\"),\n );\n\n const servicesContentsString = yield* templates.services({\n schemaImportPath,\n });\n\n yield* writeFileStringAndLog(servicesPath, servicesContentsString);\n});\n\nconst generateRegisteredFunctions = Effect.gen(function* () {\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const confectGeneratedDirectory = path.join(confectDirectory, \"_generated\");\n\n const registeredFunctionsPath = path.join(\n confectGeneratedDirectory,\n \"registeredFunctions.ts\",\n );\n const implImportPath = yield* removePathExtension(\n path.relative(\n path.dirname(registeredFunctionsPath),\n path.join(confectDirectory, \"impl.ts\"),\n ),\n );\n\n const registeredFunctionsContents = yield* templates.registeredFunctions({\n implImportPath,\n });\n\n yield* writeFileStringAndLog(\n registeredFunctionsPath,\n registeredFunctionsContents,\n );\n});\n\nexport const generateNodeRegisteredFunctions = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const nodeImplPath = path.join(confectDirectory, \"nodeImpl.ts\");\n const nodeSpecPath = yield* getNodeSpecPath;\n const nodeRegisteredFunctionsPath = path.join(\n confectDirectory,\n \"_generated\",\n \"nodeRegisteredFunctions.ts\",\n );\n\n const nodeImplExists = yield* fs.exists(nodeImplPath);\n const nodeSpecExists = yield* fs.exists(nodeSpecPath);\n\n if (!nodeImplExists || !nodeSpecExists) {\n if (yield* fs.exists(nodeRegisteredFunctionsPath)) {\n yield* fs.remove(nodeRegisteredFunctionsPath);\n yield* logFileRemoved(nodeRegisteredFunctionsPath);\n }\n return;\n }\n\n const nodeImplImportPath = yield* removePathExtension(\n path.relative(path.dirname(nodeRegisteredFunctionsPath), nodeImplPath),\n );\n\n const nodeRegisteredFunctionsContents =\n yield* templates.nodeRegisteredFunctions({\n nodeImplImportPath,\n });\n\n yield* writeFileStringAndLog(\n nodeRegisteredFunctionsPath,\n nodeRegisteredFunctionsContents,\n );\n});\n\nconst generateRefs = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const confectGeneratedDirectory = path.join(confectDirectory, \"_generated\");\n const refsPath = path.join(confectGeneratedDirectory, \"refs.ts\");\n const refsDir = path.dirname(refsPath);\n\n const specImportPath = yield* removePathExtension(\n path.relative(refsDir, path.join(confectDirectory, \"spec.ts\")),\n );\n\n const nodeSpecPath = yield* getNodeSpecPath;\n const nodeSpecExists = yield* fs.exists(nodeSpecPath);\n const nodeSpecImportPath = nodeSpecExists\n ? yield* removePathExtension(path.relative(refsDir, nodeSpecPath))\n : null;\n\n const refsContents = yield* templates.refs({\n specImportPath,\n ...(nodeSpecImportPath === null ? {} : { nodeSpecImportPath }),\n });\n\n yield* writeFileStringAndLog(refsPath, refsContents);\n});\n\nconst logGenerated = (effect: typeof generateHttp) =>\n effect.pipe(\n Effect.tap(\n Option.match({\n onNone: () => Effect.void,\n onSome: ({ change, convexFilePath }) =>\n Match.value(change).pipe(\n Match.when(\"Added\", () => logFileAdded(convexFilePath)),\n Match.when(\"Modified\", () => logFileModified(convexFilePath)),\n Match.when(\"Unchanged\", () => Effect.void),\n Match.exhaustive,\n ),\n }),\n ),\n );\n"],"mappings":"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"}
@@ -1,12 +1,12 @@
1
1
  import { modulePath, toString } from "../GroupPath.mjs";
2
2
  import { ProjectRoot } from "../services/ProjectRoot.mjs";
3
- import { logCompleted, logFailed } from "../log.mjs";
3
+ import { logFailure, logPending, logSuccess } from "../log.mjs";
4
4
  import { ConvexDirectory } from "../services/ConvexDirectory.mjs";
5
5
  import { ConfectDirectory } from "../services/ConfectDirectory.mjs";
6
6
  import { diff, make } from "../FunctionPaths.mjs";
7
7
  import { generateAuthConfig, generateConvexConfig, generateCrons, generateHttp, removeGroups, writeGroups } from "../utils.mjs";
8
- import { codegenHandler } from "./codegen.mjs";
9
- import { Array, Console, Data, Duration, Effect, Equal, HashSet, Match, Option, Queue, Ref, Schema, Stream, String, pipe } from "effect";
8
+ import { codegenHandler, generateNodeApi, generateNodeRegisteredFunctions } from "./codegen.mjs";
9
+ import { Array, Console, Deferred, Duration, Effect, Equal, HashSet, Match, Option, Queue, Ref, Schema, Stream, String, pipe } from "effect";
10
10
  import { Command } from "@effect/cli";
11
11
  import { Spec } from "@confect/core";
12
12
  import { FileSystem, Path } from "@effect/platform";
@@ -17,23 +17,12 @@ import * as esbuild from "esbuild";
17
17
  //#region src/confect/dev.ts
18
18
  const pendingInit = {
19
19
  specDirty: false,
20
+ nodeImplDirty: false,
20
21
  httpDirty: false,
21
22
  appDirty: false,
22
23
  cronsDirty: false,
23
24
  authDirty: false
24
25
  };
25
- const FileChange = Data.taggedEnum();
26
- const logChangeReport = (changes) => Effect.gen(function* () {
27
- yield* logCompleted("Generated files are up-to-date");
28
- yield* Effect.when(Effect.forEach(changes, (change) => FileChange.$match(change, {
29
- OptionalFile: ({ change: c, filePath }) => logFileChangeIndented(c, filePath),
30
- GroupModule: ({ change: c, filePath, functionsAdded, functionsRemoved }) => Effect.gen(function* () {
31
- yield* logFileChangeIndented(c, filePath);
32
- yield* Effect.forEach(functionsAdded, logFunctionAddedIndented);
33
- yield* Effect.forEach(functionsRemoved, logFunctionRemovedIndented);
34
- })
35
- })), () => Array.isNonEmptyReadonlyArray(changes));
36
- });
37
26
  const changeChar = (change) => Match.value(change).pipe(Match.when("Added", () => ({
38
27
  char: "+",
39
28
  color: Ansi.green
@@ -48,27 +37,36 @@ const logFileChangeIndented = (change, fullPath) => Effect.gen(function* () {
48
37
  const prefix = (yield* ProjectRoot.get) + (yield* Path.Path).sep;
49
38
  const suffix = pipe(fullPath, String.startsWith(prefix)) ? pipe(fullPath, String.slice(prefix.length)) : fullPath;
50
39
  const { char, color } = changeChar(change);
51
- yield* Console.log(pipe(AnsiDoc.text(" "), AnsiDoc.cat(pipe(AnsiDoc.char(char), AnsiDoc.annotate(color))), AnsiDoc.catWithSpace(AnsiDoc.hcat([pipe(AnsiDoc.text(prefix), AnsiDoc.annotate(Ansi.blackBright)), pipe(AnsiDoc.text(suffix), AnsiDoc.annotate(color))])), AnsiDoc.render({ style: "pretty" })));
40
+ yield* Console.log(pipe(AnsiDoc.char(char), AnsiDoc.annotate(color), AnsiDoc.catWithSpace(AnsiDoc.hcat([pipe(AnsiDoc.text(prefix), AnsiDoc.annotate(Ansi.blackBright)), pipe(AnsiDoc.text(suffix), AnsiDoc.annotate(color))])), AnsiDoc.render({ style: "pretty" })));
52
41
  });
53
- const logFunctionAddedIndented = (functionPath) => Console.log(pipe(AnsiDoc.text(" "), AnsiDoc.cat(pipe(AnsiDoc.char("+"), AnsiDoc.annotate(Ansi.green))), AnsiDoc.catWithSpace(AnsiDoc.hcat([pipe(AnsiDoc.text(toString(functionPath.groupPath) + "."), AnsiDoc.annotate(Ansi.blackBright)), pipe(AnsiDoc.text(functionPath.name), AnsiDoc.annotate(Ansi.green))])), AnsiDoc.render({ style: "pretty" })));
54
- const logFunctionRemovedIndented = (functionPath) => Console.log(pipe(AnsiDoc.text(" "), AnsiDoc.cat(pipe(AnsiDoc.char("-"), AnsiDoc.annotate(Ansi.red))), AnsiDoc.catWithSpace(AnsiDoc.hcat([pipe(AnsiDoc.text(toString(functionPath.groupPath) + "."), AnsiDoc.annotate(Ansi.blackBright)), pipe(AnsiDoc.text(functionPath.name), AnsiDoc.annotate(Ansi.red))])), AnsiDoc.render({ style: "pretty" })));
42
+ const logFunctionAddedIndented = (functionPath) => Console.log(pipe(AnsiDoc.text(" "), AnsiDoc.cat(pipe(AnsiDoc.char("+"), AnsiDoc.annotate(Ansi.green))), AnsiDoc.catWithSpace(AnsiDoc.hcat([pipe(AnsiDoc.text(toString(functionPath.groupPath) + "."), AnsiDoc.annotate(Ansi.blackBright)), pipe(AnsiDoc.text(functionPath.name), AnsiDoc.annotate(Ansi.green))])), AnsiDoc.render({ style: "pretty" })));
43
+ const logFunctionRemovedIndented = (functionPath) => Console.log(pipe(AnsiDoc.text(" "), AnsiDoc.cat(pipe(AnsiDoc.char("-"), AnsiDoc.annotate(Ansi.red))), AnsiDoc.catWithSpace(AnsiDoc.hcat([pipe(AnsiDoc.text(toString(functionPath.groupPath) + "."), AnsiDoc.annotate(Ansi.blackBright)), pipe(AnsiDoc.text(functionPath.name), AnsiDoc.annotate(Ansi.red))])), AnsiDoc.render({ style: "pretty" })));
55
44
  const dev = Command.make("dev", {}, () => Effect.gen(function* () {
45
+ yield* logPending("Performing initial sync…");
56
46
  const initialFunctionPaths = yield* codegenHandler;
57
47
  const pendingRef = yield* Ref.make(pendingInit);
58
48
  const signal = yield* Queue.sliding(1);
49
+ const specWatcherRestartQueue = yield* Queue.sliding(1);
59
50
  yield* Effect.all([
60
- specFileWatcher(signal, pendingRef),
61
- confectDirectoryWatcher(signal, pendingRef),
51
+ specFileWatcher(signal, pendingRef, specWatcherRestartQueue),
52
+ confectDirectoryWatcher(signal, pendingRef, specWatcherRestartQueue),
62
53
  syncLoop(signal, pendingRef, initialFunctionPaths)
63
54
  ], { concurrency: "unbounded" });
64
55
  })).pipe(Command.withDescription("Start the Confect development server"));
65
56
  const syncLoop = (signal, pendingRef, initialFunctionPaths) => Effect.gen(function* () {
66
57
  const functionPathsRef = yield* Ref.make(initialFunctionPaths);
67
- const changesRef = yield* Ref.make([]);
58
+ const initialSyncDone = yield* Deferred.make();
68
59
  return yield* Effect.forever(Effect.gen(function* () {
69
60
  yield* Effect.logDebug("Running sync loop...");
70
61
  yield* Queue.take(signal);
62
+ const isDone = yield* Deferred.isDone(initialSyncDone);
63
+ yield* Effect.when(logPending("Dependencies changed, reloading…"), () => isDone);
64
+ yield* Deferred.succeed(initialSyncDone, void 0);
71
65
  const pending = yield* Ref.getAndSet(pendingRef, pendingInit);
66
+ if (pending.specDirty || pending.nodeImplDirty) {
67
+ yield* generateNodeApi;
68
+ yield* generateNodeRegisteredFunctions;
69
+ }
72
70
  const specResult = yield* Effect.if(pending.specDirty, {
73
71
  onTrue: () => loadSpec.pipe(Effect.andThen(Effect.fn(function* (spec) {
74
72
  yield* Effect.logDebug("Spec loaded");
@@ -78,144 +76,162 @@ const syncLoop = (signal, pendingRef, initialFunctionPaths) => Effect.gen(functi
78
76
  const current = make(spec);
79
77
  const { functionsAdded, functionsRemoved, groupsRemoved, groupsAdded, groupsChanged } = diff(previous, current);
80
78
  yield* removeGroups(groupsRemoved);
81
- const removedChanges = yield* Effect.forEach(groupsRemoved, (gp) => Effect.gen(function* () {
79
+ yield* Effect.forEach(groupsRemoved, (gp) => Effect.gen(function* () {
82
80
  const relativeModulePath = yield* modulePath(gp);
83
- return FileChange.GroupModule({
84
- change: "Removed",
85
- filePath: path.join(convexDirectory, relativeModulePath),
86
- functionsAdded: [],
87
- functionsRemoved: Array.fromIterable(HashSet.filter(functionsRemoved, (fp) => Equal.equals(fp.groupPath, gp)))
88
- });
81
+ yield* logFileChangeIndented("Removed", path.join(convexDirectory, relativeModulePath));
82
+ yield* Effect.forEach(Array.fromIterable(HashSet.filter(functionsRemoved, (fp) => Equal.equals(fp.groupPath, gp))), logFunctionRemovedIndented);
89
83
  }));
90
84
  yield* writeGroups(spec, groupsAdded);
91
- const addedChanges = yield* Effect.forEach(groupsAdded, (gp) => Effect.gen(function* () {
85
+ yield* Effect.forEach(groupsAdded, (gp) => Effect.gen(function* () {
92
86
  const relativeModulePath = yield* modulePath(gp);
93
- return FileChange.GroupModule({
94
- change: "Added",
95
- filePath: path.join(convexDirectory, relativeModulePath),
96
- functionsAdded: Array.fromIterable(HashSet.filter(functionsAdded, (fp) => Equal.equals(fp.groupPath, gp))),
97
- functionsRemoved: []
98
- });
87
+ yield* logFileChangeIndented("Added", path.join(convexDirectory, relativeModulePath));
88
+ yield* Effect.forEach(Array.fromIterable(HashSet.filter(functionsAdded, (fp) => Equal.equals(fp.groupPath, gp))), logFunctionAddedIndented);
99
89
  }));
100
90
  yield* writeGroups(spec, groupsChanged);
101
- const changedChanges = yield* Effect.forEach(groupsChanged, (gp) => Effect.gen(function* () {
91
+ yield* Effect.forEach(groupsChanged, (gp) => Effect.gen(function* () {
102
92
  const relativeModulePath = yield* modulePath(gp);
103
- return FileChange.GroupModule({
104
- change: "Modified",
105
- filePath: path.join(convexDirectory, relativeModulePath),
106
- functionsAdded: Array.fromIterable(HashSet.filter(functionsAdded, (fp) => Equal.equals(fp.groupPath, gp))),
107
- functionsRemoved: Array.fromIterable(HashSet.filter(functionsRemoved, (fp) => Equal.equals(fp.groupPath, gp)))
108
- });
93
+ yield* logFileChangeIndented("Modified", path.join(convexDirectory, relativeModulePath));
94
+ yield* Effect.forEach(Array.fromIterable(HashSet.filter(functionsAdded, (fp) => Equal.equals(fp.groupPath, gp))), logFunctionAddedIndented);
95
+ yield* Effect.forEach(Array.fromIterable(HashSet.filter(functionsRemoved, (fp) => Equal.equals(fp.groupPath, gp))), logFunctionRemovedIndented);
109
96
  }));
110
97
  yield* Ref.set(functionPathsRef, current);
111
- return Option.some([
112
- ...removedChanges,
113
- ...addedChanges,
114
- ...changedChanges
115
- ]);
116
- })), Effect.catchTag("SpecImportFailedError", () => logFailed("Spec import failed").pipe(Effect.as(Option.none()))), Effect.catchTag("SpecFileDoesNotExportSpecError", () => logFailed("Spec file does not default export a spec").pipe(Effect.as(Option.none())))),
117
- onFalse: () => Effect.succeed(Option.some([]))
98
+ return Option.some(void 0);
99
+ })), Effect.catchTag("SpecImportFailedError", () => logFailure("Spec import failed").pipe(Effect.as(Option.none()))), Effect.catchTag("SpecFileDoesNotExportSpecError", () => logFailure("Spec file does not default export a Convex spec").pipe(Effect.as(Option.none()))), Effect.catchTag("NodeSpecFileDoesNotExportSpecError", () => logFailure("Node spec file does not default export a Node spec").pipe(Effect.as(Option.none())))),
100
+ onFalse: () => Effect.succeed(Option.some(void 0))
118
101
  });
119
- const specChanges = Option.getOrElse(specResult, () => []);
120
102
  const dirtyOptionalFiles = [
121
103
  ...pending.httpDirty ? [syncOptionalFile(generateHttp, "http.ts")] : [],
122
104
  ...pending.appDirty ? [syncOptionalFile(generateConvexConfig, "convex.config.ts")] : [],
123
105
  ...pending.cronsDirty ? [syncOptionalFile(generateCrons, "crons.ts")] : [],
124
106
  ...pending.authDirty ? [syncOptionalFile(generateAuthConfig, "auth.config.ts")] : []
125
107
  ];
126
- const optionalChanges = Array.isNonEmptyReadonlyArray(dirtyOptionalFiles) ? yield* pipe(Effect.all(dirtyOptionalFiles, { concurrency: "unbounded" }), Effect.map(Array.getSomes)) : [];
127
- yield* Ref.update(changesRef, (prev) => [
128
- ...prev,
129
- ...specChanges,
130
- ...optionalChanges
131
- ]);
108
+ yield* Array.isNonEmptyReadonlyArray(dirtyOptionalFiles) ? Effect.all(dirtyOptionalFiles, { concurrency: "unbounded" }) : Effect.void;
132
109
  yield* Option.match(specResult, {
133
- onSome: () => Effect.gen(function* () {
134
- const pendingSize = yield* Queue.size(signal);
135
- yield* Effect.when(Effect.gen(function* () {
136
- yield* logChangeReport(yield* Ref.getAndSet(changesRef, []));
137
- }), () => pendingSize === 0);
138
- }),
139
- onNone: () => Ref.set(changesRef, [])
110
+ onSome: () => logSuccess("Generated files are up-to-date"),
111
+ onNone: () => Effect.void
140
112
  });
141
113
  }));
142
114
  });
143
115
  const loadSpec = Effect.gen(function* () {
144
- const specPathUrl = yield* (yield* Path.Path).toFileUrl(yield* getSpecPath);
116
+ const fs = yield* FileSystem.FileSystem;
117
+ const path = yield* Path.Path;
118
+ const confectDirectory = yield* ConfectDirectory.get;
119
+ const specPathUrl = yield* path.toFileUrl(yield* getSpecPath);
145
120
  const spec = (yield* Effect.tryPromise({
146
121
  try: () => tsx.tsImport(specPathUrl.href, import.meta.url),
147
122
  catch: (error) => new SpecImportFailedError({ error })
148
123
  })).default;
149
- if (Spec.isSpec(spec)) return spec;
150
- else return yield* Effect.fail(new SpecFileDoesNotExportSpecError());
124
+ if (!Spec.isConvexSpec(spec)) return yield* new SpecFileDoesNotExportSpecError();
125
+ const nodeImplPath = path.join(confectDirectory, "nodeImpl.ts");
126
+ const nodeImplExists = yield* fs.exists(nodeImplPath);
127
+ const nodeSpecOption = yield* loadNodeSpec;
128
+ return Option.match(nodeSpecOption, {
129
+ onNone: () => spec,
130
+ onSome: (nodeSpec) => nodeImplExists ? Spec.merge(spec, nodeSpec) : spec
131
+ });
151
132
  });
152
133
  const getSpecPath = Effect.gen(function* () {
153
134
  const path = yield* Path.Path;
154
135
  const confectDirectory = yield* ConfectDirectory.get;
155
136
  return path.join(confectDirectory, "spec.ts");
156
137
  });
157
- const specFileWatcher = (signal, pendingRef) => Effect.gen(function* () {
138
+ const getNodeSpecPath = Effect.gen(function* () {
139
+ const path = yield* Path.Path;
140
+ const confectDirectory = yield* ConfectDirectory.get;
141
+ return path.join(confectDirectory, "nodeSpec.ts");
142
+ });
143
+ const loadNodeSpec = Effect.gen(function* () {
144
+ const fs = yield* FileSystem.FileSystem;
145
+ const path = yield* Path.Path;
146
+ const nodeSpecPath = yield* getNodeSpecPath;
147
+ if (!(yield* fs.exists(nodeSpecPath))) return Option.none();
148
+ const nodeSpecPathUrl = yield* path.toFileUrl(nodeSpecPath);
149
+ const nodeSpec = (yield* Effect.tryPromise({
150
+ try: () => tsx.tsImport(nodeSpecPathUrl.href, import.meta.url),
151
+ catch: (error) => new SpecImportFailedError({ error })
152
+ })).default;
153
+ if (!Spec.isNodeSpec(nodeSpec)) return yield* new NodeSpecFileDoesNotExportSpecError();
154
+ return Option.some(nodeSpec);
155
+ });
156
+ const esbuildOptions = (entryPoint) => ({
157
+ entryPoints: [entryPoint],
158
+ bundle: true,
159
+ write: false,
160
+ metafile: true,
161
+ platform: "node",
162
+ format: "esm",
163
+ logLevel: "silent",
164
+ external: [
165
+ "@confect/core",
166
+ "@confect/server",
167
+ "effect",
168
+ "@effect/*"
169
+ ],
170
+ plugins: [{
171
+ name: "notify-rebuild",
172
+ setup(build) {
173
+ build.onEnd((result) => {
174
+ if (result.errors.length === 0) build._emit?.();
175
+ else Effect.runPromise(Effect.gen(function* () {
176
+ const formattedMessages = yield* Effect.promise(() => esbuild.formatMessages(result.errors, {
177
+ kind: "error",
178
+ color: true,
179
+ terminalWidth: 80
180
+ }));
181
+ const output = formatBuildErrors(result.errors, formattedMessages);
182
+ yield* Console.error("\n" + output + "\n");
183
+ yield* logFailure("Build errors found");
184
+ }));
185
+ });
186
+ }
187
+ }]
188
+ });
189
+ const createSpecWatcher = (entryPoint) => Stream.asyncPush((emit) => Effect.acquireRelease(Effect.promise(async () => {
190
+ const opts = esbuildOptions(entryPoint);
191
+ const plugin = opts.plugins[0];
192
+ const originalSetup = plugin.setup;
193
+ plugin.setup = (build) => {
194
+ build._emit = () => emit.single();
195
+ return originalSetup(build);
196
+ };
197
+ const ctx = await esbuild.context({
198
+ ...opts,
199
+ plugins: [plugin]
200
+ });
201
+ await ctx.watch();
202
+ return ctx;
203
+ }), (ctx) => Effect.promise(() => ctx.dispose()).pipe(Effect.tap(() => Effect.logDebug("esbuild watcher disposed")))), {
204
+ bufferSize: 1,
205
+ strategy: "sliding"
206
+ });
207
+ const specFileWatcher = (signal, pendingRef, specWatcherRestartQueue) => Effect.forever(Effect.gen(function* () {
208
+ const fs = yield* FileSystem.FileSystem;
158
209
  const specPath = yield* getSpecPath;
159
- yield* pipe(Stream.asyncPush((emit) => Effect.acquireRelease(Effect.promise(async () => {
160
- const ctx = await esbuild.context({
161
- entryPoints: [specPath],
162
- bundle: true,
163
- write: false,
164
- metafile: true,
165
- platform: "node",
166
- format: "esm",
167
- logLevel: "silent",
168
- external: [
169
- "@confect/core",
170
- "@confect/server",
171
- "effect",
172
- "@effect/*"
173
- ],
174
- plugins: [{
175
- name: "notify-rebuild",
176
- setup(build) {
177
- build.onEnd((result) => {
178
- if (result.errors.length === 0) emit.single();
179
- else Effect.runPromise(Effect.gen(function* () {
180
- yield* logFailed("Build errors");
181
- const formattedMessages = yield* Effect.promise(() => esbuild.formatMessages(result.errors, {
182
- kind: "error",
183
- color: true,
184
- terminalWidth: 80
185
- }));
186
- const output = formatBuildErrors(result.errors, formattedMessages);
187
- yield* Console.error("\n" + output + "\n");
188
- }));
189
- });
190
- }
191
- }]
192
- });
193
- await ctx.watch();
194
- return ctx;
195
- }), (ctx) => Effect.promise(() => ctx.dispose()).pipe(Effect.tap(() => Effect.logDebug("esbuild watcher disposed")))), {
196
- bufferSize: 1,
197
- strategy: "sliding"
198
- }), Stream.debounce(Duration.millis(200)), Stream.runForEach(() => Ref.update(pendingRef, (pending) => ({
210
+ const nodeSpecPath = yield* getNodeSpecPath;
211
+ const nodeSpecExists = yield* fs.exists(nodeSpecPath);
212
+ const specWatcher = createSpecWatcher(specPath);
213
+ const nodeSpecWatcher = nodeSpecExists ? createSpecWatcher(nodeSpecPath) : Stream.empty;
214
+ const specChanges = pipe(Stream.merge(specWatcher, nodeSpecWatcher), Stream.map(() => "change"));
215
+ const restartStream = pipe(Stream.fromQueue(specWatcherRestartQueue), Stream.map(() => "restart"));
216
+ yield* pipe(Stream.merge(specChanges, restartStream), Stream.debounce(Duration.millis(200)), Stream.takeUntil((event) => event === "restart"), Stream.runForEach((event) => event === "change" ? Ref.update(pendingRef, (pending) => ({
199
217
  ...pending,
200
218
  specDirty: true
201
- })).pipe(Effect.andThen(Queue.offer(signal, void 0)))));
202
- });
219
+ })).pipe(Effect.andThen(Queue.offer(signal, void 0))) : Effect.void));
220
+ }));
203
221
  const formatBuildError = (error, formattedMessage) => {
204
222
  const lines = String.split(formattedMessage, "\n");
205
223
  const redErrorText = pipe(AnsiDoc.text(error?.text ?? ""), AnsiDoc.annotate(Ansi.red), AnsiDoc.render({ style: "pretty" }));
206
224
  return pipe(pipe(Array.findFirstIndex(lines, (l) => pipe(l, String.trim, String.isNonEmpty)), Option.match({
207
225
  onNone: () => lines,
208
- onSome: (idx) => Array.modify(lines, idx, () => redErrorText)
209
- })), Array.map((l) => pipe(l, String.trim, String.isNonEmpty) ? ` ${l}` : l), Array.join("\n"));
226
+ onSome: (index) => Array.modify(lines, index, () => redErrorText)
227
+ })), Array.join("\n"));
210
228
  };
211
229
  const formatBuildErrors = (errors, formattedMessages) => pipe(formattedMessages, Array.map((message, i) => formatBuildError(errors[i], message)), Array.join(""), String.trimEnd);
212
- var SpecFileDoesNotExportSpecError = class extends Schema.TaggedError("SpecFileDoesNotExportSpecError")("SpecFileDoesNotExportSpecError", {}) {};
213
- var SpecImportFailedError = class extends Schema.TaggedError("SpecImportFailedError")("SpecImportFailedError", { error: Schema.Unknown }) {};
230
+ var SpecFileDoesNotExportSpecError = class extends Schema.TaggedError()("SpecFileDoesNotExportSpecError", {}) {};
231
+ var NodeSpecFileDoesNotExportSpecError = class extends Schema.TaggedError()("NodeSpecFileDoesNotExportSpecError", {}) {};
232
+ var SpecImportFailedError = class extends Schema.TaggedError()("SpecImportFailedError", { error: Schema.Unknown }) {};
214
233
  const syncOptionalFile = (generate, convexFile) => pipe(generate, Effect.andThen(Option.match({
215
- onSome: ({ change, convexFilePath }) => Match.value(change).pipe(Match.when("Unchanged", () => Effect.succeed(Option.none())), Match.whenOr("Added", "Modified", (addedOrModified) => Effect.succeed(Option.some(FileChange.OptionalFile({
216
- change: addedOrModified,
217
- filePath: convexFilePath
218
- })))), Match.exhaustive),
234
+ onSome: ({ change, convexFilePath }) => Match.value(change).pipe(Match.when("Unchanged", () => Effect.void), Match.whenOr("Added", "Modified", (addedOrModified) => logFileChangeIndented(addedOrModified, convexFilePath)), Match.exhaustive),
219
235
  onNone: () => Effect.gen(function* () {
220
236
  const fs = yield* FileSystem.FileSystem;
221
237
  const path = yield* Path.Path;
@@ -223,29 +239,38 @@ const syncOptionalFile = (generate, convexFile) => pipe(generate, Effect.andThen
223
239
  const convexFilePath = path.join(convexDirectory, convexFile);
224
240
  if (yield* fs.exists(convexFilePath)) {
225
241
  yield* fs.remove(convexFilePath);
226
- return Option.some(FileChange.OptionalFile({
227
- change: "Removed",
228
- filePath: convexFilePath
229
- }));
230
- } else return Option.none();
242
+ yield* logFileChangeIndented("Removed", convexFilePath);
243
+ }
231
244
  })
232
245
  })));
233
246
  const optionalConfectFiles = {
234
247
  "http.ts": "httpDirty",
235
248
  "app.ts": "appDirty",
236
249
  "crons.ts": "cronsDirty",
237
- "auth.ts": "authDirty"
250
+ "auth.ts": "authDirty",
251
+ "nodeSpec.ts": "specDirty",
252
+ "nodeImpl.ts": "nodeImplDirty"
238
253
  };
239
- const confectDirectoryWatcher = (signal, pendingRef) => Effect.gen(function* () {
254
+ const confectDirectoryWatcher = (signal, pendingRef, specWatcherRestartQueue) => Effect.gen(function* () {
240
255
  const fs = yield* FileSystem.FileSystem;
256
+ const path = yield* Path.Path;
241
257
  const confectDirectory = yield* ConfectDirectory.get;
242
- yield* pipe(fs.watch(confectDirectory), Stream.runForEach((event) => pipe(Option.fromNullable(optionalConfectFiles[event.path]), Option.match({
243
- onNone: () => Effect.void,
244
- onSome: (pendingKey) => pipe(pendingRef, Ref.update((pending) => ({
245
- ...pending,
246
- [pendingKey]: true
247
- })), Effect.andThen(Queue.offer(signal, void 0)))
248
- }))));
258
+ yield* pipe(fs.watch(confectDirectory), Stream.runForEach((event) => {
259
+ const basename = path.basename(event.path);
260
+ const pendingKey = optionalConfectFiles[basename];
261
+ if (pendingKey !== void 0) return pipe(pendingRef, Ref.update((pending) => {
262
+ const next = {
263
+ ...pending,
264
+ [pendingKey]: true
265
+ };
266
+ if (basename === "nodeImpl.ts") return {
267
+ ...next,
268
+ specDirty: true
269
+ };
270
+ return next;
271
+ }), Effect.andThen(Queue.offer(signal, void 0)), Effect.andThen(basename === "nodeSpec.ts" ? Queue.offer(specWatcherRestartQueue, void 0) : Effect.void));
272
+ return Effect.void;
273
+ }));
249
274
  });
250
275
 
251
276
  //#endregion