@confect/cli 8.0.0 → 9.0.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -1
- package/dist/BuildError.mjs +101 -0
- package/dist/BuildError.mjs.map +1 -0
- package/dist/Bundler.mjs +91 -0
- package/dist/Bundler.mjs.map +1 -0
- package/dist/CodeBlockWriter.mjs +55 -0
- package/dist/CodeBlockWriter.mjs.map +1 -0
- package/dist/CodegenError.mjs +101 -0
- package/dist/CodegenError.mjs.map +1 -0
- package/dist/FunctionPaths.mjs +1 -1
- package/dist/LeafModule.mjs +170 -0
- package/dist/LeafModule.mjs.map +1 -0
- package/dist/SpecAssemblyNode.mjs +33 -0
- package/dist/SpecAssemblyNode.mjs.map +1 -0
- package/dist/confect/codegen.mjs +292 -72
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/confect/dev.mjs +234 -180
- package/dist/confect/dev.mjs.map +1 -1
- package/dist/log.mjs +13 -1
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/templates.mjs +69 -72
- package/dist/templates.mjs.map +1 -1
- package/dist/utils.mjs +77 -73
- package/dist/utils.mjs.map +1 -1
- package/package.json +4 -3
- package/src/BuildError.ts +210 -0
- package/src/Bundler.ts +144 -0
- package/src/CodeBlockWriter.ts +65 -0
- package/src/CodegenError.ts +376 -0
- package/src/LeafModule.ts +317 -0
- package/src/SpecAssemblyNode.ts +82 -0
- package/src/confect/codegen.ts +511 -141
- package/src/confect/dev.ts +556 -435
- package/src/log.ts +21 -0
- package/src/templates.ts +146 -109
- package/src/utils.ts +118 -93
package/src/confect/codegen.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type GroupSpec, Spec } from "@confect/core";
|
|
2
|
+
import * as DatabaseSchema from "@confect/server/DatabaseSchema";
|
|
2
3
|
import { Command } from "@effect/cli";
|
|
3
4
|
import { FileSystem, Path } from "@effect/platform";
|
|
4
|
-
import { Effect, Match, Option } from "effect";
|
|
5
|
+
import { Array, Effect, Either, HashSet, Match, Option, Ref } from "effect";
|
|
6
|
+
import { fromBundlerError } from "../BuildError";
|
|
7
|
+
import * as CodegenError from "../CodegenError";
|
|
8
|
+
import {
|
|
9
|
+
MissingImplFileError,
|
|
10
|
+
MissingSchemaFileError,
|
|
11
|
+
MissingSpecFileError,
|
|
12
|
+
ParentChildNameCollisionError,
|
|
13
|
+
SchemaInvalidDefaultExportError,
|
|
14
|
+
} from "../CodegenError";
|
|
15
|
+
import { ConfectDirectory } from "../ConfectDirectory";
|
|
16
|
+
import { ConvexDirectory } from "../ConvexDirectory";
|
|
17
|
+
import * as FunctionPaths from "../FunctionPaths";
|
|
5
18
|
import {
|
|
6
19
|
logFileAdded,
|
|
7
20
|
logFileModified,
|
|
@@ -9,66 +22,97 @@ import {
|
|
|
9
22
|
logPending,
|
|
10
23
|
logSuccess,
|
|
11
24
|
} from "../log";
|
|
12
|
-
import {
|
|
13
|
-
|
|
25
|
+
import {
|
|
26
|
+
discoverLeafImplFiles,
|
|
27
|
+
discoverLeafSpecFiles,
|
|
28
|
+
implPathForSpec,
|
|
29
|
+
registeredFunctionsRelativePath,
|
|
30
|
+
specPathForImpl,
|
|
31
|
+
toLeafModule,
|
|
32
|
+
toNodeRegistryLeaf,
|
|
33
|
+
validateImpl,
|
|
34
|
+
validateSpec,
|
|
35
|
+
type LeafModule,
|
|
36
|
+
} from "../LeafModule";
|
|
37
|
+
import {
|
|
38
|
+
assemblyNodesFromLeaves,
|
|
39
|
+
partitionByRuntime,
|
|
40
|
+
type SpecAssemblyNode,
|
|
41
|
+
} from "../SpecAssemblyNode";
|
|
14
42
|
import * as templates from "../templates";
|
|
43
|
+
import * as Bundler from "../Bundler";
|
|
15
44
|
import {
|
|
16
|
-
bundleAndImport,
|
|
17
45
|
generateAuthConfig,
|
|
18
46
|
generateCrons,
|
|
19
47
|
generateFunctions,
|
|
20
48
|
generateHttp,
|
|
21
49
|
removePathExtension,
|
|
50
|
+
removePathIfExists,
|
|
51
|
+
toModuleImportPath,
|
|
52
|
+
touchConvexSchema,
|
|
22
53
|
writeFileStringAndLog,
|
|
54
|
+
WriteTracker,
|
|
23
55
|
} from "../utils";
|
|
24
56
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const nodeSpec = nodeSpecModule.default;
|
|
41
|
-
|
|
42
|
-
if (!Spec.isNodeSpec(nodeSpec)) {
|
|
43
|
-
return yield* Effect.die("nodeSpec.ts does not export a valid Node Spec");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return Option.some(nodeSpec);
|
|
47
|
-
});
|
|
57
|
+
const GENERATED_SPEC_PATH = "_generated/spec.ts";
|
|
58
|
+
const GENERATED_NODE_SPEC_PATH = "_generated/nodeSpec.ts";
|
|
59
|
+
|
|
60
|
+
const LEGACY_PATHS = [
|
|
61
|
+
"spec.ts",
|
|
62
|
+
"nodeSpec.ts",
|
|
63
|
+
"impl.ts",
|
|
64
|
+
"nodeImpl.ts",
|
|
65
|
+
"notesAndRandom.impl.ts",
|
|
66
|
+
"groups.impl.ts",
|
|
67
|
+
"_generated/registeredFunctions.ts",
|
|
68
|
+
"_generated/nodeRegisteredFunctions.ts",
|
|
69
|
+
"_generated/impl.ts",
|
|
70
|
+
"_generated/nodeImpl.ts",
|
|
71
|
+
];
|
|
48
72
|
|
|
49
73
|
export const codegen = Command.make("codegen", {}, () =>
|
|
50
74
|
Effect.gen(function* () {
|
|
51
75
|
yield* logPending("Performing initial sync…");
|
|
52
|
-
yield* codegenHandler
|
|
53
|
-
|
|
76
|
+
yield* codegenHandler.pipe(
|
|
77
|
+
Effect.asVoid,
|
|
78
|
+
Effect.tap(() => logSuccess("Generated files are up-to-date")),
|
|
79
|
+
CodegenError.tapAndLog,
|
|
80
|
+
);
|
|
54
81
|
}),
|
|
55
82
|
).pipe(
|
|
56
83
|
Command.withDescription(
|
|
57
|
-
"Generate `confect/_generated` files and the contents of the `convex` directory (except `tsconfig.json`)",
|
|
84
|
+
"Generate `confect/_generated` files and the contents of the `convex` directory (except `convex.config.ts` and `tsconfig.json`)",
|
|
58
85
|
),
|
|
59
86
|
);
|
|
60
87
|
|
|
61
88
|
export const codegenHandler = Effect.gen(function* () {
|
|
89
|
+
const tracker = yield* Ref.make(false);
|
|
90
|
+
|
|
91
|
+
const functionPaths = yield* runCodegen.pipe(
|
|
92
|
+
Effect.provideService(WriteTracker, tracker),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const anyWritesHappened = yield* Ref.get(tracker);
|
|
96
|
+
return { functionPaths, anyWritesHappened };
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const runCodegen = Effect.gen(function* () {
|
|
62
100
|
yield* generateConfectGeneratedDirectory;
|
|
101
|
+
// Validate schema first so its missing-file / invalid-default-export
|
|
102
|
+
// diagnostics surface ahead of impl bundling, which transitively depends
|
|
103
|
+
// on schema via `_generated/api.ts` and would otherwise blow up with a
|
|
104
|
+
// less actionable bundler error.
|
|
105
|
+
yield* validateSchema;
|
|
106
|
+
const { leaves, groupSpecsByRelativePath } =
|
|
107
|
+
yield* loadAndValidateLeafModules;
|
|
108
|
+
yield* removeLegacyFiles;
|
|
109
|
+
yield* validateNoParentChildNameCollisions(leaves, groupSpecsByRelativePath);
|
|
110
|
+
yield* generateAssembledSpecs(leaves);
|
|
111
|
+
yield* validateImplModules(leaves);
|
|
112
|
+
yield* generateGroupRegisteredFunctions(leaves);
|
|
113
|
+
yield* removeObsoleteRegisteredFunctions(leaves);
|
|
63
114
|
yield* Effect.all(
|
|
64
|
-
[
|
|
65
|
-
generateApi,
|
|
66
|
-
generateRefs,
|
|
67
|
-
generateRegisteredFunctions,
|
|
68
|
-
generateNodeApi,
|
|
69
|
-
generateNodeRegisteredFunctions,
|
|
70
|
-
generateServices,
|
|
71
|
-
],
|
|
115
|
+
[generateApi, generateRefs, generateNodeApi, generateServices],
|
|
72
116
|
{ concurrency: "unbounded" },
|
|
73
117
|
);
|
|
74
118
|
const [functionPaths] = yield* Effect.all(
|
|
@@ -81,6 +125,7 @@ export const codegenHandler = Effect.gen(function* () {
|
|
|
81
125
|
],
|
|
82
126
|
{ concurrency: "unbounded" },
|
|
83
127
|
);
|
|
128
|
+
yield* touchConvexSchema;
|
|
84
129
|
return functionPaths;
|
|
85
130
|
});
|
|
86
131
|
|
|
@@ -97,6 +142,390 @@ const generateConfectGeneratedDirectory = Effect.gen(function* () {
|
|
|
97
142
|
}
|
|
98
143
|
});
|
|
99
144
|
|
|
145
|
+
const loadAndValidateLeafModules = Effect.gen(function* () {
|
|
146
|
+
const fs = yield* FileSystem.FileSystem;
|
|
147
|
+
const path = yield* Path.Path;
|
|
148
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
149
|
+
const specFiles = yield* discoverLeafSpecFiles;
|
|
150
|
+
|
|
151
|
+
const results = yield* Effect.forEach(specFiles, (specRelativePath) =>
|
|
152
|
+
Effect.gen(function* () {
|
|
153
|
+
const leaf = yield* toLeafModule(specRelativePath);
|
|
154
|
+
const groupSpec = yield* validateSpec(leaf);
|
|
155
|
+
|
|
156
|
+
const implRelativePath = yield* implPathForSpec(specRelativePath);
|
|
157
|
+
const implAbsolutePath = path.join(confectDirectory, implRelativePath);
|
|
158
|
+
if (!(yield* fs.exists(implAbsolutePath))) {
|
|
159
|
+
return yield* new MissingImplFileError({
|
|
160
|
+
specPath: specRelativePath,
|
|
161
|
+
expectedImplPath: implRelativePath,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { leaf, groupSpec };
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
yield* validateOrphanImpls(specFiles);
|
|
170
|
+
|
|
171
|
+
const leaves = Array.map(results, ({ leaf }) => leaf);
|
|
172
|
+
const groupSpecsByRelativePath = new Map(
|
|
173
|
+
Array.map(results, ({ leaf, groupSpec }) => [leaf.relativePath, groupSpec]),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return { leaves, groupSpecsByRelativePath };
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Walk the assembly tree and fail with a {@link ParentChildNameCollisionError}
|
|
181
|
+
* when a parent leaf declares a function or subgroup whose name matches a
|
|
182
|
+
* sibling subdirectory spec's segment. Without this check the colliding
|
|
183
|
+
* descendant would overwrite the parent's entry in the assembled
|
|
184
|
+
* `GroupSpec.groups` map at runtime, surfacing as a confusing
|
|
185
|
+
* `Refs.make` error rather than a codegen-time diagnostic.
|
|
186
|
+
*/
|
|
187
|
+
export const validateNoParentChildNameCollisions = (
|
|
188
|
+
leaves: ReadonlyArray<LeafModule>,
|
|
189
|
+
groupSpecsByRelativePath: ReadonlyMap<string, GroupSpec.AnyWithProps>,
|
|
190
|
+
) =>
|
|
191
|
+
Effect.gen(function* () {
|
|
192
|
+
const { convex, node } = partitionByRuntime(leaves);
|
|
193
|
+
const convexNodes = assemblyNodesFromLeaves(convex);
|
|
194
|
+
const nodeNodes = assemblyNodesFromLeaves(
|
|
195
|
+
Array.map(node, toNodeRegistryLeaf),
|
|
196
|
+
);
|
|
197
|
+
yield* Effect.forEach(convexNodes, (n) =>
|
|
198
|
+
checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath),
|
|
199
|
+
);
|
|
200
|
+
yield* Effect.forEach(nodeNodes, (n) =>
|
|
201
|
+
checkAssemblyNodeForCollisions(n, groupSpecsByRelativePath),
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const checkAssemblyNodeForCollisions = (
|
|
206
|
+
node: SpecAssemblyNode,
|
|
207
|
+
groupSpecsByRelativePath: ReadonlyMap<string, GroupSpec.AnyWithProps>,
|
|
208
|
+
): Effect.Effect<void, ParentChildNameCollisionError> =>
|
|
209
|
+
Effect.gen(function* () {
|
|
210
|
+
yield* Option.match(node.importBinding, {
|
|
211
|
+
onNone: () => Effect.void,
|
|
212
|
+
onSome: (binding) =>
|
|
213
|
+
Effect.gen(function* () {
|
|
214
|
+
if (node.children.length === 0) return;
|
|
215
|
+
const parentRelativePath = bindingToRelativeSpecPath(
|
|
216
|
+
binding.importPath,
|
|
217
|
+
);
|
|
218
|
+
const parentGroupSpec =
|
|
219
|
+
groupSpecsByRelativePath.get(parentRelativePath);
|
|
220
|
+
if (parentGroupSpec === undefined) return;
|
|
221
|
+
yield* Effect.forEach(node.children, (child) => {
|
|
222
|
+
if (
|
|
223
|
+
Object.prototype.hasOwnProperty.call(
|
|
224
|
+
parentGroupSpec.functions,
|
|
225
|
+
child.segment,
|
|
226
|
+
)
|
|
227
|
+
) {
|
|
228
|
+
return Effect.fail(
|
|
229
|
+
new ParentChildNameCollisionError({
|
|
230
|
+
parentSpecPath: parentRelativePath,
|
|
231
|
+
childSpecPath: childRepresentativeSpecPath(child),
|
|
232
|
+
collisionName: child.segment,
|
|
233
|
+
collisionKind: "function",
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
if (
|
|
238
|
+
Object.prototype.hasOwnProperty.call(
|
|
239
|
+
parentGroupSpec.groups,
|
|
240
|
+
child.segment,
|
|
241
|
+
)
|
|
242
|
+
) {
|
|
243
|
+
return Effect.fail(
|
|
244
|
+
new ParentChildNameCollisionError({
|
|
245
|
+
parentSpecPath: parentRelativePath,
|
|
246
|
+
childSpecPath: childRepresentativeSpecPath(child),
|
|
247
|
+
collisionName: child.segment,
|
|
248
|
+
collisionKind: "group",
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return Effect.void;
|
|
253
|
+
});
|
|
254
|
+
}),
|
|
255
|
+
});
|
|
256
|
+
yield* Effect.forEach(node.children, (child) =>
|
|
257
|
+
checkAssemblyNodeForCollisions(child, groupSpecsByRelativePath),
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* `LeafModule.specImportPath` is the import path used from inside the
|
|
263
|
+
* generated `_generated/spec.ts` (e.g. `"../notes.spec"`). Strip the
|
|
264
|
+
* `../` prefix and re-add the `.ts` extension to recover the leaf's
|
|
265
|
+
* confect-relative spec path used as the key in
|
|
266
|
+
* `groupSpecsByRelativePath`.
|
|
267
|
+
*/
|
|
268
|
+
const bindingToRelativeSpecPath = (importPath: string): string => {
|
|
269
|
+
const withoutDotDot = importPath.startsWith("../")
|
|
270
|
+
? importPath.slice(3)
|
|
271
|
+
: importPath;
|
|
272
|
+
return `${withoutDotDot}.ts`;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* A child assembly node may itself be a parent without a leaf (when the
|
|
277
|
+
* actual leaves live only in deeper subdirectories). In that case we
|
|
278
|
+
* surface the first descendant leaf as a representative path so the
|
|
279
|
+
* error message points at something the user actually wrote.
|
|
280
|
+
*/
|
|
281
|
+
const childRepresentativeSpecPath = (node: SpecAssemblyNode): string => {
|
|
282
|
+
if (Option.isSome(node.importBinding)) {
|
|
283
|
+
return bindingToRelativeSpecPath(node.importBinding.value.importPath);
|
|
284
|
+
}
|
|
285
|
+
for (const child of node.children) {
|
|
286
|
+
return childRepresentativeSpecPath(child);
|
|
287
|
+
}
|
|
288
|
+
return node.segment;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const validateOrphanImpls = (specFiles: ReadonlyArray<string>) =>
|
|
292
|
+
Effect.gen(function* () {
|
|
293
|
+
const fs = yield* FileSystem.FileSystem;
|
|
294
|
+
const path = yield* Path.Path;
|
|
295
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
296
|
+
const implFiles = yield* discoverLeafImplFiles;
|
|
297
|
+
const specPaths = new Set(specFiles);
|
|
298
|
+
|
|
299
|
+
yield* Effect.forEach(implFiles, (implRelativePath) =>
|
|
300
|
+
Effect.gen(function* () {
|
|
301
|
+
const specRelativePath = yield* specPathForImpl(implRelativePath);
|
|
302
|
+
if (specPaths.has(specRelativePath)) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const specAbsolutePath = path.join(confectDirectory, specRelativePath);
|
|
307
|
+
if (!(yield* fs.exists(specAbsolutePath))) {
|
|
308
|
+
return yield* new MissingSpecFileError({
|
|
309
|
+
implPath: implRelativePath,
|
|
310
|
+
expectedSpecPath: specRelativePath,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const removeLegacyFiles = Effect.gen(function* () {
|
|
318
|
+
const fs = yield* FileSystem.FileSystem;
|
|
319
|
+
const path = yield* Path.Path;
|
|
320
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
321
|
+
|
|
322
|
+
yield* Effect.forEach(LEGACY_PATHS, (relativePath) =>
|
|
323
|
+
Effect.gen(function* () {
|
|
324
|
+
const absolutePath = path.join(confectDirectory, relativePath);
|
|
325
|
+
if (yield* fs.exists(absolutePath)) {
|
|
326
|
+
yield* removePathIfExists(absolutePath);
|
|
327
|
+
yield* logFileRemoved(absolutePath);
|
|
328
|
+
}
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const generateAssembledSpecs = (leaves: ReadonlyArray<LeafModule>) =>
|
|
334
|
+
Effect.gen(function* () {
|
|
335
|
+
const path = yield* Path.Path;
|
|
336
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
337
|
+
const { convex, node } = partitionByRuntime(leaves);
|
|
338
|
+
|
|
339
|
+
if (convex.length > 0) {
|
|
340
|
+
const nodes = assemblyNodesFromLeaves(convex);
|
|
341
|
+
const specContents = yield* templates.assembledSpec({
|
|
342
|
+
nodes,
|
|
343
|
+
runtime: "Convex",
|
|
344
|
+
});
|
|
345
|
+
yield* writeFileStringAndLog(
|
|
346
|
+
path.join(confectDirectory, GENERATED_SPEC_PATH),
|
|
347
|
+
specContents,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (node.length > 0) {
|
|
352
|
+
const nodes = assemblyNodesFromLeaves(
|
|
353
|
+
Array.map(node, toNodeRegistryLeaf),
|
|
354
|
+
);
|
|
355
|
+
const nodeSpecContents = yield* templates.assembledSpec({
|
|
356
|
+
nodes,
|
|
357
|
+
runtime: "Node",
|
|
358
|
+
});
|
|
359
|
+
yield* writeFileStringAndLog(
|
|
360
|
+
path.join(confectDirectory, GENERATED_NODE_SPEC_PATH),
|
|
361
|
+
nodeSpecContents,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const validateImplModules = (leaves: ReadonlyArray<LeafModule>) =>
|
|
367
|
+
Effect.forEach(leaves, validateImpl);
|
|
368
|
+
|
|
369
|
+
const generateGroupRegisteredFunctions = (leaves: ReadonlyArray<LeafModule>) =>
|
|
370
|
+
Effect.gen(function* () {
|
|
371
|
+
const path = yield* Path.Path;
|
|
372
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
373
|
+
|
|
374
|
+
yield* Effect.forEach(leaves, (leaf) =>
|
|
375
|
+
Effect.gen(function* () {
|
|
376
|
+
const registryRelativePath =
|
|
377
|
+
yield* registeredFunctionsRelativePath(leaf);
|
|
378
|
+
const registryPath = path.join(
|
|
379
|
+
confectDirectory,
|
|
380
|
+
"_generated",
|
|
381
|
+
registryRelativePath,
|
|
382
|
+
);
|
|
383
|
+
const registryDir = path.dirname(registryPath);
|
|
384
|
+
const fs = yield* FileSystem.FileSystem;
|
|
385
|
+
if (!(yield* fs.exists(registryDir))) {
|
|
386
|
+
yield* fs.makeDirectory(registryDir, { recursive: true });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const implRelativePath = yield* implPathForSpec(leaf.relativePath);
|
|
390
|
+
const apiFileName = leaf.runtime === "Node" ? "nodeApi.ts" : "api.ts";
|
|
391
|
+
const apiImportPath = yield* toModuleImportPath(
|
|
392
|
+
path.relative(
|
|
393
|
+
path.dirname(registryPath),
|
|
394
|
+
path.join(confectDirectory, "_generated", apiFileName),
|
|
395
|
+
),
|
|
396
|
+
);
|
|
397
|
+
const implImportPath = yield* toModuleImportPath(
|
|
398
|
+
path.relative(
|
|
399
|
+
path.dirname(registryPath),
|
|
400
|
+
path.join(confectDirectory, implRelativePath),
|
|
401
|
+
),
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
const contents = yield* templates.registeredFunctionsForGroup({
|
|
405
|
+
apiImportPath,
|
|
406
|
+
groupPathDot: leaf.registryGroupPathDot,
|
|
407
|
+
implImportPath,
|
|
408
|
+
layerExportName: leaf.exportName,
|
|
409
|
+
useNode: leaf.runtime === "Node",
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
yield* writeFileStringAndLog(registryPath, contents);
|
|
413
|
+
}),
|
|
414
|
+
);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const removeObsoleteRegisteredFunctions = (leaves: ReadonlyArray<LeafModule>) =>
|
|
418
|
+
Effect.gen(function* () {
|
|
419
|
+
const fs = yield* FileSystem.FileSystem;
|
|
420
|
+
const path = yield* Path.Path;
|
|
421
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
422
|
+
const registryRoot = path.join(
|
|
423
|
+
confectDirectory,
|
|
424
|
+
"_generated",
|
|
425
|
+
"registeredFunctions",
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
if (!(yield* fs.exists(registryRoot))) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const expected = new Set(
|
|
433
|
+
yield* Effect.forEach(leaves, (leaf) =>
|
|
434
|
+
registeredFunctionsRelativePath(leaf),
|
|
435
|
+
),
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const existing = yield* fs.readDirectory(registryRoot, { recursive: true });
|
|
439
|
+
yield* Effect.forEach(existing, (relativePath) => {
|
|
440
|
+
if (path.extname(relativePath) !== ".ts") {
|
|
441
|
+
return Effect.void;
|
|
442
|
+
}
|
|
443
|
+
const normalized = path.join("registeredFunctions", relativePath);
|
|
444
|
+
if (!expected.has(normalized)) {
|
|
445
|
+
return Effect.gen(function* () {
|
|
446
|
+
const absolutePath = path.join(registryRoot, relativePath);
|
|
447
|
+
if (yield* fs.exists(absolutePath)) {
|
|
448
|
+
yield* removePathIfExists(absolutePath);
|
|
449
|
+
yield* logFileRemoved(absolutePath);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
return Effect.void;
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const getGeneratedSpecPath = Effect.gen(function* () {
|
|
458
|
+
const path = yield* Path.Path;
|
|
459
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
460
|
+
return path.join(confectDirectory, GENERATED_SPEC_PATH);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const getGeneratedNodeSpecPath = Effect.gen(function* () {
|
|
464
|
+
const path = yield* Path.Path;
|
|
465
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
466
|
+
return path.join(confectDirectory, GENERATED_NODE_SPEC_PATH);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const loadGeneratedSpec = Effect.gen(function* () {
|
|
470
|
+
const specPath = yield* getGeneratedSpecPath;
|
|
471
|
+
const { module: specModule } = yield* Bundler.bundle(specPath);
|
|
472
|
+
const spec = specModule.default;
|
|
473
|
+
|
|
474
|
+
if (!Spec.isConvexSpec(spec)) {
|
|
475
|
+
return yield* Effect.dieMessage(
|
|
476
|
+
"_generated/spec.ts does not export a valid Convex Spec",
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return spec;
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const loadGeneratedNodeSpec = Effect.gen(function* () {
|
|
484
|
+
const fs = yield* FileSystem.FileSystem;
|
|
485
|
+
const nodeSpecPath = yield* getGeneratedNodeSpecPath;
|
|
486
|
+
|
|
487
|
+
if (!(yield* fs.exists(nodeSpecPath))) {
|
|
488
|
+
return Option.none<Spec.AnyWithPropsWithRuntime<"Node">>();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const { module: nodeSpecModule } = yield* Bundler.bundle(nodeSpecPath);
|
|
492
|
+
const nodeSpec = nodeSpecModule.default;
|
|
493
|
+
|
|
494
|
+
if (!Spec.isNodeSpec(nodeSpec)) {
|
|
495
|
+
return yield* Effect.dieMessage(
|
|
496
|
+
"_generated/nodeSpec.ts does not export a valid Node Spec",
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return Option.some(nodeSpec);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const emptyFunctionPaths = FunctionPaths.FunctionPaths.make(HashSet.empty());
|
|
504
|
+
|
|
505
|
+
export const loadPreviousFunctionPaths = Effect.gen(function* () {
|
|
506
|
+
const fs = yield* FileSystem.FileSystem;
|
|
507
|
+
const specPath = yield* getGeneratedSpecPath;
|
|
508
|
+
|
|
509
|
+
if (!(yield* fs.exists(specPath))) {
|
|
510
|
+
return emptyFunctionPaths;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const specEither = yield* loadGeneratedSpec.pipe(Effect.either);
|
|
514
|
+
|
|
515
|
+
return yield* Either.match(specEither, {
|
|
516
|
+
onLeft: () => Effect.succeed(emptyFunctionPaths),
|
|
517
|
+
onRight: (spec) =>
|
|
518
|
+
Effect.gen(function* () {
|
|
519
|
+
const nodeSpecOption = yield* loadGeneratedNodeSpec;
|
|
520
|
+
const mergedSpec = Option.match(nodeSpecOption, {
|
|
521
|
+
onNone: () => spec,
|
|
522
|
+
onSome: (nodeSpec) => Spec.merge(spec, nodeSpec),
|
|
523
|
+
});
|
|
524
|
+
return FunctionPaths.make(mergedSpec);
|
|
525
|
+
}),
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
100
529
|
const generateApi = Effect.gen(function* () {
|
|
101
530
|
const path = yield* Path.Path;
|
|
102
531
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
@@ -104,12 +533,12 @@ const generateApi = Effect.gen(function* () {
|
|
|
104
533
|
const apiPath = path.join(confectDirectory, "_generated", "api.ts");
|
|
105
534
|
const apiDir = path.dirname(apiPath);
|
|
106
535
|
|
|
107
|
-
const schemaImportPath = yield*
|
|
536
|
+
const schemaImportPath = yield* toModuleImportPath(
|
|
108
537
|
path.relative(apiDir, path.join(confectDirectory, "schema.ts")),
|
|
109
538
|
);
|
|
110
539
|
|
|
111
|
-
const specImportPath = yield*
|
|
112
|
-
path.relative(apiDir, path.join(confectDirectory,
|
|
540
|
+
const specImportPath = yield* toModuleImportPath(
|
|
541
|
+
path.relative(apiDir, path.join(confectDirectory, GENERATED_SPEC_PATH)),
|
|
113
542
|
);
|
|
114
543
|
|
|
115
544
|
const apiContents = yield* templates.api({
|
|
@@ -125,12 +554,12 @@ export const generateNodeApi = Effect.gen(function* () {
|
|
|
125
554
|
const path = yield* Path.Path;
|
|
126
555
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
127
556
|
|
|
128
|
-
const nodeSpecPath = yield*
|
|
557
|
+
const nodeSpecPath = yield* getGeneratedNodeSpecPath;
|
|
129
558
|
const nodeApiPath = path.join(confectDirectory, "_generated", "nodeApi.ts");
|
|
130
559
|
|
|
131
560
|
if (!(yield* fs.exists(nodeSpecPath))) {
|
|
132
561
|
if (yield* fs.exists(nodeApiPath)) {
|
|
133
|
-
yield*
|
|
562
|
+
yield* removePathIfExists(nodeApiPath);
|
|
134
563
|
yield* logFileRemoved(nodeApiPath);
|
|
135
564
|
}
|
|
136
565
|
return;
|
|
@@ -138,11 +567,11 @@ export const generateNodeApi = Effect.gen(function* () {
|
|
|
138
567
|
|
|
139
568
|
const nodeApiDir = path.dirname(nodeApiPath);
|
|
140
569
|
|
|
141
|
-
const schemaImportPath = yield*
|
|
570
|
+
const schemaImportPath = yield* toModuleImportPath(
|
|
142
571
|
path.relative(nodeApiDir, path.join(confectDirectory, "schema.ts")),
|
|
143
572
|
);
|
|
144
573
|
|
|
145
|
-
const nodeSpecImportPath = yield*
|
|
574
|
+
const nodeSpecImportPath = yield* toModuleImportPath(
|
|
146
575
|
path.relative(nodeApiDir, nodeSpecPath),
|
|
147
576
|
);
|
|
148
577
|
|
|
@@ -155,47 +584,52 @@ export const generateNodeApi = Effect.gen(function* () {
|
|
|
155
584
|
});
|
|
156
585
|
|
|
157
586
|
const generateFunctionModules = Effect.gen(function* () {
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
161
|
-
|
|
162
|
-
const specPath = path.join(confectDirectory, "spec.ts");
|
|
163
|
-
|
|
164
|
-
const specModule = yield* bundleAndImport(specPath);
|
|
165
|
-
const spec = specModule.default;
|
|
166
|
-
|
|
167
|
-
if (!Spec.isConvexSpec(spec)) {
|
|
168
|
-
return yield* Effect.die("spec.ts does not export a valid Convex Spec");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const nodeImplPath = path.join(confectDirectory, "nodeImpl.ts");
|
|
172
|
-
const nodeImplExists = yield* fs.exists(nodeImplPath);
|
|
173
|
-
const nodeSpecOption = yield* loadNodeSpec;
|
|
587
|
+
const spec = yield* loadGeneratedSpec;
|
|
588
|
+
const nodeSpecOption = yield* loadGeneratedNodeSpec;
|
|
174
589
|
|
|
175
590
|
const mergedSpec = Option.match(nodeSpecOption, {
|
|
176
591
|
onNone: () => spec,
|
|
177
|
-
onSome: (nodeSpec) =>
|
|
592
|
+
onSome: (nodeSpec) => Spec.merge(spec, nodeSpec),
|
|
178
593
|
});
|
|
179
594
|
|
|
180
595
|
return yield* generateFunctions(mergedSpec);
|
|
181
596
|
});
|
|
182
597
|
|
|
183
|
-
const
|
|
598
|
+
export const validateSchema = Effect.gen(function* () {
|
|
599
|
+
const fs = yield* FileSystem.FileSystem;
|
|
184
600
|
const path = yield* Path.Path;
|
|
185
601
|
const confectDirectory = yield* ConfectDirectory.get;
|
|
186
|
-
const convexDirectory = yield* ConvexDirectory.get;
|
|
187
|
-
|
|
188
602
|
const confectSchemaPath = path.join(confectDirectory, "schema.ts");
|
|
189
603
|
|
|
190
|
-
yield*
|
|
191
|
-
|
|
604
|
+
if (!(yield* fs.exists(confectSchemaPath))) {
|
|
605
|
+
return yield* new MissingSchemaFileError({ schemaPath: "schema.ts" });
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
yield* Bundler.bundle(confectSchemaPath).pipe(
|
|
609
|
+
Effect.mapError((error) => fromBundlerError("schema.ts", error)),
|
|
610
|
+
Effect.andThen(({ module: schemaModule }) => {
|
|
192
611
|
const defaultExport = schemaModule.default;
|
|
193
612
|
|
|
194
613
|
return DatabaseSchema.isDatabaseSchema(defaultExport)
|
|
195
614
|
? Effect.succeed(defaultExport)
|
|
196
|
-
: Effect.
|
|
615
|
+
: Effect.fail(
|
|
616
|
+
new SchemaInvalidDefaultExportError({
|
|
617
|
+
schemaPath: "schema.ts",
|
|
618
|
+
}),
|
|
619
|
+
);
|
|
197
620
|
}),
|
|
198
621
|
);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const generateSchema = Effect.gen(function* () {
|
|
625
|
+
const path = yield* Path.Path;
|
|
626
|
+
const confectDirectory = yield* ConfectDirectory.get;
|
|
627
|
+
const convexDirectory = yield* ConvexDirectory.get;
|
|
628
|
+
|
|
629
|
+
const confectSchemaPath = path.join(confectDirectory, "schema.ts");
|
|
630
|
+
|
|
631
|
+
// `validateSchema` runs once at the top of `runCodegen`; no need to
|
|
632
|
+
// bundle the schema again here.
|
|
199
633
|
|
|
200
634
|
const convexSchemaPath = path.join(convexDirectory, "schema.ts");
|
|
201
635
|
|
|
@@ -230,72 +664,6 @@ const generateServices = Effect.gen(function* () {
|
|
|
230
664
|
yield* writeFileStringAndLog(servicesPath, servicesContentsString);
|
|
231
665
|
});
|
|
232
666
|
|
|
233
|
-
const generateRegisteredFunctions = Effect.gen(function* () {
|
|
234
|
-
const path = yield* Path.Path;
|
|
235
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
236
|
-
|
|
237
|
-
const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
|
|
238
|
-
|
|
239
|
-
const registeredFunctionsPath = path.join(
|
|
240
|
-
confectGeneratedDirectory,
|
|
241
|
-
"registeredFunctions.ts",
|
|
242
|
-
);
|
|
243
|
-
const implImportPath = yield* removePathExtension(
|
|
244
|
-
path.relative(
|
|
245
|
-
path.dirname(registeredFunctionsPath),
|
|
246
|
-
path.join(confectDirectory, "impl.ts"),
|
|
247
|
-
),
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
const registeredFunctionsContents = yield* templates.registeredFunctions({
|
|
251
|
-
implImportPath,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
yield* writeFileStringAndLog(
|
|
255
|
-
registeredFunctionsPath,
|
|
256
|
-
registeredFunctionsContents,
|
|
257
|
-
);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
export const generateNodeRegisteredFunctions = Effect.gen(function* () {
|
|
261
|
-
const fs = yield* FileSystem.FileSystem;
|
|
262
|
-
const path = yield* Path.Path;
|
|
263
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
264
|
-
|
|
265
|
-
const nodeImplPath = path.join(confectDirectory, "nodeImpl.ts");
|
|
266
|
-
const nodeSpecPath = yield* getNodeSpecPath;
|
|
267
|
-
const nodeRegisteredFunctionsPath = path.join(
|
|
268
|
-
confectDirectory,
|
|
269
|
-
"_generated",
|
|
270
|
-
"nodeRegisteredFunctions.ts",
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
const nodeImplExists = yield* fs.exists(nodeImplPath);
|
|
274
|
-
const nodeSpecExists = yield* fs.exists(nodeSpecPath);
|
|
275
|
-
|
|
276
|
-
if (!nodeImplExists || !nodeSpecExists) {
|
|
277
|
-
if (yield* fs.exists(nodeRegisteredFunctionsPath)) {
|
|
278
|
-
yield* fs.remove(nodeRegisteredFunctionsPath);
|
|
279
|
-
yield* logFileRemoved(nodeRegisteredFunctionsPath);
|
|
280
|
-
}
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const nodeImplImportPath = yield* removePathExtension(
|
|
285
|
-
path.relative(path.dirname(nodeRegisteredFunctionsPath), nodeImplPath),
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
const nodeRegisteredFunctionsContents =
|
|
289
|
-
yield* templates.nodeRegisteredFunctions({
|
|
290
|
-
nodeImplImportPath,
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
yield* writeFileStringAndLog(
|
|
294
|
-
nodeRegisteredFunctionsPath,
|
|
295
|
-
nodeRegisteredFunctionsContents,
|
|
296
|
-
);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
667
|
const generateRefs = Effect.gen(function* () {
|
|
300
668
|
const fs = yield* FileSystem.FileSystem;
|
|
301
669
|
const path = yield* Path.Path;
|
|
@@ -305,19 +673,21 @@ const generateRefs = Effect.gen(function* () {
|
|
|
305
673
|
const refsPath = path.join(confectGeneratedDirectory, "refs.ts");
|
|
306
674
|
const refsDir = path.dirname(refsPath);
|
|
307
675
|
|
|
308
|
-
const specImportPath = yield*
|
|
309
|
-
path.relative(refsDir, path.join(confectDirectory,
|
|
676
|
+
const specImportPath = yield* toModuleImportPath(
|
|
677
|
+
path.relative(refsDir, path.join(confectDirectory, GENERATED_SPEC_PATH)),
|
|
310
678
|
);
|
|
311
679
|
|
|
312
|
-
const nodeSpecPath = yield*
|
|
680
|
+
const nodeSpecPath = yield* getGeneratedNodeSpecPath;
|
|
313
681
|
const nodeSpecExists = yield* fs.exists(nodeSpecPath);
|
|
314
682
|
const nodeSpecImportPath = nodeSpecExists
|
|
315
|
-
?
|
|
316
|
-
|
|
683
|
+
? Option.some(
|
|
684
|
+
yield* toModuleImportPath(path.relative(refsDir, nodeSpecPath)),
|
|
685
|
+
)
|
|
686
|
+
: Option.none<string>();
|
|
317
687
|
|
|
318
688
|
const refsContents = yield* templates.refs({
|
|
319
689
|
specImportPath,
|
|
320
|
-
|
|
690
|
+
nodeSpecImportPath,
|
|
321
691
|
});
|
|
322
692
|
|
|
323
693
|
yield* writeFileStringAndLog(refsPath, refsContents);
|