@confect/core 10.0.0-next.21 → 10.0.0-next.22

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 (66) hide show
  1. package/CHANGELOG.md +150 -81
  2. package/dist/FunctionProvenance.d.ts +5 -6
  3. package/dist/FunctionProvenance.d.ts.map +1 -1
  4. package/dist/FunctionProvenance.js +3 -4
  5. package/dist/FunctionProvenance.js.map +1 -1
  6. package/dist/FunctionSpec.d.ts +3 -1
  7. package/dist/FunctionSpec.d.ts.map +1 -1
  8. package/dist/FunctionSpec.js +12 -6
  9. package/dist/FunctionSpec.js.map +1 -1
  10. package/dist/GroupSpec.d.ts +5 -1
  11. package/dist/GroupSpec.d.ts.map +1 -1
  12. package/dist/GroupSpec.js +31 -17
  13. package/dist/GroupSpec.js.map +1 -1
  14. package/dist/Identifier.d.ts +1 -1
  15. package/dist/Identifier.js +1 -1
  16. package/dist/Identifier.js.map +1 -1
  17. package/dist/Lazy.d.ts +4 -5
  18. package/dist/Lazy.d.ts.map +1 -1
  19. package/dist/Lazy.js +4 -5
  20. package/dist/Lazy.js.map +1 -1
  21. package/dist/MiddlewareAttachment.d.ts +171 -0
  22. package/dist/MiddlewareAttachment.d.ts.map +1 -0
  23. package/dist/MiddlewareAttachment.js +48 -0
  24. package/dist/MiddlewareAttachment.js.map +1 -0
  25. package/dist/MiddlewareSpec.d.ts +35 -23
  26. package/dist/MiddlewareSpec.d.ts.map +1 -1
  27. package/dist/MiddlewareSpec.js +1 -0
  28. package/dist/MiddlewareSpec.js.map +1 -1
  29. package/dist/PaginationOptions.d.ts +1 -1
  30. package/dist/PaginationOptions.js +1 -1
  31. package/dist/PaginationOptions.js.map +1 -1
  32. package/dist/Ref.d.ts +8 -7
  33. package/dist/Ref.d.ts.map +1 -1
  34. package/dist/Ref.js +11 -9
  35. package/dist/Ref.js.map +1 -1
  36. package/dist/Refs.js +1 -1
  37. package/dist/Refs.js.map +1 -1
  38. package/dist/SchemaToValidator.d.ts.map +1 -1
  39. package/dist/SchemaToValidator.js +4 -2
  40. package/dist/SchemaToValidator.js.map +1 -1
  41. package/dist/Spec.d.ts +1 -1
  42. package/dist/Spec.js.map +1 -1
  43. package/dist/SystemFields.d.ts +3 -4
  44. package/dist/SystemFields.d.ts.map +1 -1
  45. package/dist/SystemFields.js +5 -6
  46. package/dist/SystemFields.js.map +1 -1
  47. package/dist/Table.js.map +1 -1
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +2 -1
  51. package/package.json +6 -6
  52. package/src/FunctionProvenance.ts +6 -7
  53. package/src/FunctionSpec.ts +21 -7
  54. package/src/GroupSpec.ts +74 -26
  55. package/src/Identifier.ts +1 -1
  56. package/src/Lazy.ts +4 -5
  57. package/src/MiddlewareAttachment.ts +106 -0
  58. package/src/MiddlewareSpec.ts +55 -21
  59. package/src/PaginationOptions.ts +1 -1
  60. package/src/Ref.ts +42 -37
  61. package/src/Refs.ts +1 -1
  62. package/src/SchemaToValidator.ts +53 -44
  63. package/src/Spec.ts +1 -1
  64. package/src/SystemFields.ts +6 -7
  65. package/src/Table.ts +2 -2
  66. package/src/index.ts +1 -0
package/src/GroupSpec.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import * as MiddlewareAttachment from "./MiddlewareAttachment";
1
2
  import * as Predicate from "effect/Predicate";
2
3
  import * as Record from "effect/Record";
4
+ import * as Result from "effect/Result";
3
5
  import type * as FunctionSpec from "./FunctionSpec";
4
6
  import type * as MiddlewareSpec from "./MiddlewareSpec";
5
7
  import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
@@ -18,7 +20,7 @@ export interface GroupSpec<
18
20
  // Subgroups may be of any runtime, independent of this group's own runtime: a
19
21
  // group is only a namespace for its children, which are otherwise-independent
20
22
  // modules. Functions, by contrast, stay homogeneous (a Node group only accepts
21
- // Node actions) — `addFunction` keeps the `<Runtime>` bound below.
23
+ // Node actions)—`addFunction` keeps the `<Runtime>` bound below.
22
24
  Groups_ extends AnyWithProps = never,
23
25
  MiddlewareSpecs_ extends MiddlewareSpec.AnyMiddlewareSpec = never,
24
26
  > {
@@ -36,6 +38,9 @@ export interface GroupSpec<
36
38
  [GroupName in Name<Groups_>]: WithName<Groups_, GroupName>;
37
39
  };
38
40
  readonly middlewareSpecs: ReadonlyArray<MiddlewareSpecs_>;
41
+ readonly middlewareAttachments: ReadonlyArray<
42
+ MiddlewareAttachment.MiddlewareAttachment<MiddlewareSpecs_>
43
+ >;
39
44
  readonly "~Functions": Functions_;
40
45
  readonly "~Groups": Groups_;
41
46
 
@@ -72,6 +77,7 @@ export interface GroupSpec<
72
77
  Functions_,
73
78
  MiddlewareSpecs_
74
79
  >,
80
+ ...options: MiddlewareAttachment.Args<NoInfer<MiddlewareSpec_>>
75
81
  ): GroupSpec<
76
82
  Runtime,
77
83
  Name_,
@@ -154,22 +160,27 @@ export type AddGroups<
154
160
  const Proto = {
155
161
  [TypeId]: TypeId,
156
162
 
163
+ get middlewareSpecs() {
164
+ return this.middlewareAttachments.map(({ spec }) => spec);
165
+ },
166
+
157
167
  addFunction<Function extends FunctionSpec.AnyWithProps>(
158
168
  this: Any,
159
169
  function_: Function,
160
170
  ) {
161
171
  const this_ = this as AnyWithProps;
162
172
 
163
- const overlapping = function_.middlewareSpecs.find(
164
- (functionMiddlewareSpec) =>
165
- this_.middlewareSpecs.some(
166
- (groupMiddlewareSpec) =>
173
+ const overlapping = function_.middlewareAttachments.find(
174
+ ({ spec: functionMiddlewareSpec }) =>
175
+ !("options" in functionMiddlewareSpec) &&
176
+ this_.middlewareAttachments.some(
177
+ ({ spec: groupMiddlewareSpec }) =>
167
178
  groupMiddlewareSpec.key === functionMiddlewareSpec.key,
168
179
  ),
169
180
  );
170
181
  if (overlapping !== undefined) {
171
182
  throw new Error(
172
- `Middleware "${overlapping.key}" is attached to both function "${function_.name}" and its group`,
183
+ `Middleware "${overlapping.spec.key}" is attached to both function "${function_.name}" and its group`,
173
184
  );
174
185
  }
175
186
 
@@ -178,7 +189,7 @@ const Proto = {
178
189
  name: this_.name,
179
190
  functions: Record.set(this_.functions, function_.name, function_),
180
191
  groups: this_.groups,
181
- middlewareSpecs: this_.middlewareSpecs,
192
+ middlewareAttachments: this_.middlewareAttachments,
182
193
  });
183
194
  },
184
195
 
@@ -191,7 +202,7 @@ const Proto = {
191
202
  name: this_.name,
192
203
  functions: this_.functions,
193
204
  groups: Record.set(this_.groups, group_.name, group_),
194
- middlewareSpecs: this_.middlewareSpecs,
205
+ middlewareAttachments: this_.middlewareAttachments,
195
206
  });
196
207
  },
197
208
 
@@ -204,19 +215,21 @@ const Proto = {
204
215
  name: this_.name,
205
216
  functions: this_.functions,
206
217
  groups: Record.set(this_.groups, name, withName(name, group_)),
207
- middlewareSpecs: this_.middlewareSpecs,
218
+ middlewareAttachments: this_.middlewareAttachments,
208
219
  });
209
220
  },
210
221
 
211
222
  middleware<MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec>(
212
223
  this: Any,
213
224
  middlewareSpec: MiddlewareSpec_,
225
+ ...options: ReadonlyArray<unknown>
214
226
  ) {
215
227
  const this_ = this as AnyWithProps;
216
228
 
217
229
  if (
218
- this_.middlewareSpecs.some(
219
- (existing) => existing.key === middlewareSpec.key,
230
+ !("options" in middlewareSpec) &&
231
+ this_.middlewareAttachments.some(
232
+ ({ spec }) => spec.key === middlewareSpec.key,
220
233
  )
221
234
  ) {
222
235
  throw new Error(
@@ -226,8 +239,9 @@ const Proto = {
226
239
 
227
240
  for (const function_ of Object.values(this_.functions)) {
228
241
  if (
229
- function_.middlewareSpecs.some(
230
- (existing) => existing.key === middlewareSpec.key,
242
+ !("options" in middlewareSpec) &&
243
+ function_.middlewareAttachments.some(
244
+ ({ spec }) => spec.key === middlewareSpec.key,
231
245
  )
232
246
  ) {
233
247
  throw new Error(
@@ -241,37 +255,53 @@ const Proto = {
241
255
  name: this_.name,
242
256
  functions: this_.functions,
243
257
  groups: this_.groups,
244
- middlewareSpecs: [...this_.middlewareSpecs, middlewareSpec],
258
+ middlewareAttachments: [
259
+ ...this_.middlewareAttachments,
260
+ { spec: middlewareSpec, options: options[0] },
261
+ ],
245
262
  });
246
263
  },
247
- };
264
+ } satisfies ThisType<AnyWithProps>;
248
265
 
249
266
  const makeProto = <
250
267
  Runtime extends RuntimeAndFunctionType.Runtime,
251
268
  Name_ extends string,
252
269
  Functions_ extends FunctionSpec.AnyWithPropsWithRuntime<Runtime>,
253
270
  Groups_ extends AnyWithPropsWithRuntime<Runtime>,
254
- MiddlewareSpecs_ extends MiddlewareSpec.AnyMiddlewareSpec,
271
+ MiddlewareAttachments_ extends
272
+ ReadonlyArray<MiddlewareAttachment.MiddlewareAttachment>,
255
273
  >({
256
274
  runtime,
257
275
  name,
258
276
  functions,
259
277
  groups,
260
- middlewareSpecs,
278
+ middlewareAttachments,
261
279
  }: {
262
280
  runtime: Runtime;
263
281
  name: Name_;
264
282
  functions: Record.ReadonlyRecord<string, Functions_>;
265
283
  groups: Record.ReadonlyRecord<string, Groups_>;
266
- middlewareSpecs: ReadonlyArray<MiddlewareSpecs_>;
267
- }): GroupSpec<Runtime, Name_, Functions_, Groups_, MiddlewareSpecs_> =>
284
+ middlewareAttachments: MiddlewareAttachments_;
285
+ }): GroupSpec<
286
+ Runtime,
287
+ Name_,
288
+ Functions_,
289
+ Groups_,
290
+ MiddlewareAttachments_[number]["spec"]
291
+ > =>
268
292
  Object.assign(Object.create(Proto), {
269
293
  runtime,
270
294
  name,
271
295
  functions,
272
296
  groups,
273
- middlewareSpecs,
274
- }) as GroupSpec<Runtime, Name_, Functions_, Groups_, MiddlewareSpecs_>;
297
+ middlewareAttachments,
298
+ }) as GroupSpec<
299
+ Runtime,
300
+ Name_,
301
+ Functions_,
302
+ Groups_,
303
+ MiddlewareAttachments_[number]["spec"]
304
+ >;
275
305
 
276
306
  export const make = (): GroupSpec<"Convex", ""> =>
277
307
  makeProto({
@@ -279,7 +309,7 @@ export const make = (): GroupSpec<"Convex", ""> =>
279
309
  name: "",
280
310
  functions: Record.empty(),
281
311
  groups: Record.empty(),
282
- middlewareSpecs: [],
312
+ middlewareAttachments: [],
283
313
  });
284
314
 
285
315
  export const makeAt = <const Name_ extends string>(
@@ -292,7 +322,7 @@ export const makeAt = <const Name_ extends string>(
292
322
  name,
293
323
  functions: Record.empty(),
294
324
  groups: Record.empty(),
295
- middlewareSpecs: [],
325
+ middlewareAttachments: [],
296
326
  });
297
327
  };
298
328
 
@@ -302,7 +332,7 @@ export const makeNode = (): GroupSpec<"Node", ""> =>
302
332
  name: "",
303
333
  functions: Record.empty(),
304
334
  groups: Record.empty(),
305
- middlewareSpecs: [],
335
+ middlewareAttachments: [],
306
336
  });
307
337
 
308
338
  export const makeNodeAt = <const Name_ extends string>(
@@ -315,7 +345,7 @@ export const makeNodeAt = <const Name_ extends string>(
315
345
  name,
316
346
  functions: Record.empty(),
317
347
  groups: Record.empty(),
318
- middlewareSpecs: [],
348
+ middlewareAttachments: [],
319
349
  });
320
350
  };
321
351
 
@@ -335,6 +365,24 @@ export const withName = <const Name_ extends string>(
335
365
  name,
336
366
  functions: group_.functions,
337
367
  groups: group_.groups,
338
- middlewareSpecs: group_.middlewareSpecs,
368
+ middlewareAttachments: group_.middlewareAttachments,
339
369
  });
340
370
  };
371
+
372
+ export const validateMiddleware = (
373
+ group: AnyWithProps,
374
+ ): Result.Result<void, MiddlewareAttachment.ValidationError> =>
375
+ Result.gen(function* () {
376
+ yield* MiddlewareAttachment.validateAll(
377
+ group.middlewareAttachments,
378
+ `group "${group.name}"`,
379
+ );
380
+ for (const function_ of Object.values(group.functions)) {
381
+ yield* MiddlewareAttachment.validateAll(
382
+ [...group.middlewareAttachments, ...function_.middlewareAttachments],
383
+ `function "${function_.name}"`,
384
+ );
385
+ }
386
+ for (const child of Object.values(group.groups))
387
+ yield* validateMiddleware(child);
388
+ });
package/src/Identifier.ts CHANGED
@@ -99,7 +99,7 @@ export const validateConfectFunctionIdentifier = (identifier: string) => {
99
99
  * as a `confect/tables/<identifier>.ts` filename).
100
100
  *
101
101
  * Rules:
102
- * - Must match `/^[A-Za-z][A-Za-z0-9_]*$/` — letter-leading, alphanumeric plus
102
+ * - Must match `/^[A-Za-z][A-Za-z0-9_]*$/`—letter-leading, alphanumeric plus
103
103
  * underscore. No `$` (not a valid Convex table name character); no leading
104
104
  * `_` (Convex reserves `_<name>` for its system tables).
105
105
  * - Must not be a reserved JavaScript identifier, so the name can also be used
package/src/Lazy.ts CHANGED
@@ -2,20 +2,19 @@
2
2
  * Install a lazy memoised property on `target`. The first access runs
3
3
  * `compute()` and replaces the getter with a plain, non-writable data
4
4
  * property whose value is the computed result. Subsequent accesses hit
5
- * the V8 fast path for own data properties — no function call, identical
6
- * returned reference — so first and second-and-subsequent accesses are
5
+ * the V8 fast path for own data properties—no function call, identical
6
+ * returned reference—so first and second-and-subsequent accesses are
7
7
  * observably indistinguishable.
8
8
  *
9
9
  * The replacement property is `enumerable: true` so the lazy property
10
- * still participates in `Object.keys` / `JSON.stringify` after it
10
+ * still participates in `Object.keys`/`JSON.stringify` after it
11
11
  * materialises, matching the shape of a plain data property. The property
12
12
  * is also `enumerable` before materialising, so presence checks
13
13
  * (`"key" in target`, `Object.hasOwn(target, key)`) observe it without
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 lazy `Table` `Fields` /
18
- * `Doc`), so there is no chance of the two drifting apart.
17
+ * `@confect/core`'s lazy `FunctionSpec` schemas and lazy `Table` `Fields`/`Doc`), so there is no chance of the two drifting apart.
19
18
  */
20
19
  export const defineProperty = <T extends object, K extends PropertyKey>(
21
20
  target: T,
@@ -0,0 +1,106 @@
1
+ import * as Data from "effect/Data";
2
+ import * as Result from "effect/Result";
3
+ import * as Schema from "effect/Schema";
4
+ import type * as MiddlewareSpec from "./MiddlewareSpec";
5
+
6
+ export type MiddlewareAttachment<
7
+ MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec =
8
+ MiddlewareSpec.AnyMiddlewareSpec,
9
+ > = MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec
10
+ ? {
11
+ readonly spec: MiddlewareSpec_;
12
+ readonly options: [MiddlewareSpec_["~Options"]] extends [never]
13
+ ? undefined
14
+ : MiddlewareSpec.Options<MiddlewareSpec_>;
15
+ }
16
+ : never;
17
+
18
+ export type Args<MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec> = [
19
+ MiddlewareSpec_["~Options"],
20
+ ] extends [never]
21
+ ? []
22
+ : [options: MiddlewareSpec.Options<MiddlewareSpec_>];
23
+
24
+ export type ValidationError = Data.TaggedEnum<{
25
+ InvalidOptions: {
26
+ readonly middlewareKey: string;
27
+ readonly location: string;
28
+ readonly attachmentIndex: number;
29
+ };
30
+ ConflictingSpecs: {
31
+ readonly middlewareKey: string;
32
+ readonly location: string;
33
+ readonly attachmentIndex: number;
34
+ readonly previousIndex: number;
35
+ };
36
+ EquivalentOptions: {
37
+ readonly middlewareKey: string;
38
+ readonly location: string;
39
+ readonly attachmentIndex: number;
40
+ readonly previousIndex: number;
41
+ };
42
+ }>;
43
+
44
+ export const ValidationError = Data.taggedEnum<ValidationError>();
45
+
46
+ export const formatValidationError = ValidationError.$match({
47
+ InvalidOptions: ({ middlewareKey, location, attachmentIndex }) =>
48
+ `Middleware "${middlewareKey}" has invalid options at attachment ${attachmentIndex + 1} on ${location}`,
49
+ ConflictingSpecs: ({ middlewareKey, location }) =>
50
+ `Different middleware specs share key "${middlewareKey}" on ${location}`,
51
+ EquivalentOptions: ({
52
+ middlewareKey,
53
+ location,
54
+ attachmentIndex,
55
+ previousIndex,
56
+ }) =>
57
+ `Middleware "${middlewareKey}" has equivalent options at attachments ${previousIndex + 1} and ${attachmentIndex + 1} on ${location}`,
58
+ });
59
+
60
+ export const validateAll = (
61
+ attachments: ReadonlyArray<MiddlewareAttachment>,
62
+ location: string,
63
+ ): Result.Result<void, ValidationError> => {
64
+ for (const [index, attachment] of attachments.entries()) {
65
+ const schema = attachment.spec.options;
66
+ if (schema !== undefined && !Schema.is(schema)(attachment.options)) {
67
+ return Result.fail(
68
+ ValidationError.InvalidOptions({
69
+ middlewareKey: attachment.spec.key,
70
+ location,
71
+ attachmentIndex: index,
72
+ }),
73
+ );
74
+ }
75
+ const equivalent =
76
+ schema === undefined ? undefined : Schema.toEquivalence(schema);
77
+ for (const [previousIndex, previous] of attachments.entries()) {
78
+ if (previousIndex === index) break;
79
+ if (previous.spec.key !== attachment.spec.key) continue;
80
+ if (previous.spec !== attachment.spec) {
81
+ return Result.fail(
82
+ ValidationError.ConflictingSpecs({
83
+ middlewareKey: attachment.spec.key,
84
+ location,
85
+ attachmentIndex: index,
86
+ previousIndex,
87
+ }),
88
+ );
89
+ }
90
+ if (
91
+ equivalent === undefined ||
92
+ equivalent(previous.options, attachment.options)
93
+ ) {
94
+ return Result.fail(
95
+ ValidationError.EquivalentOptions({
96
+ middlewareKey: attachment.spec.key,
97
+ location,
98
+ attachmentIndex: index,
99
+ previousIndex,
100
+ }),
101
+ );
102
+ }
103
+ }
104
+ }
105
+ return Result.void;
106
+ };
@@ -44,25 +44,32 @@ export interface SuccessValue {
44
44
  *
45
45
  * Mirrors `RpcMiddleware.RpcMiddleware` in Effect.
46
46
  */
47
- export interface MiddlewareImpl<Provides_, E, R> {
47
+ export interface MiddlewareImpl<Provides_, E, R, Options_ = never> {
48
48
  (
49
49
  effect: Effect.Effect<SuccessValue, E | unhandled, Provides_>,
50
- options: MiddlewareOptions,
50
+ context: MiddlewareOptions<Options_>,
51
51
  ): Effect.Effect<SuccessValue, E | unhandled, R>;
52
52
  }
53
53
 
54
- export interface MiddlewareOptions {
55
- readonly name: string;
56
- readonly functionType: FunctionType;
57
- readonly functionVisibility: FunctionVisibility;
58
- readonly args: unknown;
59
- }
54
+ export type MiddlewareOptions<Options_ = never> = {
55
+ readonly invocation: {
56
+ readonly name: string;
57
+ readonly functionType: FunctionType;
58
+ readonly functionVisibility: FunctionVisibility;
59
+ readonly args: unknown;
60
+ };
61
+ } & ([Options_] extends [never] ? unknown : { readonly options: Options_ });
60
62
 
61
- export interface AnyMiddlewareImpl extends MiddlewareImpl<any, any, any> {}
63
+ export interface AnyMiddlewareImpl {
64
+ (
65
+ effect: Effect.Effect<SuccessValue, any, any>,
66
+ context: MiddlewareOptions | MiddlewareOptions<unknown>,
67
+ ): Effect.Effect<SuccessValue, any, any>;
68
+ }
62
69
 
63
70
  /**
64
71
  * 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
72
+ * used—the class is a value-level carrier for the middleware's key, its
66
73
  * declared functionTypes, and its (lazily-evaluated) error schema, plus the
67
74
  * type-level `Provides`/`Error` metadata. It is deliberately not a
68
75
  * `Context.Tag`: implementations are registered through the group `Registry`
@@ -75,6 +82,7 @@ export interface MiddlewareSpec<
75
82
  Requires_,
76
83
  ErrorSchema_ extends Schema.Codec<any, any>,
77
84
  FunctionTypes_ extends FunctionType,
85
+ OptionsSchema_ extends Schema.Schema<any> = never,
78
86
  > {
79
87
  new (_: never): {
80
88
  readonly [TypeId]: TypeId;
@@ -84,11 +92,13 @@ export interface MiddlewareSpec<
84
92
  readonly key: Key_;
85
93
  readonly functionTypes: SupportedFunctionTypes;
86
94
  readonly error?: ErrorSchema_;
95
+ readonly options?: OptionsSchema_;
87
96
  readonly "~Provides": Provides_;
88
97
  readonly "~Requires": Requires_;
89
98
  readonly "~Error": ErrorSchema_;
90
99
  readonly "~FunctionTypes": FunctionTypes_;
91
100
  readonly "~Self": Self;
101
+ readonly "~Options": OptionsSchema_;
92
102
  }
93
103
 
94
104
  export interface AnyMiddlewareSpec {
@@ -96,12 +106,24 @@ export interface AnyMiddlewareSpec {
96
106
  readonly key: string;
97
107
  readonly functionTypes: SupportedFunctionTypes;
98
108
  readonly error?: Schema.Codec<any, any>;
109
+ readonly options?: Schema.Schema<any>;
99
110
  readonly "~Provides": any;
100
111
  readonly "~Requires": any;
101
112
  readonly "~Error": any;
102
113
  readonly "~FunctionTypes": FunctionType;
114
+ readonly "~Options": any;
103
115
  }
104
116
 
117
+ export type Options<MiddlewareSpec_ extends AnyMiddlewareSpec> =
118
+ MiddlewareSpec_["~Options"]["Type"];
119
+
120
+ export type WithoutOptions<MiddlewareSpec_ extends AnyMiddlewareSpec> =
121
+ MiddlewareSpec_ extends AnyMiddlewareSpec
122
+ ? [MiddlewareSpec_["~Options"]] extends [never]
123
+ ? MiddlewareSpec_
124
+ : never
125
+ : never;
126
+
105
127
  export type Key<MiddlewareSpec_ extends AnyMiddlewareSpec> =
106
128
  MiddlewareSpec_["key"];
107
129
 
@@ -160,10 +182,12 @@ export const MiddlewareSpec =
160
182
  const Key_ extends string,
161
183
  const FunctionTypesConfig_ extends SupportedFunctionTypes,
162
184
  ErrorSchema_ extends Schema.Codec<any, any> = never,
185
+ OptionsSchema_ extends Schema.Schema<any> = never,
163
186
  >(
164
187
  key: Key_,
165
188
  options: {
166
189
  readonly error?: () => ErrorSchema_;
190
+ readonly options?: () => OptionsSchema_;
167
191
  readonly functionTypes: FunctionTypesConfig_ &
168
192
  ValidateFunctionTypesConfig<FunctionTypesConfig_>;
169
193
  },
@@ -173,7 +197,8 @@ export const MiddlewareSpec =
173
197
  "provides" extends keyof Config ? Config["provides"] : never,
174
198
  "requires" extends keyof Config ? Config["requires"] : never,
175
199
  ErrorSchema_,
176
- FunctionTypesFromConfig<FunctionTypesConfig_>
200
+ FunctionTypesFromConfig<FunctionTypesConfig_>,
201
+ OptionsSchema_
177
202
  > => {
178
203
  const { query, mutation, action } = options.functionTypes;
179
204
  if (!query && !mutation && !action) {
@@ -194,6 +219,9 @@ export const MiddlewareSpec =
194
219
  if (options.error !== undefined) {
195
220
  Lazy.defineProperty(class_, "error", options.error);
196
221
  }
222
+ if (options.options !== undefined) {
223
+ Lazy.defineProperty(class_, "options", options.options);
224
+ }
197
225
  return class_;
198
226
  };
199
227
 
@@ -225,7 +253,7 @@ export const errorSchemas = (
225
253
  /**
226
254
  * A branded, unconstructible type carrying a spec-authoring error message.
227
255
  * Surfaced as (part of) a parameter type so the diagnostic names the exact
228
- * problem at the `.middleware()` / `.addFunction()` call site.
256
+ * problem at the `.middleware()`/`.addFunction()` call site.
229
257
  */
230
258
  export interface AttachmentError<Message extends string> {
231
259
  readonly "~@confect/core/MiddlewareSpec/AttachmentError": Message;
@@ -251,12 +279,12 @@ export type ValidateFunction<
251
279
  readonly functionProvenance: { readonly _tag: "Convex" };
252
280
  }
253
281
  ? 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`>
282
+ ? 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
283
  : never
256
284
  : [
257
285
  Extract<
258
286
  FunctionSpec.MiddlewareSpecs<FunctionSpec_>,
259
- { readonly key: Key<MiddlewareSpec_> }
287
+ { readonly key: Key<WithoutOptions<MiddlewareSpec_>> }
260
288
  >,
261
289
  ] extends [never]
262
290
  ? FunctionTypeOf<FunctionSpec_> extends FunctionTypes<MiddlewareSpec_>
@@ -276,13 +304,16 @@ export type ValidateAttach<
276
304
  Functions_ extends FunctionSpec.AnyWithProps,
277
305
  MiddlewareSpecs_ extends AnyMiddlewareSpec,
278
306
  > = [
279
- Extract<MiddlewareSpecs_, { readonly key: Key<MiddlewareSpec_> }>,
307
+ Extract<
308
+ MiddlewareSpecs_,
309
+ { readonly key: Key<WithoutOptions<MiddlewareSpec_>> }
310
+ >,
280
311
  ] extends [never]
281
312
  ? [Exclude<Requires<MiddlewareSpec_>, Provides<MiddlewareSpecs_>>] extends [
282
313
  never,
283
314
  ]
284
315
  ? 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`>
316
+ : AttachmentError<`Middleware "${Key<MiddlewareSpec_>}" requires services that no middleware attached earlier to this group provides—attach the providing middleware first`>
286
317
  : AttachmentError<`Middleware "${Key<MiddlewareSpec_>}" is already attached to this group`>;
287
318
 
288
319
  export type ValidateAddedFunction<
@@ -304,7 +335,7 @@ type GroupOverlap<
304
335
  ? [
305
336
  Extract<
306
337
  MiddlewareSpecs_,
307
- { readonly key: Key<FunctionMiddlewareSpec> }
338
+ { readonly key: Key<WithoutOptions<FunctionMiddlewareSpec>> }
308
339
  >,
309
340
  ] extends [never]
310
341
  ? never
@@ -316,8 +347,8 @@ type GroupOverlap<
316
347
  * The parameter-type validation applied to the group spec at
317
348
  * `GroupImpl.make`: every function's attached middleware must have its
318
349
  * `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
350
+ * (the group's or the function's own). Checked here—with the group fully
351
+ * assembled—rather than at the spec builders, because a function-level
321
352
  * middleware's `requires` may legitimately be satisfied by a group
322
353
  * middleware the function spec never sees, and group attachment is
323
354
  * declaratively order-independent with respect to `addFunction`.
@@ -346,9 +377,12 @@ export type ValidateFunctionAttach<
346
377
  FunctionProvenance_ extends FunctionProvenance.FunctionProvenance,
347
378
  MiddlewareSpecs_ extends AnyMiddlewareSpec,
348
379
  > = FunctionProvenance_ extends { readonly _tag: "Convex" }
349
- ? AttachmentError<`Plain Convex functions cannot have middleware — their raw handlers are passed through untouched`>
380
+ ? AttachmentError<`Plain Convex functions cannot have middleware—their raw handlers are passed through untouched`>
350
381
  : [
351
- Extract<MiddlewareSpecs_, { readonly key: Key<MiddlewareSpec_> }>,
382
+ Extract<
383
+ MiddlewareSpecs_,
384
+ { readonly key: Key<WithoutOptions<MiddlewareSpec_>> }
385
+ >,
352
386
  ] extends [never]
353
387
  ? RuntimeAndFunctionType_["functionType"] extends FunctionTypes<MiddlewareSpec_>
354
388
  ? unknown
@@ -7,7 +7,7 @@ import * as Schema from "effect/Schema";
7
7
  * Use this as the `paginationOpts` field of a paginated query's args schema.
8
8
  * Beyond `numItems` and `cursor`, Convex's pagination protocol sends the
9
9
  * optional fields (`endCursor`, `id`, `maximumRowsRead`, `maximumBytesRead`)
10
- * with real requests — most notably `usePaginatedQuery` from `convex/react`
10
+ * with real requests—most notably `usePaginatedQuery` from `convex/react`
11
11
  * always includes `id`, and includes `endCursor` when splitting pages.
12
12
  * Declaring them is required for the generated Convex argument validator to
13
13
  * accept those requests and for the fields to survive decoding and reach