@confect/cli 9.0.0-next.1 → 9.0.0-next.10
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 +337 -1
- package/dist/BuildError.mjs +9 -2
- package/dist/BuildError.mjs.map +1 -1
- package/dist/Bundler.mjs +67 -57
- package/dist/Bundler.mjs.map +1 -1
- package/dist/CodeBlockWriter.mjs +1 -1
- package/dist/CodeBlockWriter.mjs.map +1 -1
- package/dist/CodegenError.mjs +29 -21
- package/dist/CodegenError.mjs.map +1 -1
- package/dist/ConfectDirectory.mjs +5 -2
- package/dist/ConfectDirectory.mjs.map +1 -1
- package/dist/ConvexDirectory.mjs +6 -2
- package/dist/ConvexDirectory.mjs.map +1 -1
- package/dist/FunctionPath.mjs +1 -1
- package/dist/FunctionPath.mjs.map +1 -1
- package/dist/FunctionPaths.mjs +5 -1
- package/dist/FunctionPaths.mjs.map +1 -1
- package/dist/GroupPath.mjs +9 -2
- package/dist/GroupPath.mjs.map +1 -1
- package/dist/GroupPaths.mjs +1 -1
- package/dist/GroupPaths.mjs.map +1 -1
- package/dist/LeafModule.mjs +19 -26
- package/dist/LeafModule.mjs.map +1 -1
- package/dist/ProjectRoot.mjs +8 -3
- package/dist/ProjectRoot.mjs.map +1 -1
- package/dist/SpecAssemblyNode.mjs +9 -11
- package/dist/SpecAssemblyNode.mjs.map +1 -1
- package/dist/TableModule.mjs +94 -0
- package/dist/TableModule.mjs.map +1 -0
- package/dist/cliApp.mjs +1 -1
- package/dist/cliApp.mjs.map +1 -1
- package/dist/confect/codegen.mjs +203 -142
- package/dist/confect/codegen.mjs.map +1 -1
- package/dist/confect/dev.mjs +36 -17
- package/dist/confect/dev.mjs.map +1 -1
- package/dist/confect.mjs +2 -2
- package/dist/confect.mjs.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/log.mjs +9 -4
- package/dist/log.mjs.map +1 -1
- package/dist/package.mjs +1 -1
- package/dist/templates.mjs +132 -45
- package/dist/templates.mjs.map +1 -1
- package/dist/utils.mjs +42 -22
- package/dist/utils.mjs.map +1 -1
- package/package.json +33 -50
- package/dist/index.d.mts +0 -1
- package/src/BuildError.ts +0 -210
- package/src/Bundler.ts +0 -144
- package/src/CodeBlockWriter.ts +0 -65
- package/src/CodegenError.ts +0 -376
- package/src/ConfectDirectory.ts +0 -42
- package/src/ConvexDirectory.ts +0 -68
- package/src/FunctionPath.ts +0 -27
- package/src/FunctionPaths.ts +0 -103
- package/src/GroupPath.ts +0 -118
- package/src/GroupPaths.ts +0 -7
- package/src/LeafModule.ts +0 -317
- package/src/ProjectRoot.ts +0 -50
- package/src/SpecAssemblyNode.ts +0 -82
- package/src/cliApp.ts +0 -8
- package/src/confect/codegen.ts +0 -710
- package/src/confect/dev.ts +0 -749
- package/src/confect.ts +0 -19
- package/src/index.ts +0 -22
- package/src/log.ts +0 -104
- package/src/templates.ts +0 -482
- package/src/utils.ts +0 -429
package/src/confect/codegen.ts
DELETED
|
@@ -1,710 +0,0 @@
|
|
|
1
|
-
import { type GroupSpec, Spec } from "@confect/core";
|
|
2
|
-
import * as DatabaseSchema from "@confect/server/DatabaseSchema";
|
|
3
|
-
import { Command } from "@effect/cli";
|
|
4
|
-
import { FileSystem, Path } from "@effect/platform";
|
|
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";
|
|
18
|
-
import {
|
|
19
|
-
logFileAdded,
|
|
20
|
-
logFileModified,
|
|
21
|
-
logFileRemoved,
|
|
22
|
-
logPending,
|
|
23
|
-
logSuccess,
|
|
24
|
-
} from "../log";
|
|
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";
|
|
42
|
-
import * as templates from "../templates";
|
|
43
|
-
import * as Bundler from "../Bundler";
|
|
44
|
-
import {
|
|
45
|
-
generateAuthConfig,
|
|
46
|
-
generateCrons,
|
|
47
|
-
generateFunctions,
|
|
48
|
-
generateHttp,
|
|
49
|
-
removePathExtension,
|
|
50
|
-
removePathIfExists,
|
|
51
|
-
toModuleImportPath,
|
|
52
|
-
touchConvexSchema,
|
|
53
|
-
writeFileStringAndLog,
|
|
54
|
-
WriteTracker,
|
|
55
|
-
} from "../utils";
|
|
56
|
-
|
|
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
|
-
];
|
|
72
|
-
|
|
73
|
-
export const codegen = Command.make("codegen", {}, () =>
|
|
74
|
-
Effect.gen(function* () {
|
|
75
|
-
yield* logPending("Performing initial sync…");
|
|
76
|
-
yield* codegenHandler.pipe(
|
|
77
|
-
Effect.asVoid,
|
|
78
|
-
Effect.tap(() => logSuccess("Generated files are up-to-date")),
|
|
79
|
-
CodegenError.tapAndLog,
|
|
80
|
-
);
|
|
81
|
-
}),
|
|
82
|
-
).pipe(
|
|
83
|
-
Command.withDescription(
|
|
84
|
-
"Generate `confect/_generated` files and the contents of the `convex` directory (except `convex.config.ts` and `tsconfig.json`)",
|
|
85
|
-
),
|
|
86
|
-
);
|
|
87
|
-
|
|
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* () {
|
|
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);
|
|
114
|
-
yield* Effect.all(
|
|
115
|
-
[generateApi, generateRefs, generateNodeApi, generateServices],
|
|
116
|
-
{ concurrency: "unbounded" },
|
|
117
|
-
);
|
|
118
|
-
const [functionPaths] = yield* Effect.all(
|
|
119
|
-
[
|
|
120
|
-
generateFunctionModules,
|
|
121
|
-
generateSchema,
|
|
122
|
-
logGenerated(generateHttp),
|
|
123
|
-
logGenerated(generateCrons),
|
|
124
|
-
logGenerated(generateAuthConfig),
|
|
125
|
-
],
|
|
126
|
-
{ concurrency: "unbounded" },
|
|
127
|
-
);
|
|
128
|
-
yield* touchConvexSchema;
|
|
129
|
-
return functionPaths;
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const generateConfectGeneratedDirectory = Effect.gen(function* () {
|
|
133
|
-
const fs = yield* FileSystem.FileSystem;
|
|
134
|
-
const path = yield* Path.Path;
|
|
135
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
136
|
-
|
|
137
|
-
if (!(yield* fs.exists(path.join(confectDirectory, "_generated")))) {
|
|
138
|
-
yield* fs.makeDirectory(path.join(confectDirectory, "_generated"), {
|
|
139
|
-
recursive: true,
|
|
140
|
-
});
|
|
141
|
-
yield* logFileAdded(path.join(confectDirectory, "_generated") + "/");
|
|
142
|
-
}
|
|
143
|
-
});
|
|
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
|
-
|
|
529
|
-
const generateApi = Effect.gen(function* () {
|
|
530
|
-
const path = yield* Path.Path;
|
|
531
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
532
|
-
|
|
533
|
-
const apiPath = path.join(confectDirectory, "_generated", "api.ts");
|
|
534
|
-
const apiDir = path.dirname(apiPath);
|
|
535
|
-
|
|
536
|
-
const schemaImportPath = yield* toModuleImportPath(
|
|
537
|
-
path.relative(apiDir, path.join(confectDirectory, "schema.ts")),
|
|
538
|
-
);
|
|
539
|
-
|
|
540
|
-
const specImportPath = yield* toModuleImportPath(
|
|
541
|
-
path.relative(apiDir, path.join(confectDirectory, GENERATED_SPEC_PATH)),
|
|
542
|
-
);
|
|
543
|
-
|
|
544
|
-
const apiContents = yield* templates.api({
|
|
545
|
-
schemaImportPath,
|
|
546
|
-
specImportPath,
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
yield* writeFileStringAndLog(apiPath, apiContents);
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
export const generateNodeApi = Effect.gen(function* () {
|
|
553
|
-
const fs = yield* FileSystem.FileSystem;
|
|
554
|
-
const path = yield* Path.Path;
|
|
555
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
556
|
-
|
|
557
|
-
const nodeSpecPath = yield* getGeneratedNodeSpecPath;
|
|
558
|
-
const nodeApiPath = path.join(confectDirectory, "_generated", "nodeApi.ts");
|
|
559
|
-
|
|
560
|
-
if (!(yield* fs.exists(nodeSpecPath))) {
|
|
561
|
-
if (yield* fs.exists(nodeApiPath)) {
|
|
562
|
-
yield* removePathIfExists(nodeApiPath);
|
|
563
|
-
yield* logFileRemoved(nodeApiPath);
|
|
564
|
-
}
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const nodeApiDir = path.dirname(nodeApiPath);
|
|
569
|
-
|
|
570
|
-
const schemaImportPath = yield* toModuleImportPath(
|
|
571
|
-
path.relative(nodeApiDir, path.join(confectDirectory, "schema.ts")),
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
const nodeSpecImportPath = yield* toModuleImportPath(
|
|
575
|
-
path.relative(nodeApiDir, nodeSpecPath),
|
|
576
|
-
);
|
|
577
|
-
|
|
578
|
-
const nodeApiContents = yield* templates.nodeApi({
|
|
579
|
-
schemaImportPath,
|
|
580
|
-
nodeSpecImportPath,
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
yield* writeFileStringAndLog(nodeApiPath, nodeApiContents);
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
const generateFunctionModules = Effect.gen(function* () {
|
|
587
|
-
const spec = yield* loadGeneratedSpec;
|
|
588
|
-
const nodeSpecOption = yield* loadGeneratedNodeSpec;
|
|
589
|
-
|
|
590
|
-
const mergedSpec = Option.match(nodeSpecOption, {
|
|
591
|
-
onNone: () => spec,
|
|
592
|
-
onSome: (nodeSpec) => Spec.merge(spec, nodeSpec),
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
return yield* generateFunctions(mergedSpec);
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
export const validateSchema = Effect.gen(function* () {
|
|
599
|
-
const fs = yield* FileSystem.FileSystem;
|
|
600
|
-
const path = yield* Path.Path;
|
|
601
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
602
|
-
const confectSchemaPath = path.join(confectDirectory, "schema.ts");
|
|
603
|
-
|
|
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 }) => {
|
|
611
|
-
const defaultExport = schemaModule.default;
|
|
612
|
-
|
|
613
|
-
return DatabaseSchema.isDatabaseSchema(defaultExport)
|
|
614
|
-
? Effect.succeed(defaultExport)
|
|
615
|
-
: Effect.fail(
|
|
616
|
-
new SchemaInvalidDefaultExportError({
|
|
617
|
-
schemaPath: "schema.ts",
|
|
618
|
-
}),
|
|
619
|
-
);
|
|
620
|
-
}),
|
|
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.
|
|
633
|
-
|
|
634
|
-
const convexSchemaPath = path.join(convexDirectory, "schema.ts");
|
|
635
|
-
|
|
636
|
-
const relativeImportPath = path.relative(
|
|
637
|
-
path.dirname(convexSchemaPath),
|
|
638
|
-
confectSchemaPath,
|
|
639
|
-
);
|
|
640
|
-
const importPathWithoutExt = yield* removePathExtension(relativeImportPath);
|
|
641
|
-
const schemaContents = yield* templates.schema({
|
|
642
|
-
schemaImportPath: importPathWithoutExt,
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
yield* writeFileStringAndLog(convexSchemaPath, schemaContents);
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
const generateServices = Effect.gen(function* () {
|
|
649
|
-
const path = yield* Path.Path;
|
|
650
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
651
|
-
|
|
652
|
-
const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
|
|
653
|
-
|
|
654
|
-
const servicesPath = path.join(confectGeneratedDirectory, "services.ts");
|
|
655
|
-
const schemaImportPath = path.relative(
|
|
656
|
-
path.dirname(servicesPath),
|
|
657
|
-
path.join(confectDirectory, "schema"),
|
|
658
|
-
);
|
|
659
|
-
|
|
660
|
-
const servicesContentsString = yield* templates.services({
|
|
661
|
-
schemaImportPath,
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
yield* writeFileStringAndLog(servicesPath, servicesContentsString);
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
const generateRefs = Effect.gen(function* () {
|
|
668
|
-
const fs = yield* FileSystem.FileSystem;
|
|
669
|
-
const path = yield* Path.Path;
|
|
670
|
-
const confectDirectory = yield* ConfectDirectory.get;
|
|
671
|
-
|
|
672
|
-
const confectGeneratedDirectory = path.join(confectDirectory, "_generated");
|
|
673
|
-
const refsPath = path.join(confectGeneratedDirectory, "refs.ts");
|
|
674
|
-
const refsDir = path.dirname(refsPath);
|
|
675
|
-
|
|
676
|
-
const specImportPath = yield* toModuleImportPath(
|
|
677
|
-
path.relative(refsDir, path.join(confectDirectory, GENERATED_SPEC_PATH)),
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
const nodeSpecPath = yield* getGeneratedNodeSpecPath;
|
|
681
|
-
const nodeSpecExists = yield* fs.exists(nodeSpecPath);
|
|
682
|
-
const nodeSpecImportPath = nodeSpecExists
|
|
683
|
-
? Option.some(
|
|
684
|
-
yield* toModuleImportPath(path.relative(refsDir, nodeSpecPath)),
|
|
685
|
-
)
|
|
686
|
-
: Option.none<string>();
|
|
687
|
-
|
|
688
|
-
const refsContents = yield* templates.refs({
|
|
689
|
-
specImportPath,
|
|
690
|
-
nodeSpecImportPath,
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
yield* writeFileStringAndLog(refsPath, refsContents);
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
const logGenerated = (effect: typeof generateHttp) =>
|
|
697
|
-
effect.pipe(
|
|
698
|
-
Effect.tap(
|
|
699
|
-
Option.match({
|
|
700
|
-
onNone: () => Effect.void,
|
|
701
|
-
onSome: ({ change, convexFilePath }) =>
|
|
702
|
-
Match.value(change).pipe(
|
|
703
|
-
Match.when("Added", () => logFileAdded(convexFilePath)),
|
|
704
|
-
Match.when("Modified", () => logFileModified(convexFilePath)),
|
|
705
|
-
Match.when("Unchanged", () => Effect.void),
|
|
706
|
-
Match.exhaustive,
|
|
707
|
-
),
|
|
708
|
-
}),
|
|
709
|
-
),
|
|
710
|
-
);
|