@gaganref/confect-cli 0.0.0-stage → 10.3.1-fork.2

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +1824 -0
  2. package/LICENSE +7 -0
  3. package/README.md +16 -2
  4. package/bin/confect.mjs +26 -0
  5. package/dist/Ansi.mjs +18 -0
  6. package/dist/Ansi.mjs.map +1 -0
  7. package/dist/BuildError.mjs +30 -0
  8. package/dist/BuildError.mjs.map +1 -0
  9. package/dist/Bundler.mjs +426 -0
  10. package/dist/Bundler.mjs.map +1 -0
  11. package/dist/CodeBlockWriter.mjs +55 -0
  12. package/dist/CodeBlockWriter.mjs.map +1 -0
  13. package/dist/CodegenError.mjs +194 -0
  14. package/dist/CodegenError.mjs.map +1 -0
  15. package/dist/ConfectDirectory.mjs +35 -0
  16. package/dist/ConfectDirectory.mjs.map +1 -0
  17. package/dist/ConvexConfig.mjs +198 -0
  18. package/dist/ConvexConfig.mjs.map +1 -0
  19. package/dist/ConvexDirectory.mjs +54 -0
  20. package/dist/ConvexDirectory.mjs.map +1 -0
  21. package/dist/Declaration.mjs +258 -0
  22. package/dist/Declaration.mjs.map +1 -0
  23. package/dist/DirectoryOptions.mjs +42 -0
  24. package/dist/DirectoryOptions.mjs.map +1 -0
  25. package/dist/DocName.mjs +25 -0
  26. package/dist/DocName.mjs.map +1 -0
  27. package/dist/EntryPoint.mjs +111 -0
  28. package/dist/EntryPoint.mjs.map +1 -0
  29. package/dist/Evaluation.mjs +117 -0
  30. package/dist/Evaluation.mjs.map +1 -0
  31. package/dist/EvaluationWorker.mjs +46 -0
  32. package/dist/EvaluationWorker.mjs.map +1 -0
  33. package/dist/FunctionModule.mjs +137 -0
  34. package/dist/FunctionModule.mjs.map +1 -0
  35. package/dist/FunctionPath.mjs +18 -0
  36. package/dist/FunctionPath.mjs.map +1 -0
  37. package/dist/FunctionPaths.mjs +45 -0
  38. package/dist/FunctionPaths.mjs.map +1 -0
  39. package/dist/GenerationSnapshot.mjs +183 -0
  40. package/dist/GenerationSnapshot.mjs.map +1 -0
  41. package/dist/GroupPath.mjs +51 -0
  42. package/dist/GroupPath.mjs.map +1 -0
  43. package/dist/GroupPaths.mjs +8 -0
  44. package/dist/GroupPaths.mjs.map +1 -0
  45. package/dist/ImplModulePath.mjs +8 -0
  46. package/dist/ImplModulePath.mjs.map +1 -0
  47. package/dist/LeafModule.mjs +285 -0
  48. package/dist/LeafModule.mjs.map +1 -0
  49. package/dist/ProjectRoot.mjs +38 -0
  50. package/dist/ProjectRoot.mjs.map +1 -0
  51. package/dist/RelativeModulePath.mjs +13 -0
  52. package/dist/RelativeModulePath.mjs.map +1 -0
  53. package/dist/Remove.mjs +24 -0
  54. package/dist/Remove.mjs.map +1 -0
  55. package/dist/RestoreWatcher.mjs +31 -0
  56. package/dist/RestoreWatcher.mjs.map +1 -0
  57. package/dist/Rollback.mjs +50 -0
  58. package/dist/Rollback.mjs.map +1 -0
  59. package/dist/SnapshotError.mjs +21 -0
  60. package/dist/SnapshotError.mjs.map +1 -0
  61. package/dist/SpecAssemblyNode.mjs +91 -0
  62. package/dist/SpecAssemblyNode.mjs.map +1 -0
  63. package/dist/SpecModulePath.mjs +10 -0
  64. package/dist/SpecModulePath.mjs.map +1 -0
  65. package/dist/SyncState.mjs +122 -0
  66. package/dist/SyncState.mjs.map +1 -0
  67. package/dist/TableModule.mjs +97 -0
  68. package/dist/TableModule.mjs.map +1 -0
  69. package/dist/TableModulePath.mjs +8 -0
  70. package/dist/TableModulePath.mjs.map +1 -0
  71. package/dist/WatchBuild.mjs +190 -0
  72. package/dist/WatchBuild.mjs.map +1 -0
  73. package/dist/WatchGraph.mjs +35 -0
  74. package/dist/WatchGraph.mjs.map +1 -0
  75. package/dist/WatchTopology.mjs +55 -0
  76. package/dist/WatchTopology.mjs.map +1 -0
  77. package/dist/Watcher.mjs +378 -0
  78. package/dist/Watcher.mjs.map +1 -0
  79. package/dist/cliApp.mjs +9 -0
  80. package/dist/cliApp.mjs.map +1 -0
  81. package/dist/confect/codegen.mjs +548 -0
  82. package/dist/confect/codegen.mjs.map +1 -0
  83. package/dist/confect/declaration.mjs +218 -0
  84. package/dist/confect/declaration.mjs.map +1 -0
  85. package/dist/confect/dev.mjs +182 -0
  86. package/dist/confect/dev.mjs.map +1 -0
  87. package/dist/confect.mjs +9 -0
  88. package/dist/confect.mjs.map +1 -0
  89. package/dist/index.mjs +19 -0
  90. package/dist/index.mjs.map +1 -0
  91. package/dist/log.mjs +107 -0
  92. package/dist/log.mjs.map +1 -0
  93. package/dist/package.mjs +6 -0
  94. package/dist/package.mjs.map +1 -0
  95. package/dist/templates.mjs +448 -0
  96. package/dist/templates.mjs.map +1 -0
  97. package/dist/utils.mjs +105 -0
  98. package/dist/utils.mjs.map +1 -0
  99. package/package.json +51 -3
@@ -0,0 +1,54 @@
1
+ import { ProjectRoot, layer as layer$1 } from "./ProjectRoot.mjs";
2
+ import * as Effect from "effect/Effect";
3
+ import * as FileSystem from "effect/FileSystem";
4
+ import * as Path from "effect/Path";
5
+ import * as Option from "effect/Option";
6
+ import * as Context from "effect/Context";
7
+ import * as Ref from "effect/Ref";
8
+ import * as Schema from "effect/Schema";
9
+ import * as Predicate from "effect/Predicate";
10
+ import * as Layer from "effect/Layer";
11
+ import * as IdScope from "@confect/core/IdScope";
12
+ //#region src/ConvexDirectory.ts
13
+ var ConvexDirectory = class ConvexDirectory extends Context.Service()("@confect/cli/ConvexDirectory") {
14
+ static get = ConvexDirectory.use((service) => service.get);
15
+ static target = ConvexDirectory.use((service) => Effect.succeed(service.target ?? {
16
+ kind: "app",
17
+ scope: IdScope.app
18
+ }));
19
+ };
20
+ var ConvexDirectoryNotFoundError = class extends Schema.TaggedError()("ConvexDirectoryNotFoundError", {}) {
21
+ get message() {
22
+ return "Could not find Convex directory";
23
+ }
24
+ };
25
+ /**
26
+ * Schema for `convex.json` configuration file.
27
+ *
28
+ * @see https://docs.convex.dev/production/project-configuration
29
+ */
30
+ const ConvexJsonConfig = Schema.fromJsonString(Schema.Struct({ functions: Schema.optional(Schema.String) }));
31
+ var InvalidConvexJsonError = class extends Schema.TaggedError()("InvalidConvexJsonError", { cause: Schema.Unknown }) {
32
+ get message() {
33
+ return `Failed to parse convex.json${Predicate.isError(this.cause) ? `: ${this.cause.message}` : ""}`;
34
+ }
35
+ };
36
+ const findConvexDirectory = Effect.gen(function* () {
37
+ const fs = yield* FileSystem.FileSystem;
38
+ const path = yield* Path.Path;
39
+ const projectRoot = yield* ProjectRoot.get;
40
+ const defaultPath = path.join(projectRoot, "convex");
41
+ const convexJsonPath = path.join(projectRoot, "convex.json");
42
+ const convexDirectory = (yield* fs.exists(convexJsonPath)) ? yield* fs.readFileString(convexJsonPath).pipe(Effect.flatMap((json) => Schema.decodeEffect(ConvexJsonConfig)(json).pipe(Effect.mapError((cause) => new InvalidConvexJsonError({ cause })))), Effect.map((config) => Option.fromNullishOr(config.functions).pipe(Option.map((functionsDir) => path.join(projectRoot, functionsDir)), Option.getOrElse(() => defaultPath)))) : defaultPath;
43
+ if (yield* fs.exists(convexDirectory)) return convexDirectory;
44
+ else return yield* new ConvexDirectoryNotFoundError();
45
+ });
46
+ const layer = Layer.effect(ConvexDirectory, Effect.gen(function* () {
47
+ const convexDirectory = yield* findConvexDirectory;
48
+ const ref = yield* Ref.make(convexDirectory);
49
+ return { get: Ref.get(ref) };
50
+ })).pipe(Layer.provide(layer$1));
51
+ //#endregion
52
+ export { ConvexDirectory, ConvexDirectoryNotFoundError, InvalidConvexJsonError, layer };
53
+
54
+ //# sourceMappingURL=ConvexDirectory.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConvexDirectory.mjs","names":["ProjectRoot.layer"],"sources":["../src/ConvexDirectory.ts"],"sourcesContent":["import * as IdScope from \"@confect/core/IdScope\";\nimport * as Context from \"effect/Context\";\nimport * as Effect from \"effect/Effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Layer from \"effect/Layer\";\nimport * as Option from \"effect/Option\";\nimport * as Path from \"effect/Path\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as Ref from \"effect/Ref\";\nimport * as Schema from \"effect/Schema\";\nimport * as ProjectRoot from \"./ProjectRoot\";\nimport type * as DatabaseSchema from \"@confect/server/DatabaseSchema\";\n\nexport class ConvexDirectory extends Context.Service<\n ConvexDirectory,\n {\n readonly get: Effect.Effect<string>;\n readonly target?:\n | DatabaseSchema.AppTarget\n | DatabaseSchema.ComponentTarget<IdScope.Component<string>>;\n }\n>()(\"@confect/cli/ConvexDirectory\") {\n static readonly get = ConvexDirectory.use((service) => service.get);\n static readonly target = ConvexDirectory.use((service) =>\n Effect.succeed(\n service.target ?? { kind: \"app\" as const, scope: IdScope.app },\n ),\n );\n}\n\nexport class ConvexDirectoryNotFoundError extends Schema.TaggedError<ConvexDirectoryNotFoundError>()(\n \"ConvexDirectoryNotFoundError\",\n {},\n) {\n override get message(): string {\n return \"Could not find Convex directory\";\n }\n}\n\n/**\n * Schema for `convex.json` configuration file.\n *\n * @see https://docs.convex.dev/production/project-configuration\n */\nconst ConvexJsonConfig = Schema.fromJsonString(\n Schema.Struct({\n functions: Schema.optional(Schema.String),\n }),\n);\n\nexport class InvalidConvexJsonError extends Schema.TaggedError<InvalidConvexJsonError>()(\n \"InvalidConvexJsonError\",\n {\n cause: Schema.Unknown,\n },\n) {\n override get message(): string {\n const detail = Predicate.isError(this.cause)\n ? `: ${this.cause.message}`\n : \"\";\n return `Failed to parse convex.json${detail}`;\n }\n}\n\nconst findConvexDirectory = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n\n const projectRoot = yield* ProjectRoot.ProjectRoot.get;\n\n const defaultPath = path.join(projectRoot, \"convex\");\n\n const convexJsonPath = path.join(projectRoot, \"convex.json\");\n\n const convexDirectory = (yield* fs.exists(convexJsonPath))\n ? yield* fs.readFileString(convexJsonPath).pipe(\n Effect.flatMap((json) =>\n Schema.decodeEffect(ConvexJsonConfig)(json).pipe(\n Effect.mapError((cause) => new InvalidConvexJsonError({ cause })),\n ),\n ),\n Effect.map((config) =>\n Option.fromNullishOr(config.functions).pipe(\n Option.map((functionsDir) => path.join(projectRoot, functionsDir)),\n Option.getOrElse(() => defaultPath),\n ),\n ),\n )\n : defaultPath;\n\n if (yield* fs.exists(convexDirectory)) {\n return convexDirectory;\n } else {\n return yield* new ConvexDirectoryNotFoundError();\n }\n});\n\nexport const layer = Layer.effect(\n ConvexDirectory,\n Effect.gen(function* () {\n const convexDirectory = yield* findConvexDirectory;\n\n const ref = yield* Ref.make<string>(convexDirectory);\n\n return { get: Ref.get(ref) } as const;\n }),\n).pipe(Layer.provide(ProjectRoot.layer));\n"],"mappings":";;;;;;;;;;;;AAaA,IAAa,kBAAb,MAAa,wBAAwB,QAAQ,QAQ3C,CAAC,CAAC,8BAA8B,CAAC,CAAC;CAClC,OAAgB,MAAM,gBAAgB,KAAK,YAAY,QAAQ,GAAG;CAClE,OAAgB,SAAS,gBAAgB,KAAK,YAC5C,OAAO,QACL,QAAQ,UAAU;EAAE,MAAM;EAAgB,OAAO,QAAQ;CAAI,CAC/D,CACF;AACF;AAEA,IAAa,+BAAb,cAAkD,OAAO,YAA0C,CAAC,CAClG,gCACA,CAAC,CACH,CAAC,CAAC;CACA,IAAa,UAAkB;EAC7B,OAAO;CACT;AACF;;;;;;AAOA,MAAM,mBAAmB,OAAO,eAC9B,OAAO,OAAO,EACZ,WAAW,OAAO,SAAS,OAAO,MAAM,EAC1C,CAAC,CACH;AAEA,IAAa,yBAAb,cAA4C,OAAO,YAAoC,CAAC,CACtF,0BACA,EACE,OAAO,OAAO,QAChB,CACF,CAAC,CAAC;CACA,IAAa,UAAkB;EAI7B,OAAO,8BAHQ,UAAU,QAAQ,KAAK,KAAK,IACvC,KAAK,KAAK,MAAM,YAChB;CAEN;AACF;AAEA,MAAM,sBAAsB,OAAO,IAAI,aAAa;CAClD,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,OAAO,KAAK;CAEzB,MAAM,cAAc,OAAA,YAA+B;CAEnD,MAAM,cAAc,KAAK,KAAK,aAAa,QAAQ;CAEnD,MAAM,iBAAiB,KAAK,KAAK,aAAa,aAAa;CAE3D,MAAM,mBAAmB,OAAO,GAAG,OAAO,cAAc,KACpD,OAAO,GAAG,eAAe,cAAc,CAAC,CAAC,KACvC,OAAO,SAAS,SACd,OAAO,aAAa,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,KAC1C,OAAO,UAAU,UAAU,IAAI,uBAAuB,EAAE,MAAM,CAAC,CAAC,CAClE,CACF,GACA,OAAO,KAAK,WACV,OAAO,cAAc,OAAO,SAAS,CAAC,CAAC,KACrC,OAAO,KAAK,iBAAiB,KAAK,KAAK,aAAa,YAAY,CAAC,GACjE,OAAO,gBAAgB,WAAW,CACpC,CACF,CACF,IACA;CAEJ,IAAI,OAAO,GAAG,OAAO,eAAe,GAClC,OAAO;MAEP,OAAO,OAAO,IAAI,6BAA6B;AAEnD,CAAC;AAED,MAAa,QAAQ,MAAM,OACzB,iBACA,OAAO,IAAI,aAAa;CACtB,MAAM,kBAAkB,OAAO;CAE/B,MAAM,MAAM,OAAO,IAAI,KAAa,eAAe;CAEnD,OAAO,EAAE,KAAK,IAAI,IAAI,GAAG,EAAE;AAC7B,CAAC,CACH,CAAC,CAAC,KAAK,MAAM,QAAQA,OAAiB,CAAC"}
@@ -0,0 +1,258 @@
1
+ import { App, Auth, Env } from "@confect/core";
2
+ import * as Result from "effect/Result";
3
+ import * as Option from "effect/Option";
4
+ import * as Predicate from "effect/Predicate";
5
+ import * as Data from "effect/Data";
6
+ import { createHash } from "node:crypto";
7
+ //#region src/Declaration.ts
8
+ var NotADeclaration = class extends Data.TaggedError("NotADeclaration") {
9
+ get message() {
10
+ return `${this.file} must default-export ${this.expected}.`;
11
+ }
12
+ };
13
+ var InvalidEnvKey = class extends Data.TaggedError("InvalidEnvKey") {
14
+ get message() {
15
+ return `"${this.key}" is not a valid Convex environment variable name; use letters, digits and underscores, starting with a letter.`;
16
+ }
17
+ };
18
+ var InvalidMountName = class extends Data.TaggedError("InvalidMountName") {
19
+ get message() {
20
+ return `"${this.mount}" is not a valid mount name; use letters, digits and underscores, starting with a letter or underscore.`;
21
+ }
22
+ };
23
+ var ForeignEnvBinding = class extends Data.TaggedError("ForeignEnvBinding") {
24
+ get message() {
25
+ return `The mount "${this.mount}" binds ${this.slot} to ${this.key} of the "${this.backend}" env, but this backend's env is "${this.expectedBackend}".`;
26
+ }
27
+ };
28
+ var InvalidBinding = class extends Data.TaggedError("InvalidBinding") {
29
+ get message() {
30
+ return `The mount "${this.mount}" binds ${this.slot} to something other than a string or an entry of this backend's env.`;
31
+ }
32
+ };
33
+ var UnknownComponentImport = class extends Data.TaggedError("UnknownComponentImport") {
34
+ get message() {
35
+ return `The mount "${this.mount}" must pass the default export of a component's convex.config module, imported directly in the declaration.`;
36
+ }
37
+ };
38
+ var AuthForAnotherBackend = class extends Data.TaggedError("AuthForAnotherBackend") {
39
+ get message() {
40
+ return `confect/auth.ts declares auth for the "${this.backend}" backend, but confect/app.ts declares "${this.expectedBackend}".`;
41
+ }
42
+ };
43
+ const ENV_KEY = /^[A-Za-z][A-Za-z0-9_]*$/;
44
+ const MOUNT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
45
+ /**
46
+ * The declaration file a parse error is about.
47
+ */
48
+ const fileOf = (error) => error._tag === "NotADeclaration" ? error.file : error._tag === "AuthForAnotherBackend" ? "confect/auth.ts" : "confect/app.ts";
49
+ /**
50
+ * Parse the default exports of `confect/app.ts` and, when present,
51
+ * `confect/auth.ts`. `componentImport` recovers how the generated config
52
+ * imports a mounted definition; codegen's bundler records it.
53
+ */
54
+ const parse = (input) => Result.gen(function* () {
55
+ if (!App.isApp(input.app)) return yield* Result.fail(new NotADeclaration({
56
+ file: "confect/app.ts",
57
+ expected: "App.make(...)"
58
+ }));
59
+ const env = App.env(input.app);
60
+ const name = Env.name(env);
61
+ const descriptors = Env.descriptors(env);
62
+ for (const [key] of descriptors) if (!ENV_KEY.test(key)) return yield* Result.fail(new InvalidEnvKey({ key }));
63
+ const mounts = [];
64
+ for (const mount of App.mounts(input.app)) {
65
+ if (!MOUNT_NAME.test(mount.name)) return yield* Result.fail(new InvalidMountName({ mount: mount.name }));
66
+ const component = input.componentImport(mount.definition);
67
+ if (Option.isNone(component)) return yield* Result.fail(new UnknownComponentImport({ mount: mount.name }));
68
+ const bindings = [];
69
+ for (const [slot, bound] of Object.entries(mount.env)) {
70
+ if (bound === void 0) continue;
71
+ if (typeof bound === "string") {
72
+ bindings.push([slot, {
73
+ kind: "value",
74
+ value: bound
75
+ }]);
76
+ continue;
77
+ }
78
+ if (!Env.isEntry(bound)) return yield* Result.fail(new InvalidBinding({
79
+ mount: mount.name,
80
+ slot
81
+ }));
82
+ const origin = Env.origin(bound);
83
+ if (origin.backend !== name) return yield* Result.fail(new ForeignEnvBinding({
84
+ mount: mount.name,
85
+ slot,
86
+ backend: origin.backend,
87
+ key: origin.key,
88
+ expectedBackend: name
89
+ }));
90
+ bindings.push([slot, {
91
+ kind: "env",
92
+ key: origin.key
93
+ }]);
94
+ }
95
+ mounts.push({
96
+ name: mount.name,
97
+ importPath: component.value.importPath,
98
+ componentName: component.value.componentName,
99
+ httpPrefix: mount.httpPrefix,
100
+ env: bindings
101
+ });
102
+ }
103
+ const providers = Option.isNone(input.auth) ? Option.none() : Option.some(yield* parseProviders(input.auth.value, name));
104
+ return {
105
+ target: input.target,
106
+ name,
107
+ env: descriptors,
108
+ mounts,
109
+ providers
110
+ };
111
+ });
112
+ const parseProviders = (auth, backend) => {
113
+ if (!Auth.isAuth(auth)) return Result.fail(new NotADeclaration({
114
+ file: "confect/auth.ts",
115
+ expected: "Auth.make(app, ...)"
116
+ }));
117
+ const authBackend = Env.name(App.env(auth.app));
118
+ if (authBackend !== backend) return Result.fail(new AuthForAnotherBackend({
119
+ backend: authBackend,
120
+ expectedBackend: backend
121
+ }));
122
+ return Result.succeed(Auth.providers(auth).map((provider) => Object.entries(provider).map(([field, value]) => {
123
+ const sitePath = Auth.siteUrlPath(value);
124
+ return [field, sitePath === void 0 ? {
125
+ kind: "value",
126
+ value: Predicate.isString(value) ? value : JSON.stringify(value)
127
+ } : {
128
+ kind: "siteUrl",
129
+ path: sitePath
130
+ }];
131
+ })));
132
+ };
133
+ const quote = (value) => JSON.stringify(value);
134
+ const header = (source) => `// Generated by \`confect codegen\` from ${source}. Do not edit.`;
135
+ const GENERATED_PREFIX = "// Generated by `confect codegen` from ";
136
+ /**
137
+ * Whether codegen may overwrite or remove an existing file: it carries the
138
+ * generated header, or it is exactly one of the files an earlier codegen wrote
139
+ * there (`legacy`).
140
+ */
141
+ const isGenerated = (contents, legacy) => contents.startsWith(GENERATED_PREFIX) || legacy.includes(contents);
142
+ const validator = (descriptor) => {
143
+ const base = descriptor.kind === "literals" ? descriptor.values.length === 1 ? `v.literal(${quote(descriptor.values[0])})` : `v.union(${descriptor.values.map((value) => `v.literal(${quote(value)})`).join(", ")})` : "v.string()";
144
+ return descriptor.optional ? `v.optional(${base})` : base;
145
+ };
146
+ const RESERVED = new Set("await break case catch class const continue debugger default delete do else enum export extends false finally for function if implements import in instanceof interface let new null package private protected public return static super switch this throw true try typeof var void while with yield".split(" "));
147
+ /**
148
+ * A safe import name for a component, from its Convex name.
149
+ */
150
+ const identifier = (name) => {
151
+ const cleaned = name.replaceAll(/[^A-Za-z0-9_$]/g, "_");
152
+ const valid = /^[A-Za-z_$]/.test(cleaned) ? cleaned : `_${cleaned}`;
153
+ return RESERVED.has(valid) ? `${valid}Component` : valid;
154
+ };
155
+ /**
156
+ * `convex/convex.config.ts`: Convex's own app or component definition.
157
+ */
158
+ const convexConfig = (declaration) => {
159
+ const self = declaration.target === "app" ? "app" : "component";
160
+ const aliases = /* @__PURE__ */ new Map();
161
+ const taken = /* @__PURE__ */ new Set([
162
+ self,
163
+ "v",
164
+ "defineApp",
165
+ "defineComponent"
166
+ ]);
167
+ for (const mount of declaration.mounts) {
168
+ if (aliases.has(mount.importPath)) continue;
169
+ const base = identifier(mount.componentName);
170
+ let alias = base;
171
+ for (let n = 2; taken.has(alias); n++) alias = `${base}${n}`;
172
+ taken.add(alias);
173
+ aliases.set(mount.importPath, alias);
174
+ }
175
+ const lines = [header("confect/app.ts")];
176
+ lines.push(declaration.target === "app" ? "import { defineApp } from \"convex/server\";" : "import { defineComponent } from \"convex/server\";");
177
+ if (declaration.env.length > 0) lines.push("import { v } from \"convex/values\";");
178
+ for (const [importPath, alias] of aliases) lines.push(`import ${alias} from ${quote(importPath)};`);
179
+ lines.push("");
180
+ const envBlock = declaration.env.length === 0 ? [] : [
181
+ " env: {",
182
+ ...declaration.env.map(([key, descriptor]) => ` ${key}: ${validator(descriptor)},`),
183
+ " },"
184
+ ];
185
+ if (declaration.target === "app") lines.push(envBlock.length === 0 ? "const app = defineApp();" : [
186
+ "const app = defineApp({",
187
+ ...envBlock,
188
+ "});"
189
+ ].join("\n"));
190
+ else lines.push(envBlock.length === 0 ? `const component = defineComponent(${quote(declaration.name)});` : [
191
+ `const component = defineComponent(${quote(declaration.name)}, {`,
192
+ ...envBlock,
193
+ "});"
194
+ ].join("\n"));
195
+ for (const mount of declaration.mounts) {
196
+ lines.push("");
197
+ lines.push(`${self}.use(${aliases.get(mount.importPath)}, {`);
198
+ lines.push(` name: ${quote(mount.name)},`);
199
+ if (mount.httpPrefix !== void 0) lines.push(` httpPrefix: ${quote(mount.httpPrefix)},`);
200
+ if (mount.env.length > 0) {
201
+ lines.push(" env: {");
202
+ for (const [slot, binding] of mount.env) lines.push(` ${slot}: ${binding.kind === "value" ? quote(binding.value) : `${self}.env.${binding.key}`},`);
203
+ lines.push(" },");
204
+ }
205
+ lines.push("});");
206
+ }
207
+ lines.push("", `export default ${self};`, "");
208
+ return lines.join("\n");
209
+ };
210
+ const providerValue = (value) => value.kind === "value" ? quote(value.value) : `\`\${process.env.CONVEX_SITE_URL}${value.path.replaceAll(/[`$\\]/g, "\\$&")}\``;
211
+ /**
212
+ * `convex/auth.config.ts`: Convex's provider list, with site URLs resolved
213
+ * there.
214
+ */
215
+ const authConfig = (providers) => [
216
+ header("confect/auth.ts"),
217
+ "import type { AuthConfig } from \"convex/server\";",
218
+ "",
219
+ "export default {",
220
+ providers.length === 0 ? " providers: []," : " providers: [",
221
+ ...providers.length === 0 ? [] : [...providers.flatMap((provider) => [
222
+ " {",
223
+ ...provider.map(([field, value]) => ` ${field}: ${providerValue(value)},`),
224
+ " },"
225
+ ]), " ],"],
226
+ "} satisfies AuthConfig;",
227
+ ""
228
+ ].join("\n");
229
+ /**
230
+ * `confect/_generated/manifest.ts`: plain data, no imports, for infra.
231
+ */
232
+ const manifest = (declaration, digest) => {
233
+ const data = {
234
+ name: declaration.name,
235
+ env: Object.fromEntries(declaration.env),
236
+ http: { mounts: Object.fromEntries(declaration.mounts.flatMap(({ name, httpPrefix }) => httpPrefix === void 0 ? [] : [[name, httpPrefix]])) },
237
+ digest
238
+ };
239
+ return `${header("confect/env.ts and confect/app.ts")}\n\nexport default ${JSON.stringify(data, null, 2)} as const;\n`;
240
+ };
241
+ /**
242
+ * A digest of the declaration's inputs: every first-party file its modules
243
+ * import (by project-relative path and contents) and the versions of the
244
+ * packages that shape the generated output.
245
+ */
246
+ const digest = (input) => {
247
+ const hash = createHash("sha256");
248
+ const byFirst = (a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
249
+ hash.update(JSON.stringify({
250
+ files: [...input.files].sort(byFirst),
251
+ versions: [...input.versions].sort(byFirst)
252
+ }));
253
+ return `sha256:${hash.digest("hex")}`;
254
+ };
255
+ //#endregion
256
+ export { AuthForAnotherBackend, ForeignEnvBinding, InvalidBinding, InvalidEnvKey, InvalidMountName, NotADeclaration, UnknownComponentImport, authConfig, convexConfig, digest, fileOf, header, isGenerated, manifest, parse };
257
+
258
+ //# sourceMappingURL=Declaration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Declaration.mjs","names":[],"sources":["../src/Declaration.ts"],"sourcesContent":["import { App, Auth, Env } from \"@confect/core\";\nimport * as Data from \"effect/Data\";\nimport * as Option from \"effect/Option\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as Result from \"effect/Result\";\n// oxlint-disable-next-line effecttsgo/node-builtin-import -- a synchronous hash of in-memory strings in a pure function; Effect's Crypto is a service.\nimport { createHash } from \"node:crypto\";\n\n/**\n * A backend's declaration (`confect/env.ts`, `app.ts`, `auth.ts`) reduced to\n * the plain data codegen renders. The evaluated modules are parsed into this\n * once, at {@link parse}; every renderer below is a pure function of it.\n */\nexport interface Declaration {\n readonly target: \"app\" | \"component\";\n readonly name: string;\n readonly env: ReadonlyArray<readonly [string, Env.Descriptor]>;\n readonly mounts: ReadonlyArray<Mount>;\n readonly providers: Option.Option<ReadonlyArray<Provider>>;\n}\n\nexport interface Mount {\n readonly name: string;\n readonly importPath: string;\n readonly componentName: string;\n readonly httpPrefix: string | undefined;\n readonly env: ReadonlyArray<readonly [string, Binding]>;\n}\n\nexport type Binding =\n | { readonly kind: \"value\"; readonly value: string }\n | { readonly kind: \"env\"; readonly key: string };\n\nexport type ProviderValue =\n | { readonly kind: \"value\"; readonly value: string }\n | { readonly kind: \"siteUrl\"; readonly path: string };\n\nexport type Provider = ReadonlyArray<readonly [string, ProviderValue]>;\n\nexport class NotADeclaration extends Data.TaggedError(\"NotADeclaration\")<{\n readonly file: string;\n readonly expected: string;\n}> {\n override get message(): string {\n return `${this.file} must default-export ${this.expected}.`;\n }\n}\n\nexport class InvalidEnvKey extends Data.TaggedError(\"InvalidEnvKey\")<{\n readonly key: string;\n}> {\n override get message(): string {\n return `\"${this.key}\" is not a valid Convex environment variable name; use letters, digits and underscores, starting with a letter.`;\n }\n}\n\nexport class InvalidMountName extends Data.TaggedError(\"InvalidMountName\")<{\n readonly mount: string;\n}> {\n override get message(): string {\n return `\"${this.mount}\" is not a valid mount name; use letters, digits and underscores, starting with a letter or underscore.`;\n }\n}\n\nexport class ForeignEnvBinding extends Data.TaggedError(\"ForeignEnvBinding\")<{\n readonly mount: string;\n readonly slot: string;\n readonly backend: string;\n readonly key: string;\n readonly expectedBackend: string;\n}> {\n override get message(): string {\n return `The mount \"${this.mount}\" binds ${this.slot} to ${this.key} of the \"${this.backend}\" env, but this backend's env is \"${this.expectedBackend}\".`;\n }\n}\n\nexport class InvalidBinding extends Data.TaggedError(\"InvalidBinding\")<{\n readonly mount: string;\n readonly slot: string;\n}> {\n override get message(): string {\n return `The mount \"${this.mount}\" binds ${this.slot} to something other than a string or an entry of this backend's env.`;\n }\n}\n\nexport class UnknownComponentImport extends Data.TaggedError(\n \"UnknownComponentImport\",\n)<{ readonly mount: string }> {\n override get message(): string {\n return `The mount \"${this.mount}\" must pass the default export of a component's convex.config module, imported directly in the declaration.`;\n }\n}\n\nexport class AuthForAnotherBackend extends Data.TaggedError(\n \"AuthForAnotherBackend\",\n)<{ readonly backend: string; readonly expectedBackend: string }> {\n override get message(): string {\n return `confect/auth.ts declares auth for the \"${this.backend}\" backend, but confect/app.ts declares \"${this.expectedBackend}\".`;\n }\n}\n\nexport type DeclarationError =\n | NotADeclaration\n | InvalidEnvKey\n | InvalidMountName\n | ForeignEnvBinding\n | InvalidBinding\n | UnknownComponentImport\n | AuthForAnotherBackend;\n\nconst ENV_KEY = /^[A-Za-z][A-Za-z0-9_]*$/;\nconst MOUNT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * The declaration file a parse error is about.\n */\nexport const fileOf = (error: DeclarationError): string =>\n error._tag === \"NotADeclaration\"\n ? error.file\n : error._tag === \"AuthForAnotherBackend\"\n ? \"confect/auth.ts\"\n : \"confect/app.ts\";\n\n/**\n * A mounted component's import as the generated `convex.config.ts` writes it.\n */\nexport interface ComponentImport {\n readonly importPath: string;\n readonly componentName: string;\n}\n\n/**\n * Parse the default exports of `confect/app.ts` and, when present,\n * `confect/auth.ts`. `componentImport` recovers how the generated config\n * imports a mounted definition; codegen's bundler records it.\n */\nexport const parse = (input: {\n readonly target: \"app\" | \"component\";\n readonly app: unknown;\n readonly auth: Option.Option<unknown>;\n readonly componentImport: (\n definition: unknown,\n ) => Option.Option<ComponentImport>;\n}): Result.Result<Declaration, DeclarationError> =>\n Result.gen(function* () {\n if (!App.isApp(input.app)) {\n return yield* Result.fail(\n new NotADeclaration({\n file: \"confect/app.ts\",\n expected: \"App.make(...)\",\n }),\n );\n }\n const env = App.env(input.app);\n const name = Env.name(env);\n const descriptors = Env.descriptors(env);\n for (const [key] of descriptors) {\n if (!ENV_KEY.test(key))\n return yield* Result.fail(new InvalidEnvKey({ key }));\n }\n\n const mounts: Array<Mount> = [];\n for (const mount of App.mounts(input.app)) {\n if (!MOUNT_NAME.test(mount.name)) {\n return yield* Result.fail(new InvalidMountName({ mount: mount.name }));\n }\n const component = input.componentImport(mount.definition);\n if (Option.isNone(component)) {\n return yield* Result.fail(\n new UnknownComponentImport({ mount: mount.name }),\n );\n }\n const bindings: Array<readonly [string, Binding]> = [];\n for (const [slot, bound] of Object.entries(mount.env)) {\n if (bound === undefined) continue;\n if (typeof bound === \"string\") {\n bindings.push([slot, { kind: \"value\", value: bound }]);\n continue;\n }\n if (!Env.isEntry(bound)) {\n return yield* Result.fail(\n new InvalidBinding({ mount: mount.name, slot }),\n );\n }\n const origin = Env.origin(bound);\n if (origin.backend !== name) {\n return yield* Result.fail(\n new ForeignEnvBinding({\n mount: mount.name,\n slot,\n backend: origin.backend,\n key: origin.key,\n expectedBackend: name,\n }),\n );\n }\n bindings.push([slot, { kind: \"env\", key: origin.key }]);\n }\n mounts.push({\n name: mount.name,\n importPath: component.value.importPath,\n componentName: component.value.componentName,\n httpPrefix: mount.httpPrefix,\n env: bindings,\n });\n }\n\n const providers: Option.Option<ReadonlyArray<Provider>> = Option.isNone(\n input.auth,\n )\n ? Option.none()\n : Option.some(yield* parseProviders(input.auth.value, name));\n\n return {\n target: input.target,\n name,\n env: descriptors,\n mounts,\n providers,\n };\n });\n\nconst parseProviders = (\n auth: unknown,\n backend: string,\n): Result.Result<ReadonlyArray<Provider>, DeclarationError> => {\n if (!Auth.isAuth(auth)) {\n return Result.fail(\n new NotADeclaration({\n file: \"confect/auth.ts\",\n expected: \"Auth.make(app, ...)\",\n }),\n );\n }\n const authBackend = Env.name(App.env(auth.app));\n if (authBackend !== backend) {\n return Result.fail(\n new AuthForAnotherBackend({\n backend: authBackend,\n expectedBackend: backend,\n }),\n );\n }\n return Result.succeed(\n Auth.providers(auth).map((provider) =>\n Object.entries(provider).map(([field, value]) => {\n const sitePath = Auth.siteUrlPath(value);\n return [\n field,\n sitePath === undefined\n ? {\n kind: \"value\",\n value: Predicate.isString(value)\n ? value\n : JSON.stringify(value),\n }\n : { kind: \"siteUrl\", path: sitePath },\n ] as const;\n }),\n ),\n );\n};\n\nconst quote = (value: string): string => JSON.stringify(value);\n\nexport const header = (source: string): string =>\n `// Generated by \\`confect codegen\\` from ${source}. Do not edit.`;\n\nconst GENERATED_PREFIX = \"// Generated by `confect codegen` from \";\n\n/**\n * Whether codegen may overwrite or remove an existing file: it carries the\n * generated header, or it is exactly one of the files an earlier codegen wrote\n * there (`legacy`).\n */\nexport const isGenerated = (\n contents: string,\n legacy: ReadonlyArray<string>,\n): boolean =>\n contents.startsWith(GENERATED_PREFIX) || legacy.includes(contents);\n\nconst validator = (descriptor: Env.Descriptor): string => {\n const base =\n descriptor.kind === \"literals\"\n ? descriptor.values.length === 1\n ? `v.literal(${quote(descriptor.values[0])})`\n : `v.union(${descriptor.values.map((value) => `v.literal(${quote(value)})`).join(\", \")})`\n : \"v.string()\";\n return descriptor.optional ? `v.optional(${base})` : base;\n};\n\nconst RESERVED = new Set(\n \"await break case catch class const continue debugger default delete do else enum export extends false finally for function if implements import in instanceof interface let new null package private protected public return static super switch this throw true try typeof var void while with yield\".split(\n \" \",\n ),\n);\n\n/**\n * A safe import name for a component, from its Convex name.\n */\nconst identifier = (name: string): string => {\n const cleaned = name.replaceAll(/[^A-Za-z0-9_$]/g, \"_\");\n const valid = /^[A-Za-z_$]/.test(cleaned) ? cleaned : `_${cleaned}`;\n return RESERVED.has(valid) ? `${valid}Component` : valid;\n};\n\n/**\n * `convex/convex.config.ts`: Convex's own app or component definition.\n */\nexport const convexConfig = (declaration: Declaration): string => {\n const self = declaration.target === \"app\" ? \"app\" : \"component\";\n const aliases = new Map<string, string>();\n const taken = new Set([self, \"v\", \"defineApp\", \"defineComponent\"]);\n for (const mount of declaration.mounts) {\n if (aliases.has(mount.importPath)) continue;\n const base = identifier(mount.componentName);\n let alias = base;\n for (let n = 2; taken.has(alias); n++) alias = `${base}${n}`;\n taken.add(alias);\n aliases.set(mount.importPath, alias);\n }\n\n const lines = [header(\"confect/app.ts\")];\n lines.push(\n declaration.target === \"app\"\n ? 'import { defineApp } from \"convex/server\";'\n : 'import { defineComponent } from \"convex/server\";',\n );\n if (declaration.env.length > 0)\n lines.push('import { v } from \"convex/values\";');\n for (const [importPath, alias] of aliases) {\n lines.push(`import ${alias} from ${quote(importPath)};`);\n }\n lines.push(\"\");\n\n const envBlock =\n declaration.env.length === 0\n ? []\n : [\n \" env: {\",\n ...declaration.env.map(\n ([key, descriptor]) => ` ${key}: ${validator(descriptor)},`,\n ),\n \" },\",\n ];\n if (declaration.target === \"app\") {\n lines.push(\n envBlock.length === 0\n ? \"const app = defineApp();\"\n : [\"const app = defineApp({\", ...envBlock, \"});\"].join(\"\\n\"),\n );\n } else {\n lines.push(\n envBlock.length === 0\n ? `const component = defineComponent(${quote(declaration.name)});`\n : [\n `const component = defineComponent(${quote(declaration.name)}, {`,\n ...envBlock,\n \"});\",\n ].join(\"\\n\"),\n );\n }\n\n for (const mount of declaration.mounts) {\n lines.push(\"\");\n lines.push(`${self}.use(${aliases.get(mount.importPath)}, {`);\n lines.push(` name: ${quote(mount.name)},`);\n if (mount.httpPrefix !== undefined) {\n lines.push(` httpPrefix: ${quote(mount.httpPrefix)},`);\n }\n if (mount.env.length > 0) {\n lines.push(\" env: {\");\n for (const [slot, binding] of mount.env) {\n lines.push(\n ` ${slot}: ${binding.kind === \"value\" ? quote(binding.value) : `${self}.env.${binding.key}`},`,\n );\n }\n lines.push(\" },\");\n }\n lines.push(\"});\");\n }\n\n lines.push(\"\", `export default ${self};`, \"\");\n return lines.join(\"\\n\");\n};\n\nconst providerValue = (value: ProviderValue): string =>\n value.kind === \"value\"\n ? quote(value.value)\n : `\\`\\${process.env.CONVEX_SITE_URL}${value.path.replaceAll(/[`$\\\\]/g, \"\\\\$&\")}\\``;\n\n/**\n * `convex/auth.config.ts`: Convex's provider list, with site URLs resolved\n * there.\n */\nexport const authConfig = (providers: ReadonlyArray<Provider>): string =>\n [\n header(\"confect/auth.ts\"),\n 'import type { AuthConfig } from \"convex/server\";',\n \"\",\n \"export default {\",\n providers.length === 0 ? \" providers: [],\" : \" providers: [\",\n ...(providers.length === 0\n ? []\n : [\n ...providers.flatMap((provider) => [\n \" {\",\n ...provider.map(\n ([field, value]) => ` ${field}: ${providerValue(value)},`,\n ),\n \" },\",\n ]),\n \" ],\",\n ]),\n \"} satisfies AuthConfig;\",\n \"\",\n ].join(\"\\n\");\n\n/**\n * `confect/_generated/manifest.ts`: plain data, no imports, for infra.\n */\nexport const manifest = (declaration: Declaration, digest: string): string => {\n const data = {\n name: declaration.name,\n env: Object.fromEntries(declaration.env),\n http: {\n mounts: Object.fromEntries(\n declaration.mounts.flatMap(({ name, httpPrefix }) =>\n httpPrefix === undefined ? [] : [[name, httpPrefix]],\n ),\n ),\n },\n digest,\n };\n return `${header(\"confect/env.ts and confect/app.ts\")}\\n\\nexport default ${JSON.stringify(data, null, 2)} as const;\\n`;\n};\n\n/**\n * A digest of the declaration's inputs: every first-party file its modules\n * import (by project-relative path and contents) and the versions of the\n * packages that shape the generated output.\n */\nexport const digest = (input: {\n readonly files: ReadonlyArray<readonly [string, string]>;\n readonly versions: ReadonlyArray<readonly [string, string]>;\n}): string => {\n const hash = createHash(\"sha256\");\n const byFirst = (\n a: readonly [string, string],\n b: readonly [string, string],\n ) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);\n hash.update(\n JSON.stringify({\n files: [...input.files].sort(byFirst),\n versions: [...input.versions].sort(byFirst),\n }),\n );\n return `sha256:${hash.digest(\"hex\")}`;\n};\n"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { ProjectRoot, findProjectRootFrom, layer as layer$1 } from "./ProjectRoot.mjs";
2
+ import { ConvexDirectory, ConvexDirectoryNotFoundError, layer as layer$2 } from "./ConvexDirectory.mjs";
3
+ import { layerFromConvexDirectory } from "./ConfectDirectory.mjs";
4
+ import * as Effect from "effect/Effect";
5
+ import { IdScope } from "@confect/core";
6
+ import * as FileSystem from "effect/FileSystem";
7
+ import * as Path from "effect/Path";
8
+ import * as Option from "effect/Option";
9
+ import * as Schema from "effect/Schema";
10
+ import * as Layer from "effect/Layer";
11
+ import * as Flag from "effect/cli/Flag";
12
+ //#region src/DirectoryOptions.ts
13
+ const flags = { componentDir: Flag.String("component-dir").pipe(Flag.withDescription("Generate a component instead of the application; its sibling confect/ directory contains the source."), Flag.optional) };
14
+ const layer = ({ componentDir }) => {
15
+ const projectRootLayer = Option.match(componentDir, {
16
+ onNone: () => layer$1,
17
+ onSome: (directory) => Layer.effect(ProjectRoot, Effect.map(findProjectRootFrom(directory), (root) => ProjectRoot.of({ get: Effect.succeed(root) })))
18
+ });
19
+ const convex = Option.match(componentDir, {
20
+ onNone: () => layer$2,
21
+ onSome: (directory) => Layer.effect(ConvexDirectory, Effect.gen(function* () {
22
+ const path = yield* Path.Path;
23
+ const fs = yield* FileSystem.FileSystem;
24
+ const projectRoot = yield* ProjectRoot.get;
25
+ const absolute = path.resolve(directory);
26
+ if (!(yield* fs.exists(path.join(absolute, "convex.config.ts"))) && !(yield* fs.exists(path.join(path.dirname(absolute), "confect", "app.ts")))) return yield* new ConvexDirectoryNotFoundError();
27
+ const definition = `${(yield* fs.readFileString(path.join(projectRoot, "package.json")).pipe(Effect.flatMap(Schema.decodeEffect(Schema.fromJsonString(Schema.Struct({ name: Schema.String })))))).name}:${path.relative(projectRoot, absolute).split(path.sep).join("/")}`;
28
+ return ConvexDirectory.of({
29
+ get: Effect.succeed(absolute),
30
+ target: {
31
+ kind: "component",
32
+ scope: IdScope.component(definition)
33
+ }
34
+ });
35
+ })).pipe(Layer.provide(projectRootLayer))
36
+ });
37
+ return Layer.mergeAll(projectRootLayer, layerFromConvexDirectory.pipe(Layer.provideMerge(convex)));
38
+ };
39
+ //#endregion
40
+ export { flags, layer };
41
+
42
+ //# sourceMappingURL=DirectoryOptions.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DirectoryOptions.mjs","names":["ProjectRoot.layer","ProjectRoot.ProjectRoot","ProjectRoot.findProjectRootFrom","ConvexDirectory.layer","ConvexDirectory.ConvexDirectory","ConvexDirectory.ConvexDirectoryNotFoundError"],"sources":["../src/DirectoryOptions.ts"],"sourcesContent":["import { IdScope } from \"@confect/core\";\nimport * as Effect from \"effect/Effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Layer from \"effect/Layer\";\nimport * as Option from \"effect/Option\";\nimport * as Path from \"effect/Path\";\nimport * as Schema from \"effect/Schema\";\nimport * as Flag from \"effect/cli/Flag\";\nimport * as ConfectDirectory from \"./ConfectDirectory\";\nimport * as ConvexDirectory from \"./ConvexDirectory\";\nimport * as ProjectRoot from \"./ProjectRoot\";\n\nexport const flags = {\n componentDir: Flag.String(\"component-dir\").pipe(\n Flag.withDescription(\n \"Generate a component instead of the application; its sibling confect/ directory contains the source.\",\n ),\n Flag.optional,\n ),\n};\n\nexport const layer = ({\n componentDir,\n}: {\n componentDir: Option.Option<string>;\n}) => {\n const projectRootLayer = Option.match(componentDir, {\n onNone: () => ProjectRoot.layer,\n onSome: (directory) =>\n Layer.effect(\n ProjectRoot.ProjectRoot,\n Effect.map(ProjectRoot.findProjectRootFrom(directory), (root) =>\n ProjectRoot.ProjectRoot.of({\n get: Effect.succeed(root),\n }),\n ),\n ),\n });\n const convex: Layer.Layer<\n ConvexDirectory.ConvexDirectory,\n Layer.Error<typeof ConvexDirectory.layer> | Schema.SchemaError,\n FileSystem.FileSystem | Path.Path\n > = Option.match(componentDir, {\n onNone: () => ConvexDirectory.layer,\n onSome: (directory) =>\n Layer.effect(\n ConvexDirectory.ConvexDirectory,\n Effect.gen(function* () {\n const path = yield* Path.Path;\n const fs = yield* FileSystem.FileSystem;\n const projectRoot = yield* ProjectRoot.ProjectRoot.get;\n const absolute = path.resolve(directory);\n // A declared component's `convex.config.ts` is codegen output, so\n // its sibling `confect/app.ts` also marks the directory.\n if (\n !(yield* fs.exists(path.join(absolute, \"convex.config.ts\"))) &&\n !(yield* fs.exists(\n path.join(path.dirname(absolute), \"confect\", \"app.ts\"),\n ))\n ) {\n return yield* new ConvexDirectory.ConvexDirectoryNotFoundError();\n }\n const pkg = yield* fs\n .readFileString(path.join(projectRoot, \"package.json\"))\n .pipe(\n Effect.flatMap(\n Schema.decodeEffect(\n Schema.fromJsonString(Schema.Struct({ name: Schema.String })),\n ),\n ),\n );\n const definition = `${pkg.name}:${path.relative(projectRoot, absolute).split(path.sep).join(\"/\")}`;\n return ConvexDirectory.ConvexDirectory.of({\n get: Effect.succeed(absolute),\n target: {\n kind: \"component\",\n scope: IdScope.component(definition),\n },\n });\n }),\n ).pipe(Layer.provide(projectRootLayer)),\n });\n return Layer.mergeAll(\n projectRootLayer,\n ConfectDirectory.layerFromConvexDirectory.pipe(Layer.provideMerge(convex)),\n );\n};\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,QAAQ,EACnB,cAAc,KAAK,OAAO,eAAe,CAAC,CAAC,KACzC,KAAK,gBACH,sGACF,GACA,KAAK,QACP,EACF;AAEA,MAAa,SAAS,EACpB,mBAGI;CACJ,MAAM,mBAAmB,OAAO,MAAM,cAAc;EAClD,cAAcA;EACd,SAAS,cACP,MAAM,OACJC,aACA,OAAO,IAAIC,oBAAgC,SAAS,IAAI,SAAA,YAC9B,GAAG,EACzB,KAAK,OAAO,QAAQ,IAAI,EAC1B,CAAC,CACH,CACF;CACJ,CAAC;CACD,MAAM,SAIF,OAAO,MAAM,cAAc;EAC7B,cAAcC;EACd,SAAS,cACP,MAAM,OACJC,iBACA,OAAO,IAAI,aAAa;GACtB,MAAM,OAAO,OAAO,KAAK;GACzB,MAAM,KAAK,OAAO,WAAW;GAC7B,MAAM,cAAc,OAAA,YAA+B;GACnD,MAAM,WAAW,KAAK,QAAQ,SAAS;GAGvC,IACE,EAAE,OAAO,GAAG,OAAO,KAAK,KAAK,UAAU,kBAAkB,CAAC,MAC1D,EAAE,OAAO,GAAG,OACV,KAAK,KAAK,KAAK,QAAQ,QAAQ,GAAG,WAAW,QAAQ,CACvD,IAEA,OAAO,OAAO,IAAIC,6BAA6C;GAWjE,MAAM,aAAa,IAAG,OATH,GAChB,eAAe,KAAK,KAAK,aAAa,cAAc,CAAC,CAAC,CACtD,KACC,OAAO,QACL,OAAO,aACL,OAAO,eAAe,OAAO,OAAO,EAAE,MAAM,OAAO,OAAO,CAAC,CAAC,CAC9D,CACF,CACF,EAAA,CACwB,KAAK,GAAG,KAAK,SAAS,aAAa,QAAQ,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG;GAC/F,OAAA,gBAAuC,GAAG;IACxC,KAAK,OAAO,QAAQ,QAAQ;IAC5B,QAAQ;KACN,MAAM;KACN,OAAO,QAAQ,UAAU,UAAU;IACrC;GACF,CAAC;EACH,CAAC,CACH,CAAC,CAAC,KAAK,MAAM,QAAQ,gBAAgB,CAAC;CAC1C,CAAC;CACD,OAAO,MAAM,SACX,kBAAA,yBAC0C,KAAK,MAAM,aAAa,MAAM,CAAC,CAC3E;AACF"}
@@ -0,0 +1,25 @@
1
+ import * as Array from "effect/Array";
2
+ import { pipe } from "effect/Function";
3
+ import * as String from "effect/String";
4
+ import * as Brand from "effect/Brand";
5
+ //#region src/DocName.ts
6
+ const DocName = Brand.nominal();
7
+ /**
8
+ * Convert a Convex table name to the name of its generated document type in
9
+ * `confect/_generated/docs.ts`.
10
+ *
11
+ * The table name is split on underscores and the first letter of each segment
12
+ * is upper-cased, then a `Doc` suffix is appended—so both `snake_case` and
13
+ * `camelCase` spellings of a table fold to the same idiomatic PascalCase type
14
+ * name (`user_profiles` and `userProfiles` both become `UserProfilesDoc`). That
15
+ * folding can make two distinct tables map to the same document name; codegen
16
+ * guards against it (see `validateNoDocNameCollisions`).
17
+ *
18
+ * Note this name is purely cosmetic: the `Docs` registry is keyed by the
19
+ * verbatim table name, which is what document lookups index through.
20
+ */
21
+ const fromTableName = (tableName) => DocName(pipe(tableName, String.split("_"), Array.filter(String.isNonEmpty), Array.map(String.capitalize), Array.join(""), String.concat("Doc")));
22
+ //#endregion
23
+ export { fromTableName };
24
+
25
+ //# sourceMappingURL=DocName.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocName.mjs","names":[],"sources":["../src/DocName.ts"],"sourcesContent":["import * as Array from \"effect/Array\";\nimport * as Brand from \"effect/Brand\";\nimport { pipe } from \"effect/Function\";\nimport * as String from \"effect/String\";\n\n/**\n * The name of a table's generated document type in `confect/_generated/docs.ts`\n * (e.g. `UserProfilesDoc`). Branded so it can't be confused with an arbitrary\n * string—construct it with {@link fromTableName}.\n */\nexport type DocName = string & Brand.Brand<\"DocName\">;\n\nconst DocName = Brand.nominal<DocName>();\n\n/**\n * Convert a Convex table name to the name of its generated document type in\n * `confect/_generated/docs.ts`.\n *\n * The table name is split on underscores and the first letter of each segment\n * is upper-cased, then a `Doc` suffix is appended—so both `snake_case` and\n * `camelCase` spellings of a table fold to the same idiomatic PascalCase type\n * name (`user_profiles` and `userProfiles` both become `UserProfilesDoc`). That\n * folding can make two distinct tables map to the same document name; codegen\n * guards against it (see `validateNoDocNameCollisions`).\n *\n * Note this name is purely cosmetic: the `Docs` registry is keyed by the\n * verbatim table name, which is what document lookups index through.\n */\nexport const fromTableName = (tableName: string): DocName =>\n DocName(\n pipe(\n tableName,\n String.split(\"_\"),\n Array.filter(String.isNonEmpty),\n Array.map(String.capitalize),\n Array.join(\"\"),\n String.concat(\"Doc\"),\n ),\n );\n"],"mappings":";;;;;AAYA,MAAM,UAAU,MAAM,QAAiB;;;;;;;;;;;;;;;AAgBvC,MAAa,iBAAiB,cAC5B,QACE,KACE,WACA,OAAO,MAAM,GAAG,GAChB,MAAM,OAAO,OAAO,UAAU,GAC9B,MAAM,IAAI,OAAO,UAAU,GAC3B,MAAM,KAAK,EAAE,GACb,OAAO,OAAO,KAAK,CACrB,CACF"}
@@ -0,0 +1,111 @@
1
+ import { ConvexDirectory } from "./ConvexDirectory.mjs";
2
+ import { ConfectDirectory } from "./ConfectDirectory.mjs";
3
+ import { CONVEX_CONFIG_FILENAME } from "./ConvexConfig.mjs";
4
+ import { ImplModulePath } from "./ImplModulePath.mjs";
5
+ import { SpecModulePath, toImpl } from "./SpecModulePath.mjs";
6
+ import { TableModulePath } from "./TableModulePath.mjs";
7
+ import { discoverLeafImplFiles, discoverLeafSpecFiles } from "./LeafModule.mjs";
8
+ import { discoverTableFiles } from "./TableModule.mjs";
9
+ import * as Effect from "effect/Effect";
10
+ import * as FileSystem from "effect/FileSystem";
11
+ import * as Path from "effect/Path";
12
+ import * as Array from "effect/Array";
13
+ import * as HashSet from "effect/HashSet";
14
+ import * as Option from "effect/Option";
15
+ import * as Chunk from "effect/Chunk";
16
+ import * as Schema from "effect/Schema";
17
+ import * as Predicate from "effect/Predicate";
18
+ import * as Data from "effect/Data";
19
+ //#region src/EntryPoint.ts
20
+ const Kind = Data.taggedEnum();
21
+ const Declaration = Schema.TaggedUnion({
22
+ Spec: { relativePath: SpecModulePath },
23
+ Table: { relativePath: TableModulePath }
24
+ });
25
+ const planDeclarations = (declarations, affectedImpls, path) => {
26
+ const plan = Array.reduce(declarations, {
27
+ tables: Chunk.empty(),
28
+ paths: HashSet.fromIterable(affectedImpls)
29
+ }, (state, declaration) => Declaration.match(declaration, {
30
+ Spec: ({ relativePath }) => ({
31
+ ...state,
32
+ paths: HashSet.add(state.paths, toImpl(relativePath))
33
+ }),
34
+ Table: ({ relativePath }) => ({
35
+ ...state,
36
+ tables: Chunk.append(state.tables, {
37
+ relativePath,
38
+ tableName: path.parse(relativePath).name
39
+ })
40
+ })
41
+ }));
42
+ return {
43
+ tables: Chunk.toReadonlyArray(plan.tables),
44
+ paths: Array.fromIterable(plan.paths)
45
+ };
46
+ };
47
+ const isOptional = Predicate.or(Kind.$is("Http"), Predicate.or(Kind.$is("Crons"), Kind.$is("Auth")));
48
+ const parse = (relativePath) => {
49
+ if (relativePath === "env.ts") return Option.some(Kind.Env({ relativePath }));
50
+ if (relativePath === "app.ts") return Option.some(Kind.App({ relativePath }));
51
+ if (relativePath === "callbacks.ts") return Option.some(Kind.Callbacks({ relativePath }));
52
+ if (relativePath === "http.ts") return Option.some(Kind.Http({ relativePath }));
53
+ if (relativePath === "crons.ts") return Option.some(Kind.Crons({ relativePath }));
54
+ if (relativePath === "auth.ts") return Option.some(Kind.Auth({ relativePath }));
55
+ const tablePath = Schema.decodeOption(TableModulePath)(relativePath);
56
+ if (Option.isSome(tablePath)) return Option.some(Kind.Table({ relativePath: tablePath.value }));
57
+ const specPath = Schema.decodeOption(SpecModulePath)(relativePath);
58
+ if (Option.isSome(specPath)) return Option.some(Kind.Spec({ relativePath: specPath.value }));
59
+ return Schema.decodeOption(ImplModulePath)(relativePath).pipe(Option.map((implPath) => Kind.Impl({ relativePath: implPath })));
60
+ };
61
+ const discover = Effect.gen(function* () {
62
+ const fs = yield* FileSystem.FileSystem;
63
+ const path = yield* Path.Path;
64
+ const confectDirectory = yield* ConfectDirectory.get;
65
+ const tryEntry = Effect.fnUntraced(function* (kind) {
66
+ const absolutePath = path.join(confectDirectory, kind.relativePath);
67
+ if (!(yield* fs.exists(absolutePath))) return Option.none();
68
+ return Option.some({
69
+ ...kind,
70
+ absolutePath,
71
+ inputPath: yield* fs.realPath(absolutePath).pipe(Effect.orElseSucceed(() => absolutePath))
72
+ });
73
+ });
74
+ const declaration = yield* tryEntry(Kind.App({ relativePath: "app.ts" }));
75
+ const fixedEntryOptions = yield* Effect.all([
76
+ tryEntry(Kind.Env({ relativePath: "env.ts" })),
77
+ Effect.succeed(declaration),
78
+ (yield* ConvexDirectory.target).kind === "component" ? tryEntry(Kind.Callbacks({ relativePath: "callbacks.ts" })) : Effect.succeedNone,
79
+ tryEntry(Kind.Http({ relativePath: "http.ts" })),
80
+ tryEntry(Kind.Crons({ relativePath: "crons.ts" })),
81
+ tryEntry(Kind.Auth({ relativePath: "auth.ts" }))
82
+ ]);
83
+ const moduleRelativePaths = yield* Effect.all([
84
+ discoverLeafSpecFiles,
85
+ discoverLeafImplFiles,
86
+ discoverTableFiles
87
+ ]).pipe(Effect.map(Array.flatten));
88
+ const moduleEntryOptions = yield* Effect.forEach(Array.getSomes(moduleRelativePaths.map(parse)), tryEntry);
89
+ const convexDirectory = yield* ConvexDirectory.get;
90
+ const convexConfigEntryOption = yield* Effect.gen(function* () {
91
+ const absolutePath = path.join(convexDirectory, CONVEX_CONFIG_FILENAME);
92
+ if (Option.isSome(declaration) || !(yield* fs.exists(absolutePath))) return Option.none();
93
+ return Option.some({
94
+ ...Kind.Config(),
95
+ absolutePath,
96
+ inputPath: yield* fs.realPath(absolutePath).pipe(Effect.orElseSucceed(() => absolutePath))
97
+ });
98
+ });
99
+ return Array.getSomes([
100
+ ...fixedEntryOptions,
101
+ convexConfigEntryOption,
102
+ ...moduleEntryOptions
103
+ ]);
104
+ }).pipe(Effect.retry({
105
+ times: 3,
106
+ while: (error) => error._tag === "PlatformError" && error.reason._tag === "NotFound"
107
+ }));
108
+ //#endregion
109
+ export { Declaration, Kind, discover, isOptional, parse, planDeclarations };
110
+
111
+ //# sourceMappingURL=EntryPoint.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntryPoint.mjs","names":["SpecModulePath.SpecModulePath","SpecModulePath.toImpl"],"sources":["../src/EntryPoint.ts"],"sourcesContent":["import * as Array from \"effect/Array\";\nimport * as Chunk from \"effect/Chunk\";\nimport * as Data from \"effect/Data\";\nimport * as Effect from \"effect/Effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\nimport * as Path from \"effect/Path\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as Schema from \"effect/Schema\";\nimport { ImplModulePath } from \"./ImplModulePath\";\nimport * as SpecModulePath from \"./SpecModulePath\";\nimport { TableModulePath } from \"./TableModulePath\";\nimport { ConfectDirectory } from \"./ConfectDirectory\";\nimport { ConvexDirectory } from \"./ConvexDirectory\";\nimport { CONVEX_CONFIG_FILENAME } from \"./ConvexConfig\";\nimport { discoverLeafImplFiles, discoverLeafSpecFiles } from \"./LeafModule\";\nimport { discoverTableFiles, type TableModule } from \"./TableModule\";\n\nexport type Kind = Data.TaggedEnum<{\n Env: { readonly relativePath: \"env.ts\" };\n App: { readonly relativePath: \"app.ts\" };\n Callbacks: { readonly relativePath: \"callbacks.ts\" };\n Spec: { readonly relativePath: SpecModulePath.SpecModulePath };\n Impl: { readonly relativePath: ImplModulePath };\n Table: { readonly relativePath: TableModulePath };\n Http: { readonly relativePath: \"http.ts\" };\n Crons: { readonly relativePath: \"crons.ts\" };\n Auth: { readonly relativePath: \"auth.ts\" };\n Config: {};\n}>;\n\nexport const Kind = Data.taggedEnum<Kind>();\nexport const Declaration = Schema.TaggedUnion({\n Spec: { relativePath: SpecModulePath.SpecModulePath },\n Table: { relativePath: TableModulePath },\n});\nexport type Declaration = typeof Declaration.Type;\n\nexport const planDeclarations = (\n declarations: Array.NonEmptyReadonlyArray<Declaration>,\n affectedImpls: ReadonlyArray<ImplModulePath>,\n path: Path.Path,\n): {\n readonly tables: ReadonlyArray<TableModule>;\n readonly paths: ReadonlyArray<ImplModulePath>;\n} => {\n const plan = Array.reduce(\n declarations,\n {\n tables: Chunk.empty<TableModule>(),\n paths: HashSet.fromIterable(affectedImpls),\n },\n (state, declaration) =>\n Declaration.match(declaration, {\n Spec: ({ relativePath }) => ({\n ...state,\n paths: HashSet.add(state.paths, SpecModulePath.toImpl(relativePath)),\n }),\n Table: ({ relativePath }) => ({\n ...state,\n tables: Chunk.append(state.tables, {\n relativePath,\n tableName: path.parse(relativePath).name,\n }),\n }),\n }),\n );\n return {\n tables: Chunk.toReadonlyArray(plan.tables),\n paths: Array.fromIterable(plan.paths),\n };\n};\n\nexport type EntryPoint = Kind & {\n readonly absolutePath: string;\n readonly inputPath: string;\n};\nexport type OptionalFile = \"Http\" | \"Crons\" | \"Auth\";\n\nexport const isOptional = Predicate.or(\n Kind.$is(\"Http\"),\n Predicate.or(Kind.$is(\"Crons\"), Kind.$is(\"Auth\")),\n);\n\nexport const parse = (\n relativePath: string,\n): Option.Option<Exclude<Kind, { readonly _tag: \"Config\" }>> => {\n if (relativePath === \"env.ts\") return Option.some(Kind.Env({ relativePath }));\n if (relativePath === \"app.ts\") return Option.some(Kind.App({ relativePath }));\n if (relativePath === \"callbacks.ts\")\n return Option.some(Kind.Callbacks({ relativePath }));\n if (relativePath === \"http.ts\")\n return Option.some(Kind.Http({ relativePath }));\n if (relativePath === \"crons.ts\")\n return Option.some(Kind.Crons({ relativePath }));\n if (relativePath === \"auth.ts\")\n return Option.some(Kind.Auth({ relativePath }));\n const tablePath = Schema.decodeOption(TableModulePath)(relativePath);\n if (Option.isSome(tablePath))\n return Option.some(Kind.Table({ relativePath: tablePath.value }));\n const specPath = Schema.decodeOption(SpecModulePath.SpecModulePath)(\n relativePath,\n );\n if (Option.isSome(specPath))\n return Option.some(Kind.Spec({ relativePath: specPath.value }));\n return Schema.decodeOption(ImplModulePath)(relativePath).pipe(\n Option.map((implPath) => Kind.Impl({ relativePath: implPath })),\n );\n};\n\nexport const discover = Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const confectDirectory = yield* ConfectDirectory.get;\n\n const tryEntry = Effect.fnUntraced(function* (\n kind: Exclude<Kind, { readonly _tag: \"Config\" }>,\n ) {\n const absolutePath = path.join(confectDirectory, kind.relativePath);\n if (!(yield* fs.exists(absolutePath))) {\n return Option.none<EntryPoint>();\n }\n return Option.some<EntryPoint>({\n ...kind,\n absolutePath,\n inputPath: yield* fs\n .realPath(absolutePath)\n .pipe(Effect.orElseSucceed(() => absolutePath)),\n });\n });\n\n const declaration = yield* tryEntry(Kind.App({ relativePath: \"app.ts\" }));\n const fixedEntryOptions = yield* Effect.all([\n tryEntry(Kind.Env({ relativePath: \"env.ts\" })),\n Effect.succeed(declaration),\n (yield* ConvexDirectory.target).kind === \"component\"\n ? tryEntry(Kind.Callbacks({ relativePath: \"callbacks.ts\" }))\n : Effect.succeedNone,\n tryEntry(Kind.Http({ relativePath: \"http.ts\" })),\n tryEntry(Kind.Crons({ relativePath: \"crons.ts\" })),\n tryEntry(Kind.Auth({ relativePath: \"auth.ts\" })),\n ]);\n\n const moduleRelativePaths = yield* Effect.all([\n discoverLeafSpecFiles,\n discoverLeafImplFiles,\n discoverTableFiles,\n ]).pipe(Effect.map(Array.flatten));\n const moduleEntryOptions = yield* Effect.forEach(\n Array.getSomes(moduleRelativePaths.map(parse)),\n tryEntry,\n );\n\n // `convex/convex.config.ts` feeds the generated components registry, so\n // edits to it (or to a locally-defined component definition it imports—npm component definitions are externalized and not watched) must re-run\n // codegen.\n const convexDirectory = yield* ConvexDirectory.get;\n const convexConfigEntryOption = yield* Effect.gen(function* () {\n const absolutePath = path.join(convexDirectory, CONVEX_CONFIG_FILENAME);\n // A declared backend's `convex.config.ts` is codegen output, not input.\n if (Option.isSome(declaration) || !(yield* fs.exists(absolutePath))) {\n return Option.none<EntryPoint>();\n }\n return Option.some<EntryPoint>({\n ...Kind.Config(),\n absolutePath,\n inputPath: yield* fs\n .realPath(absolutePath)\n .pipe(Effect.orElseSucceed(() => absolutePath)),\n });\n });\n\n return Array.getSomes([\n ...fixedEntryOptions,\n convexConfigEntryOption,\n ...moduleEntryOptions,\n ]);\n}).pipe(\n Effect.retry({\n times: 3,\n while: (error) =>\n error._tag === \"PlatformError\" && error.reason._tag === \"NotFound\",\n }),\n);\n"],"mappings":"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"}