@confect/core 10.0.0-next.8 → 10.0.0

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 (103) hide show
  1. package/CHANGELOG.md +966 -46
  2. package/README.md +3 -1
  3. package/dist/FunctionProvenance.d.ts +166 -68
  4. package/dist/FunctionProvenance.d.ts.map +1 -1
  5. package/dist/FunctionProvenance.js +55 -13
  6. package/dist/FunctionProvenance.js.map +1 -1
  7. package/dist/FunctionSpec.d.ts +320 -138
  8. package/dist/FunctionSpec.d.ts.map +1 -1
  9. package/dist/FunctionSpec.js +56 -13
  10. package/dist/FunctionSpec.js.map +1 -1
  11. package/dist/GenericId.d.ts.map +1 -1
  12. package/dist/GenericId.js +1 -1
  13. package/dist/GroupPath.d.ts +6 -10
  14. package/dist/GroupPath.d.ts.map +1 -1
  15. package/dist/GroupPath.js.map +1 -1
  16. package/dist/GroupSpec.d.ts +25 -12
  17. package/dist/GroupSpec.d.ts.map +1 -1
  18. package/dist/GroupSpec.js +51 -14
  19. package/dist/GroupSpec.js.map +1 -1
  20. package/dist/Identifier.d.ts +6 -5
  21. package/dist/Identifier.d.ts.map +1 -1
  22. package/dist/Identifier.js +6 -5
  23. package/dist/Identifier.js.map +1 -1
  24. package/dist/Lazy.d.ts +11 -14
  25. package/dist/Lazy.d.ts.map +1 -1
  26. package/dist/Lazy.js +11 -14
  27. package/dist/Lazy.js.map +1 -1
  28. package/dist/MiddlewareAttachment.d.ts +171 -0
  29. package/dist/MiddlewareAttachment.d.ts.map +1 -0
  30. package/dist/MiddlewareAttachment.js +48 -0
  31. package/dist/MiddlewareAttachment.js.map +1 -0
  32. package/dist/MiddlewareSpec.d.ts +198 -0
  33. package/dist/MiddlewareSpec.d.ts.map +1 -0
  34. package/dist/MiddlewareSpec.js +60 -0
  35. package/dist/MiddlewareSpec.js.map +1 -0
  36. package/dist/PaginationError.d.ts +20 -0
  37. package/dist/PaginationError.d.ts.map +1 -0
  38. package/dist/PaginationError.js +31 -0
  39. package/dist/PaginationError.js.map +1 -0
  40. package/dist/PaginationOptions.d.ts +23 -0
  41. package/dist/PaginationOptions.d.ts.map +1 -0
  42. package/dist/PaginationOptions.js +29 -0
  43. package/dist/PaginationOptions.js.map +1 -0
  44. package/dist/PaginationResult.d.ts +1 -0
  45. package/dist/PaginationResult.d.ts.map +1 -1
  46. package/dist/PaginationResult.js.map +1 -1
  47. package/dist/Ref.d.ts +135 -47
  48. package/dist/Ref.d.ts.map +1 -1
  49. package/dist/Ref.js +104 -46
  50. package/dist/Ref.js.map +1 -1
  51. package/dist/Refs.d.ts +10 -5
  52. package/dist/Refs.d.ts.map +1 -1
  53. package/dist/Refs.js +5 -4
  54. package/dist/Refs.js.map +1 -1
  55. package/dist/SchemaToValidator.d.ts +99 -0
  56. package/dist/SchemaToValidator.d.ts.map +1 -0
  57. package/dist/SchemaToValidator.js +158 -0
  58. package/dist/SchemaToValidator.js.map +1 -0
  59. package/dist/Spec.d.ts +16 -12
  60. package/dist/Spec.d.ts.map +1 -1
  61. package/dist/Spec.js +14 -9
  62. package/dist/Spec.js.map +1 -1
  63. package/dist/SystemFields.d.ts +16 -15
  64. package/dist/SystemFields.d.ts.map +1 -1
  65. package/dist/SystemFields.js +16 -15
  66. package/dist/SystemFields.js.map +1 -1
  67. package/dist/Table.d.ts +75 -0
  68. package/dist/Table.d.ts.map +1 -0
  69. package/dist/Table.js +77 -0
  70. package/dist/Table.js.map +1 -0
  71. package/dist/Types.d.ts +8 -2
  72. package/dist/Types.d.ts.map +1 -1
  73. package/dist/Types.js.map +1 -1
  74. package/dist/UserIdentity.d.ts.map +1 -1
  75. package/dist/index.d.ts +6 -1
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +9 -4
  78. package/package.json +17 -15
  79. package/src/FunctionProvenance.ts +197 -34
  80. package/src/FunctionSpec.ts +332 -86
  81. package/src/GroupPath.ts +6 -12
  82. package/src/GroupSpec.ts +184 -25
  83. package/src/Identifier.ts +6 -5
  84. package/src/Lazy.ts +11 -14
  85. package/src/MiddlewareAttachment.ts +106 -0
  86. package/src/MiddlewareSpec.ts +392 -0
  87. package/src/PaginationError.ts +78 -0
  88. package/src/PaginationOptions.ts +23 -0
  89. package/src/PaginationResult.ts +4 -0
  90. package/src/Ref.ts +468 -236
  91. package/src/Refs.ts +43 -18
  92. package/src/SchemaToValidator.ts +612 -0
  93. package/src/Spec.ts +50 -39
  94. package/src/SystemFields.ts +32 -36
  95. package/src/Table.ts +427 -0
  96. package/src/Types.ts +9 -3
  97. package/src/index.ts +6 -1
  98. package/dist/Registry.d.ts +0 -24
  99. package/dist/Registry.d.ts.map +0 -1
  100. package/dist/Registry.js +0 -19
  101. package/dist/Registry.js.map +0 -1
  102. package/dist/tsconfig.src.tsbuildinfo +0 -1
  103. package/src/Registry.ts +0 -30
package/src/GroupSpec.ts CHANGED
@@ -1,10 +1,13 @@
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";
6
+ import type * as MiddlewareSpec from "./MiddlewareSpec";
4
7
  import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
5
8
  import { validateConfectFunctionIdentifier } from "./Identifier";
6
9
 
7
- export const TypeId = "@confect/core/GroupSpec";
10
+ export const TypeId = "~@confect/core/GroupSpec";
8
11
  export type TypeId = typeof TypeId;
9
12
 
10
13
  export const isGroupSpec = (u: unknown): u is AnyWithProps =>
@@ -17,33 +20,71 @@ export interface GroupSpec<
17
20
  // Subgroups may be of any runtime, independent of this group's own runtime: a
18
21
  // group is only a namespace for its children, which are otherwise-independent
19
22
  // modules. Functions, by contrast, stay homogeneous (a Node group only accepts
20
- // Node actions) — `addFunction` keeps the `<Runtime>` bound below.
23
+ // Node actions)—`addFunction` keeps the `<Runtime>` bound below.
21
24
  Groups_ extends AnyWithProps = never,
25
+ MiddlewareSpecs_ extends MiddlewareSpec.AnyMiddlewareSpec = never,
22
26
  > {
23
27
  readonly [TypeId]: TypeId;
24
28
  readonly runtime: Runtime;
25
29
  readonly name: Name_;
26
30
  readonly functions: {
27
- [FunctionName in FunctionSpec.Name<
28
- FunctionSpec.AnyWithPropsWithRuntime<Runtime>
29
- >]: FunctionSpec.WithName<Functions_, FunctionName>;
31
+ [
32
+ FunctionName in FunctionSpec.Name<
33
+ FunctionSpec.AnyWithPropsWithRuntime<Runtime>
34
+ >
35
+ ]: FunctionSpec.WithName<Functions_, FunctionName>;
30
36
  };
31
37
  readonly groups: {
32
38
  [GroupName in Name<Groups_>]: WithName<Groups_, GroupName>;
33
39
  };
40
+ readonly middlewareSpecs: ReadonlyArray<MiddlewareSpecs_>;
41
+ readonly middlewareAttachments: ReadonlyArray<
42
+ MiddlewareAttachment.MiddlewareAttachment<MiddlewareSpecs_>
43
+ >;
44
+ readonly "~Functions": Functions_;
45
+ readonly "~Groups": Groups_;
34
46
 
35
47
  addFunction<Function extends FunctionSpec.AnyWithPropsWithRuntime<Runtime>>(
36
- function_: Function,
37
- ): GroupSpec<Runtime, Name_, Functions_ | Function, Groups_>;
48
+ function_: Function &
49
+ MiddlewareSpec.ValidateAddedFunction<Function, MiddlewareSpecs_>,
50
+ ): GroupSpec<
51
+ Runtime,
52
+ Name_,
53
+ Functions_ | Function,
54
+ Groups_,
55
+ MiddlewareSpecs_
56
+ >;
38
57
 
39
58
  addGroup<Group extends AnyWithProps>(
40
59
  group: Group,
41
- ): GroupSpec<Runtime, Name_, Functions_, Groups_ | Group>;
60
+ ): GroupSpec<Runtime, Name_, Functions_, Groups_ | Group, MiddlewareSpecs_>;
42
61
 
43
62
  addGroupAt<const AtName extends string, Group extends AnyWithProps>(
44
63
  name: AtName,
45
64
  group: Group,
46
- ): GroupSpec<Runtime, Name_, Functions_, Groups_ | NamedAt<Group, AtName>>;
65
+ ): GroupSpec<
66
+ Runtime,
67
+ Name_,
68
+ Functions_,
69
+ Groups_ | NamedAt<Group, AtName>,
70
+ MiddlewareSpecs_
71
+ >;
72
+
73
+ middleware<MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec>(
74
+ middlewareSpec: MiddlewareSpec_ &
75
+ MiddlewareSpec.ValidateAttach<
76
+ MiddlewareSpec_,
77
+ Functions_,
78
+ MiddlewareSpecs_
79
+ >,
80
+ ...options: MiddlewareAttachment.Args<NoInfer<MiddlewareSpec_>>
81
+ ): GroupSpec<
82
+ Runtime,
83
+ Name_,
84
+ Functions_,
85
+ Groups_,
86
+ MiddlewareSpecs_ | MiddlewareSpec_
87
+ >;
47
88
  }
48
89
 
49
90
  export interface Any {
@@ -54,7 +95,8 @@ export interface AnyWithProps extends GroupSpec<
54
95
  RuntimeAndFunctionType.Runtime,
55
96
  string,
56
97
  FunctionSpec.AnyWithProps,
57
- AnyWithProps
98
+ AnyWithProps,
99
+ MiddlewareSpec.AnyMiddlewareSpec
58
100
  > {}
59
101
 
60
102
  export interface AnyWithPropsWithRuntime<
@@ -63,16 +105,18 @@ export interface AnyWithPropsWithRuntime<
63
105
  Runtime,
64
106
  string,
65
107
  FunctionSpec.AnyWithPropsWithRuntime<Runtime>,
66
- AnyWithPropsWithRuntime<Runtime>
108
+ AnyWithPropsWithRuntime<Runtime>,
109
+ MiddlewareSpec.AnyMiddlewareSpec
67
110
  > {}
68
111
 
69
112
  export type Name<Group extends AnyWithProps> = Group["name"];
70
113
 
71
- export type Functions<Group extends AnyWithProps> =
72
- Group["functions"][keyof Group["functions"]];
114
+ export type Functions<Group extends AnyWithProps> = Group["~Functions"];
115
+
116
+ export type Groups<Group extends AnyWithProps> = Group["~Groups"];
73
117
 
74
- export type Groups<Group extends AnyWithProps> =
75
- Group["groups"][keyof Group["groups"]];
118
+ export type MiddlewareSpecs<Group extends AnyWithProps> =
119
+ Group["middlewareSpecs"][number];
76
120
 
77
121
  export type GroupNames<Group extends AnyWithProps> = [Groups<Group>] extends [
78
122
  never,
@@ -85,7 +129,10 @@ export type WithName<
85
129
  Name_ extends Name<Group>,
86
130
  > = Extract<Group, { readonly name: Name_ }>;
87
131
 
88
- /** Assigns a segment name to a leaf group created with {@link make} for typing and refs. */
132
+ /**
133
+ * Assigns a segment name to a leaf group created with {@link make} for typing
134
+ * and refs.
135
+ */
89
136
  export type NamedAt<Group extends Any, Name_ extends string> = Omit<
90
137
  Group,
91
138
  "name"
@@ -101,25 +148,51 @@ export type AddGroups<
101
148
  infer Runtime,
102
149
  infer Name_,
103
150
  infer Functions_,
104
- infer Groups_
151
+ infer Groups_,
152
+ infer MiddlewareSpecs_
105
153
  >
106
- ? GroupSpec<Runtime, Name_, Functions_, Groups_ | ExtraGroups>
154
+ ? GroupSpec<
155
+ Runtime,
156
+ Name_,
157
+ Functions_,
158
+ Groups_ | ExtraGroups,
159
+ MiddlewareSpecs_
160
+ >
107
161
  : never;
108
162
 
109
163
  const Proto = {
110
164
  [TypeId]: TypeId,
111
165
 
166
+ get middlewareSpecs() {
167
+ return this.middlewareAttachments.map(({ spec }) => spec);
168
+ },
169
+
112
170
  addFunction<Function extends FunctionSpec.AnyWithProps>(
113
171
  this: Any,
114
172
  function_: Function,
115
173
  ) {
116
174
  const this_ = this as AnyWithProps;
117
175
 
176
+ const overlapping = function_.middlewareAttachments.find(
177
+ ({ spec: functionMiddlewareSpec }) =>
178
+ !("options" in functionMiddlewareSpec) &&
179
+ this_.middlewareAttachments.some(
180
+ ({ spec: groupMiddlewareSpec }) =>
181
+ groupMiddlewareSpec.key === functionMiddlewareSpec.key,
182
+ ),
183
+ );
184
+ if (overlapping !== undefined) {
185
+ throw new Error(
186
+ `Middleware "${overlapping.spec.key}" is attached to both function "${function_.name}" and its group`,
187
+ );
188
+ }
189
+
118
190
  return makeProto({
119
191
  runtime: this_.runtime,
120
192
  name: this_.name,
121
193
  functions: Record.set(this_.functions, function_.name, function_),
122
194
  groups: this_.groups,
195
+ middlewareAttachments: this_.middlewareAttachments,
123
196
  });
124
197
  },
125
198
 
@@ -132,6 +205,7 @@ const Proto = {
132
205
  name: this_.name,
133
206
  functions: this_.functions,
134
207
  groups: Record.set(this_.groups, group_.name, group_),
208
+ middlewareAttachments: this_.middlewareAttachments,
135
209
  });
136
210
  },
137
211
 
@@ -144,32 +218,93 @@ const Proto = {
144
218
  name: this_.name,
145
219
  functions: this_.functions,
146
220
  groups: Record.set(this_.groups, name, withName(name, group_)),
221
+ middlewareAttachments: this_.middlewareAttachments,
147
222
  });
148
223
  },
149
- };
224
+
225
+ middleware<MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec>(
226
+ this: Any,
227
+ middlewareSpec: MiddlewareSpec_,
228
+ ...options: ReadonlyArray<unknown>
229
+ ) {
230
+ const this_ = this as AnyWithProps;
231
+
232
+ if (
233
+ !("options" in middlewareSpec) &&
234
+ this_.middlewareAttachments.some(
235
+ ({ spec }) => spec.key === middlewareSpec.key,
236
+ )
237
+ ) {
238
+ throw new Error(
239
+ `Middleware "${middlewareSpec.key}" is already attached to this group`,
240
+ );
241
+ }
242
+
243
+ for (const function_ of Object.values(this_.functions)) {
244
+ if (
245
+ !("options" in middlewareSpec) &&
246
+ function_.middlewareAttachments.some(
247
+ ({ spec }) => spec.key === middlewareSpec.key,
248
+ )
249
+ ) {
250
+ throw new Error(
251
+ `Middleware "${middlewareSpec.key}" is attached to both function "${function_.name}" and its group`,
252
+ );
253
+ }
254
+ }
255
+
256
+ return makeProto({
257
+ runtime: this_.runtime,
258
+ name: this_.name,
259
+ functions: this_.functions,
260
+ groups: this_.groups,
261
+ middlewareAttachments: [
262
+ ...this_.middlewareAttachments,
263
+ { spec: middlewareSpec, options: options[0] },
264
+ ],
265
+ });
266
+ },
267
+ } satisfies ThisType<AnyWithProps>;
150
268
 
151
269
  const makeProto = <
152
270
  Runtime extends RuntimeAndFunctionType.Runtime,
153
271
  Name_ extends string,
154
272
  Functions_ extends FunctionSpec.AnyWithPropsWithRuntime<Runtime>,
155
273
  Groups_ extends AnyWithPropsWithRuntime<Runtime>,
274
+ MiddlewareAttachments_ extends
275
+ ReadonlyArray<MiddlewareAttachment.MiddlewareAttachment>,
156
276
  >({
157
277
  runtime,
158
278
  name,
159
279
  functions,
160
280
  groups,
281
+ middlewareAttachments,
161
282
  }: {
162
283
  runtime: Runtime;
163
284
  name: Name_;
164
285
  functions: Record.ReadonlyRecord<string, Functions_>;
165
286
  groups: Record.ReadonlyRecord<string, Groups_>;
166
- }): GroupSpec<Runtime, Name_, Functions_, Groups_> =>
287
+ middlewareAttachments: MiddlewareAttachments_;
288
+ }): GroupSpec<
289
+ Runtime,
290
+ Name_,
291
+ Functions_,
292
+ Groups_,
293
+ MiddlewareAttachments_[number]["spec"]
294
+ > =>
167
295
  Object.assign(Object.create(Proto), {
168
296
  runtime,
169
297
  name,
170
298
  functions,
171
299
  groups,
172
- });
300
+ middlewareAttachments,
301
+ }) as GroupSpec<
302
+ Runtime,
303
+ Name_,
304
+ Functions_,
305
+ Groups_,
306
+ MiddlewareAttachments_[number]["spec"]
307
+ >;
173
308
 
174
309
  export const make = (): GroupSpec<"Convex", ""> =>
175
310
  makeProto({
@@ -177,6 +312,7 @@ export const make = (): GroupSpec<"Convex", ""> =>
177
312
  name: "",
178
313
  functions: Record.empty(),
179
314
  groups: Record.empty(),
315
+ middlewareAttachments: [],
180
316
  });
181
317
 
182
318
  export const makeAt = <const Name_ extends string>(
@@ -189,6 +325,7 @@ export const makeAt = <const Name_ extends string>(
189
325
  name,
190
326
  functions: Record.empty(),
191
327
  groups: Record.empty(),
328
+ middlewareAttachments: [],
192
329
  });
193
330
  };
194
331
 
@@ -198,6 +335,7 @@ export const makeNode = (): GroupSpec<"Node", ""> =>
198
335
  name: "",
199
336
  functions: Record.empty(),
200
337
  groups: Record.empty(),
338
+ middlewareAttachments: [],
201
339
  });
202
340
 
203
341
  export const makeNodeAt = <const Name_ extends string>(
@@ -210,13 +348,15 @@ export const makeNodeAt = <const Name_ extends string>(
210
348
  name,
211
349
  functions: Record.empty(),
212
350
  groups: Record.empty(),
351
+ middlewareAttachments: [],
213
352
  });
214
353
  };
215
354
 
216
- export const withName = <const Name_ extends string>(
355
+ export function withName<const Name_ extends string, Group extends Any>(
217
356
  name: Name_,
218
- group: Any,
219
- ): AnyWithProps => {
357
+ group: Group,
358
+ ): NamedAt<Group, Name_>;
359
+ export function withName(name: string, group: Any): AnyWithProps {
220
360
  validateConfectFunctionIdentifier(name);
221
361
  const group_ = group as AnyWithProps;
222
362
 
@@ -229,5 +369,24 @@ export const withName = <const Name_ extends string>(
229
369
  name,
230
370
  functions: group_.functions,
231
371
  groups: group_.groups,
372
+ middlewareAttachments: group_.middlewareAttachments,
373
+ });
374
+ }
375
+
376
+ export const validateMiddleware = (
377
+ group: AnyWithProps,
378
+ ): Result.Result<void, MiddlewareAttachment.ValidationError> =>
379
+ Result.gen(function* () {
380
+ yield* MiddlewareAttachment.validateAll(
381
+ group.middlewareAttachments,
382
+ `group "${group.name}"`,
383
+ );
384
+ for (const function_ of Object.values(group.functions)) {
385
+ yield* MiddlewareAttachment.validateAll(
386
+ [...group.middlewareAttachments, ...function_.middlewareAttachments],
387
+ `function "${function_.name}"`,
388
+ );
389
+ }
390
+ for (const child of Object.values(group.groups))
391
+ yield* validateMiddleware(child);
232
392
  });
233
- };
package/src/Identifier.ts CHANGED
@@ -95,15 +95,16 @@ export const validateConfectFunctionIdentifier = (identifier: string) => {
95
95
  };
96
96
 
97
97
  /**
98
- * Validate that `identifier` is suitable as a Convex table name (and, equivalently,
99
- * as a `confect/tables/<identifier>.ts` filename).
98
+ * Validate that `identifier` is suitable as a Convex table name (and,
99
+ * equivalently, 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
+ *
103
+ * - Must match `/^[A-Za-z][A-Za-z0-9_]*$/`—letter-leading, alphanumeric plus
103
104
  * underscore. No `$` (not a valid Convex table name character); no leading
104
105
  * `_` (Convex reserves `_<name>` for its system tables).
105
- * - Must not be a reserved JavaScript identifier, so the name can also be used
106
- * as a binding name in generated code without escaping.
106
+ * - Must not be a reserved JavaScript identifier, so the name can also be used as
107
+ * a binding name in generated code without escaping.
107
108
  */
108
109
  export const validateConfectTableIdentifier = (identifier: string) => {
109
110
  if (!matchesTableNamePattern(identifier)) {
package/src/Lazy.ts CHANGED
@@ -1,22 +1,19 @@
1
1
  /**
2
2
  * Install a lazy memoised property on `target`. The first access runs
3
- * `compute()` and replaces the getter with a plain, non-writable data
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
7
- * observably indistinguishable.
3
+ * `compute()` and replaces the getter with a plain, non-writable data property
4
+ * whose value is the computed result. Subsequent accesses hit the V8 fast path
5
+ * for own data properties—no function call, identical returned reference—so
6
+ * first and second-and-subsequent accesses are observably indistinguishable.
8
7
  *
9
- * The replacement property is `enumerable: true` so the lazy property
10
- * still participates in `Object.keys` / `JSON.stringify` after it
11
- * materialises, matching the shape of a plain data property. The property
12
- * is also `enumerable` before materialising, so presence checks
13
- * (`"key" in target`, `Object.hasOwn(target, key)`) observe it without
14
- * forcing the computation.
8
+ * The replacement property is `enumerable: true` so the lazy property still
9
+ * participates in `Object.keys`/`JSON.stringify` after it materialises,
10
+ * matching the shape of a plain data property. The property is also
11
+ * `enumerable` before materialising, so presence checks (`"key" in target`,
12
+ * `Object.hasOwn(target, key)`) observe it without forcing the computation.
15
13
  *
16
14
  * 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.
15
+ * `@confect/core`'s lazy `FunctionSpec` schemas and lazy `Table`
16
+ * `Fields`/`Doc`), so there is no chance of the two drifting apart.
20
17
  */
21
18
  export const defineProperty = <T extends object, K extends PropertyKey>(
22
19
  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
+ };