@confect/core 10.0.0-next.15 → 10.0.0-next.18

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 (70) hide show
  1. package/CHANGELOG.md +246 -0
  2. package/dist/FunctionProvenance.d.ts +18 -18
  3. package/dist/FunctionProvenance.d.ts.map +1 -1
  4. package/dist/FunctionProvenance.js +1 -1
  5. package/dist/FunctionProvenance.js.map +1 -1
  6. package/dist/FunctionSpec.d.ts +54 -48
  7. package/dist/FunctionSpec.d.ts.map +1 -1
  8. package/dist/FunctionSpec.js +23 -5
  9. package/dist/FunctionSpec.js.map +1 -1
  10. package/dist/GroupPath.d.ts +1 -4
  11. package/dist/GroupPath.d.ts.map +1 -1
  12. package/dist/GroupPath.js.map +1 -1
  13. package/dist/GroupSpec.d.ts +16 -10
  14. package/dist/GroupSpec.d.ts.map +1 -1
  15. package/dist/GroupSpec.js +34 -11
  16. package/dist/GroupSpec.js.map +1 -1
  17. package/dist/Lazy.d.ts +2 -3
  18. package/dist/Lazy.d.ts.map +1 -1
  19. package/dist/Lazy.js +2 -3
  20. package/dist/Lazy.js.map +1 -1
  21. package/dist/MiddlewareSpec.d.ts +184 -0
  22. package/dist/MiddlewareSpec.d.ts.map +1 -0
  23. package/dist/MiddlewareSpec.js +55 -0
  24. package/dist/MiddlewareSpec.js.map +1 -0
  25. package/dist/Ref.d.ts +53 -37
  26. package/dist/Ref.d.ts.map +1 -1
  27. package/dist/Ref.js +53 -35
  28. package/dist/Ref.js.map +1 -1
  29. package/dist/Refs.d.ts +8 -3
  30. package/dist/Refs.d.ts.map +1 -1
  31. package/dist/Refs.js +3 -3
  32. package/dist/Refs.js.map +1 -1
  33. package/dist/SchemaToValidator.d.ts +99 -0
  34. package/dist/SchemaToValidator.d.ts.map +1 -0
  35. package/dist/SchemaToValidator.js +156 -0
  36. package/dist/SchemaToValidator.js.map +1 -0
  37. package/dist/Spec.d.ts +3 -2
  38. package/dist/Spec.d.ts.map +1 -1
  39. package/dist/Spec.js +1 -1
  40. package/dist/Spec.js.map +1 -1
  41. package/dist/Table.d.ts +74 -0
  42. package/dist/Table.d.ts.map +1 -0
  43. package/dist/Table.js +77 -0
  44. package/dist/Table.js.map +1 -0
  45. package/dist/Types.d.ts +8 -2
  46. package/dist/Types.d.ts.map +1 -1
  47. package/dist/Types.js.map +1 -1
  48. package/dist/index.d.ts +3 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +4 -2
  51. package/package.json +9 -8
  52. package/src/FunctionProvenance.ts +8 -8
  53. package/src/FunctionSpec.ts +93 -14
  54. package/src/GroupPath.ts +1 -6
  55. package/src/GroupSpec.ts +125 -18
  56. package/src/Lazy.ts +2 -3
  57. package/src/MiddlewareSpec.ts +356 -0
  58. package/src/Ref.ts +310 -205
  59. package/src/Refs.ts +40 -16
  60. package/src/SchemaToValidator.ts +601 -0
  61. package/src/Spec.ts +4 -4
  62. package/src/Table.ts +426 -0
  63. package/src/Types.ts +9 -3
  64. package/src/index.ts +3 -1
  65. package/dist/Registry.d.ts +0 -24
  66. package/dist/Registry.d.ts.map +0 -1
  67. package/dist/Registry.js +0 -19
  68. package/dist/Registry.js.map +0 -1
  69. package/dist/tsconfig.src.tsbuildinfo +0 -1
  70. package/src/Registry.ts +0 -30
package/src/GroupSpec.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import * as Predicate from "effect/Predicate";
2
2
  import * as Record from "effect/Record";
3
3
  import type * as FunctionSpec from "./FunctionSpec";
4
+ import type * as MiddlewareSpec from "./MiddlewareSpec";
4
5
  import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
5
6
  import { validateConfectFunctionIdentifier } from "./Identifier";
6
7
 
7
- export const TypeId = "@confect/core/GroupSpec";
8
+ export const TypeId = "~@confect/core/GroupSpec";
8
9
  export type TypeId = typeof TypeId;
9
10
 
10
11
  export const isGroupSpec = (u: unknown): u is AnyWithProps =>
@@ -19,31 +20,65 @@ export interface GroupSpec<
19
20
  // modules. Functions, by contrast, stay homogeneous (a Node group only accepts
20
21
  // Node actions) — `addFunction` keeps the `<Runtime>` bound below.
21
22
  Groups_ extends AnyWithProps = never,
23
+ MiddlewareSpecs_ extends MiddlewareSpec.AnyMiddlewareSpec = never,
22
24
  > {
23
25
  readonly [TypeId]: TypeId;
24
26
  readonly runtime: Runtime;
25
27
  readonly name: Name_;
26
28
  readonly functions: {
27
- [FunctionName in FunctionSpec.Name<
28
- FunctionSpec.AnyWithPropsWithRuntime<Runtime>
29
- >]: FunctionSpec.WithName<Functions_, FunctionName>;
29
+ [
30
+ FunctionName in FunctionSpec.Name<
31
+ FunctionSpec.AnyWithPropsWithRuntime<Runtime>
32
+ >
33
+ ]: FunctionSpec.WithName<Functions_, FunctionName>;
30
34
  };
31
35
  readonly groups: {
32
36
  [GroupName in Name<Groups_>]: WithName<Groups_, GroupName>;
33
37
  };
38
+ readonly middlewareSpecs: ReadonlyArray<MiddlewareSpecs_>;
39
+ readonly "~Functions": Functions_;
40
+ readonly "~Groups": Groups_;
34
41
 
35
42
  addFunction<Function extends FunctionSpec.AnyWithPropsWithRuntime<Runtime>>(
36
- function_: Function,
37
- ): GroupSpec<Runtime, Name_, Functions_ | Function, Groups_>;
43
+ function_: Function &
44
+ MiddlewareSpec.ValidateAddedFunction<Function, MiddlewareSpecs_>,
45
+ ): GroupSpec<
46
+ Runtime,
47
+ Name_,
48
+ Functions_ | Function,
49
+ Groups_,
50
+ MiddlewareSpecs_
51
+ >;
38
52
 
39
53
  addGroup<Group extends AnyWithProps>(
40
54
  group: Group,
41
- ): GroupSpec<Runtime, Name_, Functions_, Groups_ | Group>;
55
+ ): GroupSpec<Runtime, Name_, Functions_, Groups_ | Group, MiddlewareSpecs_>;
42
56
 
43
57
  addGroupAt<const AtName extends string, Group extends AnyWithProps>(
44
58
  name: AtName,
45
59
  group: Group,
46
- ): GroupSpec<Runtime, Name_, Functions_, Groups_ | NamedAt<Group, AtName>>;
60
+ ): GroupSpec<
61
+ Runtime,
62
+ Name_,
63
+ Functions_,
64
+ Groups_ | NamedAt<Group, AtName>,
65
+ MiddlewareSpecs_
66
+ >;
67
+
68
+ middleware<MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec>(
69
+ middlewareSpec: MiddlewareSpec_ &
70
+ MiddlewareSpec.ValidateAttach<
71
+ MiddlewareSpec_,
72
+ Functions_,
73
+ MiddlewareSpecs_
74
+ >,
75
+ ): GroupSpec<
76
+ Runtime,
77
+ Name_,
78
+ Functions_,
79
+ Groups_,
80
+ MiddlewareSpecs_ | MiddlewareSpec_
81
+ >;
47
82
  }
48
83
 
49
84
  export interface Any {
@@ -54,7 +89,8 @@ export interface AnyWithProps extends GroupSpec<
54
89
  RuntimeAndFunctionType.Runtime,
55
90
  string,
56
91
  FunctionSpec.AnyWithProps,
57
- AnyWithProps
92
+ AnyWithProps,
93
+ MiddlewareSpec.AnyMiddlewareSpec
58
94
  > {}
59
95
 
60
96
  export interface AnyWithPropsWithRuntime<
@@ -63,16 +99,18 @@ export interface AnyWithPropsWithRuntime<
63
99
  Runtime,
64
100
  string,
65
101
  FunctionSpec.AnyWithPropsWithRuntime<Runtime>,
66
- AnyWithPropsWithRuntime<Runtime>
102
+ AnyWithPropsWithRuntime<Runtime>,
103
+ MiddlewareSpec.AnyMiddlewareSpec
67
104
  > {}
68
105
 
69
106
  export type Name<Group extends AnyWithProps> = Group["name"];
70
107
 
71
- export type Functions<Group extends AnyWithProps> =
72
- Group["functions"][keyof Group["functions"]];
108
+ export type Functions<Group extends AnyWithProps> = Group["~Functions"];
109
+
110
+ export type Groups<Group extends AnyWithProps> = Group["~Groups"];
73
111
 
74
- export type Groups<Group extends AnyWithProps> =
75
- Group["groups"][keyof Group["groups"]];
112
+ export type MiddlewareSpecs<Group extends AnyWithProps> =
113
+ Group["middlewareSpecs"][number];
76
114
 
77
115
  export type GroupNames<Group extends AnyWithProps> = [Groups<Group>] extends [
78
116
  never,
@@ -101,9 +139,16 @@ export type AddGroups<
101
139
  infer Runtime,
102
140
  infer Name_,
103
141
  infer Functions_,
104
- infer Groups_
142
+ infer Groups_,
143
+ infer MiddlewareSpecs_
105
144
  >
106
- ? GroupSpec<Runtime, Name_, Functions_, Groups_ | ExtraGroups>
145
+ ? GroupSpec<
146
+ Runtime,
147
+ Name_,
148
+ Functions_,
149
+ Groups_ | ExtraGroups,
150
+ MiddlewareSpecs_
151
+ >
107
152
  : never;
108
153
 
109
154
  const Proto = {
@@ -115,11 +160,25 @@ const Proto = {
115
160
  ) {
116
161
  const this_ = this as AnyWithProps;
117
162
 
163
+ const overlapping = function_.middlewareSpecs.find(
164
+ (functionMiddlewareSpec) =>
165
+ this_.middlewareSpecs.some(
166
+ (groupMiddlewareSpec) =>
167
+ groupMiddlewareSpec.key === functionMiddlewareSpec.key,
168
+ ),
169
+ );
170
+ if (overlapping !== undefined) {
171
+ throw new Error(
172
+ `Middleware "${overlapping.key}" is attached to both function "${function_.name}" and its group`,
173
+ );
174
+ }
175
+
118
176
  return makeProto({
119
177
  runtime: this_.runtime,
120
178
  name: this_.name,
121
179
  functions: Record.set(this_.functions, function_.name, function_),
122
180
  groups: this_.groups,
181
+ middlewareSpecs: this_.middlewareSpecs,
123
182
  });
124
183
  },
125
184
 
@@ -132,6 +191,7 @@ const Proto = {
132
191
  name: this_.name,
133
192
  functions: this_.functions,
134
193
  groups: Record.set(this_.groups, group_.name, group_),
194
+ middlewareSpecs: this_.middlewareSpecs,
135
195
  });
136
196
  },
137
197
 
@@ -144,6 +204,44 @@ const Proto = {
144
204
  name: this_.name,
145
205
  functions: this_.functions,
146
206
  groups: Record.set(this_.groups, name, withName(name, group_)),
207
+ middlewareSpecs: this_.middlewareSpecs,
208
+ });
209
+ },
210
+
211
+ middleware<MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec>(
212
+ this: Any,
213
+ middlewareSpec: MiddlewareSpec_,
214
+ ) {
215
+ const this_ = this as AnyWithProps;
216
+
217
+ if (
218
+ this_.middlewareSpecs.some(
219
+ (existing) => existing.key === middlewareSpec.key,
220
+ )
221
+ ) {
222
+ throw new Error(
223
+ `Middleware "${middlewareSpec.key}" is already attached to this group`,
224
+ );
225
+ }
226
+
227
+ for (const function_ of Object.values(this_.functions)) {
228
+ if (
229
+ function_.middlewareSpecs.some(
230
+ (existing) => existing.key === middlewareSpec.key,
231
+ )
232
+ ) {
233
+ throw new Error(
234
+ `Middleware "${middlewareSpec.key}" is attached to both function "${function_.name}" and its group`,
235
+ );
236
+ }
237
+ }
238
+
239
+ return makeProto({
240
+ runtime: this_.runtime,
241
+ name: this_.name,
242
+ functions: this_.functions,
243
+ groups: this_.groups,
244
+ middlewareSpecs: [...this_.middlewareSpecs, middlewareSpec],
147
245
  });
148
246
  },
149
247
  };
@@ -153,23 +251,27 @@ const makeProto = <
153
251
  Name_ extends string,
154
252
  Functions_ extends FunctionSpec.AnyWithPropsWithRuntime<Runtime>,
155
253
  Groups_ extends AnyWithPropsWithRuntime<Runtime>,
254
+ MiddlewareSpecs_ extends MiddlewareSpec.AnyMiddlewareSpec,
156
255
  >({
157
256
  runtime,
158
257
  name,
159
258
  functions,
160
259
  groups,
260
+ middlewareSpecs,
161
261
  }: {
162
262
  runtime: Runtime;
163
263
  name: Name_;
164
264
  functions: Record.ReadonlyRecord<string, Functions_>;
165
265
  groups: Record.ReadonlyRecord<string, Groups_>;
166
- }): GroupSpec<Runtime, Name_, Functions_, Groups_> =>
266
+ middlewareSpecs: ReadonlyArray<MiddlewareSpecs_>;
267
+ }): GroupSpec<Runtime, Name_, Functions_, Groups_, MiddlewareSpecs_> =>
167
268
  Object.assign(Object.create(Proto), {
168
269
  runtime,
169
270
  name,
170
271
  functions,
171
272
  groups,
172
- });
273
+ middlewareSpecs,
274
+ }) as GroupSpec<Runtime, Name_, Functions_, Groups_, MiddlewareSpecs_>;
173
275
 
174
276
  export const make = (): GroupSpec<"Convex", ""> =>
175
277
  makeProto({
@@ -177,6 +279,7 @@ export const make = (): GroupSpec<"Convex", ""> =>
177
279
  name: "",
178
280
  functions: Record.empty(),
179
281
  groups: Record.empty(),
282
+ middlewareSpecs: [],
180
283
  });
181
284
 
182
285
  export const makeAt = <const Name_ extends string>(
@@ -189,6 +292,7 @@ export const makeAt = <const Name_ extends string>(
189
292
  name,
190
293
  functions: Record.empty(),
191
294
  groups: Record.empty(),
295
+ middlewareSpecs: [],
192
296
  });
193
297
  };
194
298
 
@@ -198,6 +302,7 @@ export const makeNode = (): GroupSpec<"Node", ""> =>
198
302
  name: "",
199
303
  functions: Record.empty(),
200
304
  groups: Record.empty(),
305
+ middlewareSpecs: [],
201
306
  });
202
307
 
203
308
  export const makeNodeAt = <const Name_ extends string>(
@@ -210,6 +315,7 @@ export const makeNodeAt = <const Name_ extends string>(
210
315
  name,
211
316
  functions: Record.empty(),
212
317
  groups: Record.empty(),
318
+ middlewareSpecs: [],
213
319
  });
214
320
  };
215
321
 
@@ -229,5 +335,6 @@ export const withName = <const Name_ extends string>(
229
335
  name,
230
336
  functions: group_.functions,
231
337
  groups: group_.groups,
338
+ middlewareSpecs: group_.middlewareSpecs,
232
339
  });
233
340
  };
package/src/Lazy.ts CHANGED
@@ -14,9 +14,8 @@
14
14
  * forcing the computation.
15
15
  *
16
16
  * This is the single shared implementation consumed across packages (e.g.
17
- * `@confect/core`'s lazy `FunctionSpec` schemas and `@confect/server`'s lazy
18
- * `Table` `Fields` / `Doc` / `tableDefinition`), so there is no chance of the
19
- * two drifting apart.
17
+ * `@confect/core`'s lazy `FunctionSpec` schemas and lazy `Table` `Fields` /
18
+ * `Doc`), so there is no chance of the two drifting apart.
20
19
  */
21
20
  export const defineProperty = <T extends object, K extends PropertyKey>(
22
21
  target: T,
@@ -0,0 +1,356 @@
1
+ import type { FunctionType, FunctionVisibility } from "convex/server";
2
+ import type * as Effect from "effect/Effect";
3
+ import * as Predicate from "effect/Predicate";
4
+ import type * as Schema from "effect/Schema";
5
+ import type { unhandled } from "effect/Types";
6
+ import type * as FunctionProvenance from "./FunctionProvenance";
7
+ import type * as FunctionSpec from "./FunctionSpec";
8
+ import * as Lazy from "./Lazy";
9
+ import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
10
+
11
+ export const TypeId = "~@confect/core/MiddlewareSpec";
12
+ export type TypeId = typeof TypeId;
13
+
14
+ export const isMiddlewareSpec = (u: unknown): u is AnyMiddlewareSpec =>
15
+ Predicate.hasProperty(u, TypeId);
16
+
17
+ export const allFunctionTypes: ReadonlyArray<FunctionType> = [
18
+ "query",
19
+ "mutation",
20
+ "action",
21
+ ];
22
+
23
+ export interface SupportedFunctionTypes {
24
+ readonly query: boolean;
25
+ readonly mutation: boolean;
26
+ readonly action: boolean;
27
+ }
28
+
29
+ /**
30
+ * Marker success type used by middleware to represent the handler's outcome
31
+ * without exposing its concrete success value.
32
+ *
33
+ * Mirrors `RpcMiddleware.SuccessValue` in Effect.
34
+ */
35
+ export interface SuccessValue {
36
+ readonly _: unique symbol;
37
+ }
38
+
39
+ /**
40
+ * The wrap-style middleware implementation shape: receives the downstream
41
+ * effect (the remaining middleware plus the function handler) and the
42
+ * covered invocation's metadata, and returns the effect that runs in its
43
+ * place.
44
+ *
45
+ * Mirrors `RpcMiddleware.RpcMiddleware` in Effect.
46
+ */
47
+ export interface MiddlewareImpl<Provides_, E, R> {
48
+ (
49
+ effect: Effect.Effect<SuccessValue, E | unhandled, Provides_>,
50
+ options: MiddlewareOptions,
51
+ ): Effect.Effect<SuccessValue, E | unhandled, R>;
52
+ }
53
+
54
+ export interface MiddlewareOptions {
55
+ readonly name: string;
56
+ readonly functionType: FunctionType;
57
+ readonly functionVisibility: FunctionVisibility;
58
+ readonly args: unknown;
59
+ }
60
+
61
+ export interface AnyMiddlewareImpl extends MiddlewareImpl<any, any, any> {}
62
+
63
+ /**
64
+ * The class shape produced by {@link MiddlewareSpec}. Only the static side is ever
65
+ * used — the class is a value-level carrier for the middleware's key, its
66
+ * declared functionTypes, and its (lazily-evaluated) error schema, plus the
67
+ * type-level `Provides`/`Error` metadata. It is deliberately not a
68
+ * `Context.Tag`: implementations are registered through the group `Registry`
69
+ * (like `FunctionImpl`s), not resolved from a Layer context.
70
+ */
71
+ export interface MiddlewareSpec<
72
+ in out Self,
73
+ Key_ extends string,
74
+ Provides_,
75
+ Requires_,
76
+ ErrorSchema_ extends Schema.Codec<any, any>,
77
+ FunctionTypes_ extends FunctionType,
78
+ > {
79
+ new (_: never): {
80
+ readonly [TypeId]: TypeId;
81
+ readonly key: Key_;
82
+ };
83
+ readonly [TypeId]: TypeId;
84
+ readonly key: Key_;
85
+ readonly functionTypes: SupportedFunctionTypes;
86
+ readonly error?: ErrorSchema_;
87
+ readonly "~Provides": Provides_;
88
+ readonly "~Requires": Requires_;
89
+ readonly "~Error": ErrorSchema_;
90
+ readonly "~FunctionTypes": FunctionTypes_;
91
+ readonly "~Self": Self;
92
+ }
93
+
94
+ export interface AnyMiddlewareSpec {
95
+ readonly [TypeId]: TypeId;
96
+ readonly key: string;
97
+ readonly functionTypes: SupportedFunctionTypes;
98
+ readonly error?: Schema.Codec<any, any>;
99
+ readonly "~Provides": any;
100
+ readonly "~Requires": any;
101
+ readonly "~Error": any;
102
+ readonly "~FunctionTypes": FunctionType;
103
+ }
104
+
105
+ export type Key<MiddlewareSpec_ extends AnyMiddlewareSpec> =
106
+ MiddlewareSpec_["key"];
107
+
108
+ export type Provides<MiddlewareSpec_ extends AnyMiddlewareSpec> =
109
+ MiddlewareSpec_ extends AnyMiddlewareSpec
110
+ ? MiddlewareSpec_["~Provides"]
111
+ : never;
112
+
113
+ /**
114
+ * The services a middleware's implementation may consume from middleware
115
+ * that runs earlier in the same chain (type-level only, like `provides`).
116
+ */
117
+ export type Requires<MiddlewareSpec_ extends AnyMiddlewareSpec> =
118
+ MiddlewareSpec_ extends AnyMiddlewareSpec
119
+ ? MiddlewareSpec_["~Requires"]
120
+ : never;
121
+
122
+ export type ErrorSchema<MiddlewareSpec_ extends AnyMiddlewareSpec> =
123
+ MiddlewareSpec_ extends AnyMiddlewareSpec ? MiddlewareSpec_["~Error"] : never;
124
+
125
+ export type Error<MiddlewareSpec_ extends AnyMiddlewareSpec> =
126
+ MiddlewareSpec_ extends AnyMiddlewareSpec
127
+ ? MiddlewareSpec_["~Error"]["Type"]
128
+ : never;
129
+
130
+ export type EncodedError<MiddlewareSpec_ extends AnyMiddlewareSpec> =
131
+ MiddlewareSpec_ extends AnyMiddlewareSpec
132
+ ? MiddlewareSpec_["~Error"]["Encoded"]
133
+ : never;
134
+
135
+ export type FunctionTypes<MiddlewareSpec_ extends AnyMiddlewareSpec> =
136
+ MiddlewareSpec_ extends AnyMiddlewareSpec
137
+ ? MiddlewareSpec_["~FunctionTypes"]
138
+ : never;
139
+
140
+ /**
141
+ * Declare a middleware's client-safe interface: its identifying key, the
142
+ * service it provides to downstream handlers, the error schema its failures
143
+ * encode through, and the function types it may attach to.
144
+ *
145
+ * ```ts
146
+ * class RequireUser extends MiddlewareSpec.MiddlewareSpec<RequireUser, {
147
+ * provides: CurrentUser
148
+ * }>()("RequireUser", {
149
+ * error: () => NotSignedIn,
150
+ * functionTypes: { query: true, mutation: true, action: false },
151
+ * }) {}
152
+ * ```
153
+ *
154
+ * The implementation is server-only and supplied separately via
155
+ * `MiddlewareImpl.make` (or `makeByFunctionType`/`provides`) in `@confect/server`.
156
+ */
157
+ export const MiddlewareSpec =
158
+ <Self, Config extends { provides?: any; requires?: any } = {}>() =>
159
+ <
160
+ const Key_ extends string,
161
+ const FunctionTypesConfig_ extends SupportedFunctionTypes,
162
+ ErrorSchema_ extends Schema.Codec<any, any> = never,
163
+ >(
164
+ key: Key_,
165
+ options: {
166
+ readonly error?: () => ErrorSchema_;
167
+ readonly functionTypes: FunctionTypesConfig_ &
168
+ ValidateFunctionTypesConfig<FunctionTypesConfig_>;
169
+ },
170
+ ): MiddlewareSpec<
171
+ Self,
172
+ Key_,
173
+ "provides" extends keyof Config ? Config["provides"] : never,
174
+ "requires" extends keyof Config ? Config["requires"] : never,
175
+ ErrorSchema_,
176
+ FunctionTypesFromConfig<FunctionTypesConfig_>
177
+ > => {
178
+ const { query, mutation, action } = options.functionTypes;
179
+ if (!query && !mutation && !action) {
180
+ throw new Error(
181
+ `Middleware "${key}" must declare at least one function type`,
182
+ );
183
+ }
184
+
185
+ function MiddlewareSpecClass() {}
186
+ const class_ = MiddlewareSpecClass as any;
187
+ class_[TypeId] = TypeId;
188
+ class_.key = key;
189
+ class_.functionTypes = {
190
+ query,
191
+ mutation,
192
+ action,
193
+ } satisfies SupportedFunctionTypes;
194
+ if (options.error !== undefined) {
195
+ Lazy.defineProperty(class_, "error", options.error);
196
+ }
197
+ return class_;
198
+ };
199
+
200
+ type FunctionTypesFromConfig<Config extends SupportedFunctionTypes> = {
201
+ [K in FunctionType]: Config[K] extends true ? K : never;
202
+ }[FunctionType];
203
+
204
+ type ValidateFunctionTypesConfig<Config extends SupportedFunctionTypes> =
205
+ ValidationResult<
206
+ | {
207
+ [K in keyof Config]: boolean extends Config[K]
208
+ ? AttachmentError<`Function type flag "${K & string}" must be a literal \`true\` or \`false\`, not a computed \`boolean\``>
209
+ : never;
210
+ }[keyof Config]
211
+ | ([FunctionTypesFromConfig<Config>] extends [never]
212
+ ? AttachmentError<`A middleware must declare at least one function type`>
213
+ : never)
214
+ >;
215
+
216
+ export const errorSchemas = (
217
+ middlewareSpecs: ReadonlyArray<AnyMiddlewareSpec>,
218
+ ): ReadonlyArray<Schema.Codec<any, any>> =>
219
+ middlewareSpecs.flatMap((middlewareSpec) =>
220
+ "error" in middlewareSpec && middlewareSpec.error !== undefined
221
+ ? [middlewareSpec.error]
222
+ : [],
223
+ );
224
+
225
+ /**
226
+ * A branded, unconstructible type carrying a spec-authoring error message.
227
+ * Surfaced as (part of) a parameter type so the diagnostic names the exact
228
+ * problem at the `.middleware()` / `.addFunction()` call site.
229
+ */
230
+ export interface AttachmentError<Message extends string> {
231
+ readonly "~@confect/core/MiddlewareSpec/AttachmentError": Message;
232
+ }
233
+
234
+ type FunctionTypeOf<FunctionSpec_ extends FunctionSpec.AnyWithProps> =
235
+ FunctionSpec_["runtimeAndFunctionType"]["functionType"];
236
+
237
+ type ValidationResult<Errors> = [Errors] extends [never] ? unknown : Errors;
238
+
239
+ /**
240
+ * A matching-type Convex-provenance function is rejected rather than
241
+ * silently skipped: its raw handler passes through Confect untouched, so
242
+ * the middleware could not actually cover it, and skipping it would be a
243
+ * policy hole.
244
+ */
245
+ export type ValidateFunction<
246
+ FunctionSpec_ extends FunctionSpec.AnyWithProps,
247
+ MiddlewareSpec_ extends AnyMiddlewareSpec,
248
+ > = FunctionSpec_ extends any
249
+ ? MiddlewareSpec_ extends any
250
+ ? FunctionSpec_ extends {
251
+ readonly functionProvenance: { readonly _tag: "Convex" };
252
+ }
253
+ ? FunctionTypeOf<FunctionSpec_> extends FunctionTypes<MiddlewareSpec_>
254
+ ? AttachmentError<`Convex-provenance function "${FunctionSpec_["name"]}" cannot be covered by middleware "${Key<MiddlewareSpec_>}" — attach middleware only to groups whose matching-type functions are all Confect-provenance`>
255
+ : never
256
+ : [
257
+ Extract<
258
+ FunctionSpec.MiddlewareSpecs<FunctionSpec_>,
259
+ { readonly key: Key<MiddlewareSpec_> }
260
+ >,
261
+ ] extends [never]
262
+ ? FunctionTypeOf<FunctionSpec_> extends FunctionTypes<MiddlewareSpec_>
263
+ ? never
264
+ : AttachmentError<`Middleware "${Key<MiddlewareSpec_>}" does not declare function type "${FunctionTypeOf<FunctionSpec_>}", the type of function "${FunctionSpec_["name"]}"`>
265
+ : AttachmentError<`Middleware "${Key<MiddlewareSpec_>}" is already attached to function "${FunctionSpec_["name"]}"`>
266
+ : never
267
+ : never;
268
+
269
+ /**
270
+ * Attachment order is chain order, so requiring `requires` to be satisfied
271
+ * by already-attached middleware is exactly requiring the provider to run
272
+ * earlier.
273
+ */
274
+ export type ValidateAttach<
275
+ MiddlewareSpec_ extends AnyMiddlewareSpec,
276
+ Functions_ extends FunctionSpec.AnyWithProps,
277
+ MiddlewareSpecs_ extends AnyMiddlewareSpec,
278
+ > = [
279
+ Extract<MiddlewareSpecs_, { readonly key: Key<MiddlewareSpec_> }>,
280
+ ] extends [never]
281
+ ? [Exclude<Requires<MiddlewareSpec_>, Provides<MiddlewareSpecs_>>] extends [
282
+ never,
283
+ ]
284
+ ? ValidationResult<ValidateFunction<Functions_, MiddlewareSpec_>>
285
+ : AttachmentError<`Middleware "${Key<MiddlewareSpec_>}" requires services that no middleware attached earlier to this group provides — attach the providing middleware first`>
286
+ : AttachmentError<`Middleware "${Key<MiddlewareSpec_>}" is already attached to this group`>;
287
+
288
+ export type ValidateAddedFunction<
289
+ FunctionSpec_ extends FunctionSpec.AnyWithProps,
290
+ MiddlewareSpecs_ extends AnyMiddlewareSpec,
291
+ > = [MiddlewareSpecs_] extends [never]
292
+ ? unknown
293
+ : ValidationResult<
294
+ | ValidateFunction<FunctionSpec_, MiddlewareSpecs_>
295
+ | GroupOverlap<FunctionSpec_, MiddlewareSpecs_>
296
+ >;
297
+
298
+ type GroupOverlap<
299
+ FunctionSpec_ extends FunctionSpec.AnyWithProps,
300
+ MiddlewareSpecs_ extends AnyMiddlewareSpec,
301
+ > =
302
+ FunctionSpec.MiddlewareSpecs<FunctionSpec_> extends infer FunctionMiddlewareSpec
303
+ ? FunctionMiddlewareSpec extends AnyMiddlewareSpec
304
+ ? [
305
+ Extract<
306
+ MiddlewareSpecs_,
307
+ { readonly key: Key<FunctionMiddlewareSpec> }
308
+ >,
309
+ ] extends [never]
310
+ ? never
311
+ : AttachmentError<`Middleware "${Key<FunctionMiddlewareSpec>}" is attached to both function "${FunctionSpec_["name"]}" and its group`>
312
+ : never
313
+ : never;
314
+
315
+ /**
316
+ * The parameter-type validation applied to the group spec at
317
+ * `GroupImpl.make`: every function's attached middleware must have its
318
+ * `requires` services provided by some middleware covering that function
319
+ * (the group's or the function's own). Checked here — with the group fully
320
+ * assembled — rather than at the spec builders, because a function-level
321
+ * middleware's `requires` may legitimately be satisfied by a group
322
+ * middleware the function spec never sees, and group attachment is
323
+ * declaratively order-independent with respect to `addFunction`.
324
+ */
325
+ export type ValidateImplRequires<
326
+ Functions_ extends FunctionSpec.AnyWithProps,
327
+ GroupMiddlewareSpecs_ extends AnyMiddlewareSpec,
328
+ > = ValidationResult<
329
+ Functions_ extends any
330
+ ? [
331
+ Exclude<
332
+ Requires<FunctionSpec.MiddlewareSpecs<Functions_>>,
333
+ Provides<
334
+ GroupMiddlewareSpecs_ | FunctionSpec.MiddlewareSpecs<Functions_>
335
+ >
336
+ >,
337
+ ] extends [never]
338
+ ? never
339
+ : AttachmentError<`Function "${Functions_["name"]}" has middleware requiring services that no middleware covering it provides`>
340
+ : never
341
+ >;
342
+
343
+ export type ValidateFunctionAttach<
344
+ MiddlewareSpec_ extends AnyMiddlewareSpec,
345
+ RuntimeAndFunctionType_ extends RuntimeAndFunctionType.RuntimeAndFunctionType,
346
+ FunctionProvenance_ extends FunctionProvenance.FunctionProvenance,
347
+ MiddlewareSpecs_ extends AnyMiddlewareSpec,
348
+ > = FunctionProvenance_ extends { readonly _tag: "Convex" }
349
+ ? AttachmentError<`Plain Convex functions cannot have middleware — their raw handlers are passed through untouched`>
350
+ : [
351
+ Extract<MiddlewareSpecs_, { readonly key: Key<MiddlewareSpec_> }>,
352
+ ] extends [never]
353
+ ? RuntimeAndFunctionType_["functionType"] extends FunctionTypes<MiddlewareSpec_>
354
+ ? unknown
355
+ : AttachmentError<`Middleware "${Key<MiddlewareSpec_>}" does not declare function type "${RuntimeAndFunctionType_["functionType"]}"`>
356
+ : AttachmentError<`Middleware "${Key<MiddlewareSpec_>}" is already attached to this function`>;