@confect/core 10.0.0-next.21 → 10.0.0-next.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +168 -81
- package/dist/FunctionProvenance.d.ts +34 -26
- package/dist/FunctionProvenance.d.ts.map +1 -1
- package/dist/FunctionProvenance.js +14 -13
- package/dist/FunctionProvenance.js.map +1 -1
- package/dist/FunctionSpec.d.ts +15 -9
- package/dist/FunctionSpec.d.ts.map +1 -1
- package/dist/FunctionSpec.js +12 -6
- package/dist/FunctionSpec.js.map +1 -1
- package/dist/GroupPath.d.ts +5 -6
- package/dist/GroupPath.d.ts.map +1 -1
- package/dist/GroupPath.js.map +1 -1
- package/dist/GroupSpec.d.ts +10 -3
- package/dist/GroupSpec.d.ts.map +1 -1
- package/dist/GroupSpec.js +33 -19
- package/dist/GroupSpec.js.map +1 -1
- package/dist/Identifier.d.ts +6 -5
- package/dist/Identifier.d.ts.map +1 -1
- package/dist/Identifier.js +6 -5
- package/dist/Identifier.js.map +1 -1
- package/dist/Lazy.d.ts +11 -13
- package/dist/Lazy.d.ts.map +1 -1
- package/dist/Lazy.js +11 -13
- package/dist/Lazy.js.map +1 -1
- package/dist/MiddlewareAttachment.d.ts +171 -0
- package/dist/MiddlewareAttachment.d.ts.map +1 -0
- package/dist/MiddlewareAttachment.js +48 -0
- package/dist/MiddlewareAttachment.js.map +1 -0
- package/dist/MiddlewareSpec.d.ts +59 -45
- package/dist/MiddlewareSpec.d.ts.map +1 -1
- package/dist/MiddlewareSpec.js +9 -4
- package/dist/MiddlewareSpec.js.map +1 -1
- package/dist/PaginationError.d.ts +9 -3
- package/dist/PaginationError.d.ts.map +1 -1
- package/dist/PaginationError.js +9 -3
- package/dist/PaginationError.js.map +1 -1
- package/dist/PaginationOptions.d.ts +3 -3
- package/dist/PaginationOptions.js +3 -3
- package/dist/PaginationOptions.js.map +1 -1
- package/dist/Ref.d.ts +42 -35
- package/dist/Ref.d.ts.map +1 -1
- package/dist/Ref.js +32 -30
- package/dist/Ref.js.map +1 -1
- package/dist/Refs.d.ts +1 -1
- package/dist/Refs.d.ts.map +1 -1
- package/dist/Refs.js +3 -2
- package/dist/Refs.js.map +1 -1
- package/dist/SchemaToValidator.d.ts.map +1 -1
- package/dist/SchemaToValidator.js +4 -2
- package/dist/SchemaToValidator.js.map +1 -1
- package/dist/Spec.d.ts +15 -12
- package/dist/Spec.d.ts.map +1 -1
- package/dist/Spec.js +13 -8
- package/dist/Spec.js.map +1 -1
- package/dist/SystemFields.d.ts +15 -14
- package/dist/SystemFields.d.ts.map +1 -1
- package/dist/SystemFields.js +11 -10
- package/dist/SystemFields.js.map +1 -1
- package/dist/Table.d.ts +1 -0
- package/dist/Table.d.ts.map +1 -1
- package/dist/Table.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/package.json +8 -7
- package/src/FunctionProvenance.ts +35 -27
- package/src/FunctionSpec.ts +33 -15
- package/src/GroupPath.ts +5 -6
- package/src/GroupSpec.ts +83 -31
- package/src/Identifier.ts +6 -5
- package/src/Lazy.ts +11 -13
- package/src/MiddlewareAttachment.ts +106 -0
- package/src/MiddlewareSpec.ts +79 -43
- package/src/PaginationError.ts +9 -3
- package/src/PaginationOptions.ts +3 -3
- package/src/Ref.ts +76 -65
- package/src/Refs.ts +3 -3
- package/src/SchemaToValidator.ts +53 -44
- package/src/Spec.ts +50 -39
- package/src/SystemFields.ts +18 -17
- package/src/Table.ts +3 -2
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@confect/core",
|
|
3
3
|
"description": "Shared specs and schemas used by all Confect packages",
|
|
4
|
-
"version": "10.0.0-next.
|
|
4
|
+
"version": "10.0.0-next.23",
|
|
5
5
|
"author": "RJ Dellecese",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/rjdellecese/confect/issues"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@effect/vitest": "4.0.0-rc.
|
|
10
|
+
"@effect/vitest": "4.0.0-rc.115",
|
|
11
11
|
"@types/node": "24.13.3",
|
|
12
12
|
"confect-bench-harness": "0.0.0",
|
|
13
|
-
"effect": "4.0.0-rc.
|
|
14
|
-
"tsdown": "0.
|
|
13
|
+
"effect": "4.0.0-rc.115",
|
|
14
|
+
"tsdown": "0.23.0",
|
|
15
15
|
"tsx": "4.23.13",
|
|
16
16
|
"typescript": "7.0.2",
|
|
17
17
|
"unrun": "0.3.1",
|
|
18
|
-
"vitest": "
|
|
18
|
+
"vitest": "5.0.0"
|
|
19
19
|
},
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=24"
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"module": "./dist/index.js",
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"convex": "^1.32.0",
|
|
54
|
-
"effect": "^4.0.0-rc.
|
|
54
|
+
"effect": "^4.0.0-rc.115"
|
|
55
55
|
},
|
|
56
56
|
"repository": {
|
|
57
57
|
"type": "git",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"clean": "shx rm -rf dist coverage node_modules",
|
|
66
66
|
"dev": "tsdown --watch --no-clean --config-loader unrun",
|
|
67
67
|
"test": "vitest run",
|
|
68
|
-
"typecheck": "tsc -b tsconfig.src.json"
|
|
68
|
+
"typecheck": "tsc -b tsconfig.src.json",
|
|
69
|
+
"typecheck:inexact": "tsc --noEmit --project tsconfig.json --exactOptionalPropertyTypes false"
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -27,7 +27,9 @@ export interface ArgsSchema<
|
|
|
27
27
|
readonly fields: ArgsFields_;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Erased structural view used after the exact field map is no longer needed.
|
|
32
|
+
*/
|
|
31
33
|
export type AnyArgsSchema = ArgsSchema<ArgsFields>;
|
|
32
34
|
|
|
33
35
|
export type FunctionProvenance = Data.TaggedEnum<{
|
|
@@ -44,12 +46,11 @@ export type FunctionProvenance = Data.TaggedEnum<{
|
|
|
44
46
|
}>;
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
|
-
* The declaration shape of a Confect function
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* composed.
|
|
49
|
+
* The declaration shape of a Confect function—orthogonal to both the provenance
|
|
50
|
+
* origin (Confect vs Convex) and the function type (query/mutation/action). A
|
|
51
|
+
* `Standard` function declares its args fields and returns schema directly; a
|
|
52
|
+
* `Paginated` function declares user-args fields and an item schema from which
|
|
53
|
+
* the Convex-facing `args`/`returns` are composed.
|
|
53
54
|
*/
|
|
54
55
|
export type ConfectKind = Standard | Paginated;
|
|
55
56
|
|
|
@@ -62,11 +63,17 @@ export interface Paginated<
|
|
|
62
63
|
Item extends Schema.Codec<any, any> = Schema.Codec<any, any>,
|
|
63
64
|
> {
|
|
64
65
|
readonly _tag: "Paginated";
|
|
65
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* User-declared args—no `paginationOpts`.
|
|
68
|
+
*/
|
|
66
69
|
readonly userArgs: ArgsSchema<UserArgsFields_>;
|
|
67
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Page element schema.
|
|
72
|
+
*/
|
|
68
73
|
readonly item: Item;
|
|
69
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Mutable array of items—the page decode target.
|
|
76
|
+
*/
|
|
70
77
|
readonly page: Schema.Codec<any, any>;
|
|
71
78
|
}
|
|
72
79
|
|
|
@@ -105,19 +112,20 @@ export const FunctionProvenance = Data.taggedEnum<FunctionProvenance>();
|
|
|
105
112
|
const Standard: Standard = { _tag: "Standard" };
|
|
106
113
|
|
|
107
114
|
/**
|
|
108
|
-
* Build a `Confect` provenance from lazy args-fields and schema thunks.
|
|
109
|
-
* `
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* assembled `_generated/spec.ts` cheap
|
|
113
|
-
* `Schema.Array(...)` work runs at module load; it is
|
|
114
|
-
* invocation that actually compiles validators or runs a
|
|
115
|
+
* Build a `Confect` provenance from lazy args-fields and schema thunks. `args`,
|
|
116
|
+
* `returns`, and `error` are exposed as sync lazy memoised getters (via
|
|
117
|
+
* {@link Lazy.defineProperty}) that only evaluate their thunk on first access,
|
|
118
|
+
* mirroring how `Table` defers `Fields`/`Doc`. This keeps importing the
|
|
119
|
+
* assembled `_generated/spec.ts` cheap—no
|
|
120
|
+
* `Schema.Struct(...)`/`Schema.Array(...)` work runs at module load; it is
|
|
121
|
+
* deferred to the first invocation that actually compiles validators or runs a
|
|
122
|
+
* codec.
|
|
115
123
|
*
|
|
116
124
|
* The object is built by hand rather than through `FunctionProvenance.Confect`
|
|
117
125
|
* because the `Data` constructor copies its input with `Object.assign`, which
|
|
118
|
-
* would force the getters at construction time and defeat the laziness.
|
|
119
|
-
*
|
|
120
|
-
*
|
|
126
|
+
* would force the getters at construction time and defeat the laziness. `error`
|
|
127
|
+
* is only installed when an `errorThunk` is provided, so its absence is
|
|
128
|
+
* observable via `"error" in provenance` without forcing anything; nothing
|
|
121
129
|
* relies on `Data`'s structural `Equal`/`Hash` for provenance values.
|
|
122
130
|
*/
|
|
123
131
|
export const Confect = <
|
|
@@ -141,8 +149,8 @@ export const Confect = <
|
|
|
141
149
|
};
|
|
142
150
|
|
|
143
151
|
/**
|
|
144
|
-
* The composed args schema of a paginated query: the user-declared fields
|
|
145
|
-
*
|
|
152
|
+
* The composed args schema of a paginated query: the user-declared fields plus
|
|
153
|
+
* the `paginationOpts` field managed by Convex's pagination protocol.
|
|
146
154
|
*
|
|
147
155
|
* The fields-first representation keeps the user-declared map available for
|
|
148
156
|
* type-level composition without reconstructing it from an erased schema.
|
|
@@ -157,8 +165,8 @@ export type PaginatedArgs<UserArgsFields extends ArgsFields> = Schema.Struct<
|
|
|
157
165
|
>;
|
|
158
166
|
|
|
159
167
|
/**
|
|
160
|
-
* The composed returns schema of a paginated query: a `PaginationResult` of
|
|
161
|
-
*
|
|
168
|
+
* The composed returns schema of a paginated query: a `PaginationResult` of the
|
|
169
|
+
* item schema. Same deferred-conditional device as {@link PaginatedArgs}.
|
|
162
170
|
*/
|
|
163
171
|
export type PaginatedReturns<Item extends Schema.Codec<any, any>> =
|
|
164
172
|
PaginationResult.PaginationResult<Item> extends infer ComposedReturns extends
|
|
@@ -183,8 +191,8 @@ export interface AnyConfectPaginated extends AnyConfect {
|
|
|
183
191
|
|
|
184
192
|
/**
|
|
185
193
|
* Build the provenance of a paginated query from lazy schema thunks, with the
|
|
186
|
-
* same laziness contract as {@link Confect}. The user-facing schemas live on
|
|
187
|
-
*
|
|
194
|
+
* same laziness contract as {@link Confect}. The user-facing schemas live on the
|
|
195
|
+
* `Paginated` kind (`kind.userArgs`, `kind.item`, `kind.page`)—the kind
|
|
188
196
|
* container is built eagerly (it is cheap) while the schemas inside it stay
|
|
189
197
|
* lazy. The composed Convex-facing `args`/`returns` are derived lazily from
|
|
190
198
|
* them: `args` spreads the user fields plus `paginationOpts`, and `returns`
|
|
@@ -216,7 +224,7 @@ export const ConfectPaginated = <
|
|
|
216
224
|
const fields = paginatedKind.userArgs.fields;
|
|
217
225
|
if ("paginationOpts" in fields) {
|
|
218
226
|
throw new globalThis.Error(
|
|
219
|
-
"A paginated query's args schema must not declare `paginationOpts
|
|
227
|
+
"A paginated query's args schema must not declare `paginationOpts`—" +
|
|
220
228
|
"it is added automatically from the `PaginationOptions` schema",
|
|
221
229
|
);
|
|
222
230
|
}
|
package/src/FunctionSpec.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as MiddlewareAttachment from "./MiddlewareAttachment";
|
|
1
2
|
import type {
|
|
2
3
|
DefaultFunctionArgs,
|
|
3
4
|
FunctionType,
|
|
@@ -32,6 +33,9 @@ export interface FunctionSpec<
|
|
|
32
33
|
readonly name: Name_;
|
|
33
34
|
readonly functionProvenance: FunctionProvenance_;
|
|
34
35
|
readonly middlewareSpecs: ReadonlyArray<MiddlewareSpecs_>;
|
|
36
|
+
readonly middlewareAttachments: ReadonlyArray<
|
|
37
|
+
MiddlewareAttachment.MiddlewareAttachment<MiddlewareSpecs_>
|
|
38
|
+
>;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export interface Builder<
|
|
@@ -55,6 +59,7 @@ export interface Builder<
|
|
|
55
59
|
FunctionProvenance_,
|
|
56
60
|
MiddlewareSpecs_
|
|
57
61
|
>,
|
|
62
|
+
...options: MiddlewareAttachment.Args<NoInfer<MiddlewareSpec_>>
|
|
58
63
|
): Builder<
|
|
59
64
|
RuntimeAndFunctionType_,
|
|
60
65
|
FunctionVisibility_,
|
|
@@ -136,7 +141,9 @@ export type Name<FunctionSpec_ extends AnyWithProps> = FunctionSpec_["name"];
|
|
|
136
141
|
export type MiddlewareSpecs<FunctionSpec_ extends AnyWithProps> =
|
|
137
142
|
FunctionSpec_["middlewareSpecs"][number];
|
|
138
143
|
|
|
139
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* The field map declared by a Confect-provenance spec.
|
|
146
|
+
*/
|
|
140
147
|
export type ArgsFields<FunctionSpec_ extends AnyWithProps> =
|
|
141
148
|
FunctionSpec_ extends {
|
|
142
149
|
readonly functionProvenance: {
|
|
@@ -152,7 +159,9 @@ export type ArgsFields<FunctionSpec_ extends AnyWithProps> =
|
|
|
152
159
|
? FunctionProvenance.ArgsFields
|
|
153
160
|
: never;
|
|
154
161
|
|
|
155
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* The args schema assembled from a Confect-provenance spec's field map.
|
|
164
|
+
*/
|
|
156
165
|
export type ArgsSchema<FunctionSpec_ extends AnyWithProps> =
|
|
157
166
|
FunctionSpec_ extends {
|
|
158
167
|
readonly functionProvenance: {
|
|
@@ -186,10 +195,10 @@ export type ErrorSchema<FunctionSpec_ extends AnyWithProps> =
|
|
|
186
195
|
FunctionSpec_ extends {
|
|
187
196
|
readonly functionProvenance: {
|
|
188
197
|
readonly _tag: "Confect";
|
|
189
|
-
readonly error?: infer ErrorSchema_
|
|
198
|
+
readonly error?: infer ErrorSchema_;
|
|
190
199
|
};
|
|
191
200
|
}
|
|
192
|
-
? ErrorSchema_
|
|
201
|
+
? Extract<ErrorSchema_, Schema.Codec<any, any>>
|
|
193
202
|
: never;
|
|
194
203
|
|
|
195
204
|
export type Args<FunctionSpec_ extends AnyWithProps> = FunctionSpec_ extends {
|
|
@@ -282,9 +291,14 @@ export type WithoutName<
|
|
|
282
291
|
const Proto = {
|
|
283
292
|
[TypeId]: TypeId,
|
|
284
293
|
|
|
294
|
+
get middlewareSpecs() {
|
|
295
|
+
return this.middlewareAttachments.map(({ spec }) => spec);
|
|
296
|
+
},
|
|
297
|
+
|
|
285
298
|
middleware(
|
|
286
299
|
this: AnyWithProps,
|
|
287
300
|
middlewareSpec: MiddlewareSpec.AnyMiddlewareSpec,
|
|
301
|
+
...options: ReadonlyArray<unknown>
|
|
288
302
|
) {
|
|
289
303
|
if (this.functionProvenance._tag === "Convex") {
|
|
290
304
|
throw new Error(
|
|
@@ -298,8 +312,9 @@ const Proto = {
|
|
|
298
312
|
);
|
|
299
313
|
}
|
|
300
314
|
if (
|
|
301
|
-
|
|
302
|
-
|
|
315
|
+
!("options" in middlewareSpec) &&
|
|
316
|
+
this.middlewareAttachments.some(
|
|
317
|
+
({ spec }) => spec.key === middlewareSpec.key,
|
|
303
318
|
)
|
|
304
319
|
) {
|
|
305
320
|
throw new Error(
|
|
@@ -312,10 +327,13 @@ const Proto = {
|
|
|
312
327
|
functionVisibility: this.functionVisibility,
|
|
313
328
|
name: this.name,
|
|
314
329
|
functionProvenance: this.functionProvenance,
|
|
315
|
-
|
|
330
|
+
middlewareAttachments: [
|
|
331
|
+
...this.middlewareAttachments,
|
|
332
|
+
{ spec: middlewareSpec, options: options[0] },
|
|
333
|
+
],
|
|
316
334
|
});
|
|
317
335
|
},
|
|
318
|
-
}
|
|
336
|
+
} satisfies ThisType<AnyWithProps>;
|
|
319
337
|
|
|
320
338
|
interface Options {
|
|
321
339
|
readonly name: string;
|
|
@@ -373,7 +391,7 @@ const make = <
|
|
|
373
391
|
args === undefined
|
|
374
392
|
? FunctionProvenance.Confect(() => ({}), returns, error)
|
|
375
393
|
: FunctionProvenance.Confect(args, returns, error),
|
|
376
|
-
|
|
394
|
+
middlewareAttachments: [],
|
|
377
395
|
});
|
|
378
396
|
}
|
|
379
397
|
|
|
@@ -381,10 +399,10 @@ const make = <
|
|
|
381
399
|
};
|
|
382
400
|
|
|
383
401
|
/**
|
|
384
|
-
* `Schema.Struct` fields that must not declare `paginationOpts`. Used to
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
402
|
+
* `Schema.Struct` fields that must not declare `paginationOpts`. Used to reject
|
|
403
|
+
* a user args schema that redeclares the field the paginated constructors add
|
|
404
|
+
* themselves; the check reports at the `args` thunk with an unsatisfiable type
|
|
405
|
+
* when violated.
|
|
388
406
|
*/
|
|
389
407
|
type ForbidPaginationOpts<
|
|
390
408
|
UserArgsFields extends FunctionProvenance.ArgsFields,
|
|
@@ -448,7 +466,7 @@ const makePaginated = <
|
|
|
448
466
|
args === undefined
|
|
449
467
|
? FunctionProvenance.ConfectPaginated(() => ({}), item, error)
|
|
450
468
|
: FunctionProvenance.ConfectPaginated(args, item, error),
|
|
451
|
-
|
|
469
|
+
middlewareAttachments: [],
|
|
452
470
|
});
|
|
453
471
|
}
|
|
454
472
|
|
|
@@ -562,7 +580,7 @@ const makeConvex =
|
|
|
562
580
|
ExtractArgs<F>,
|
|
563
581
|
ExtractReturns<F>
|
|
564
582
|
>(),
|
|
565
|
-
|
|
583
|
+
middlewareAttachments: [],
|
|
566
584
|
}) as any;
|
|
567
585
|
};
|
|
568
586
|
|
package/src/GroupPath.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type * as GroupSpec from "./GroupSpec";
|
|
|
4
4
|
* Recursively generates paths for a group and its nested groups.
|
|
5
5
|
*
|
|
6
6
|
* - For a group with no subgroups, returns just the group name.
|
|
7
|
-
* - For a group with subgroups, returns the group name plus all possible paths
|
|
7
|
+
* - For a group with subgroups, returns the group name plus all possible paths
|
|
8
|
+
* through its direct subgroups.
|
|
8
9
|
*/
|
|
9
10
|
export type All<
|
|
10
11
|
Group extends GroupSpec.AnyWithProps,
|
|
@@ -26,13 +27,11 @@ type AllHelper<
|
|
|
26
27
|
: never;
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
|
-
* Recursively extracts the group at the given dot-separated path.
|
|
30
|
-
*
|
|
30
|
+
* Recursively extracts the group at the given dot-separated path. Path must
|
|
31
|
+
* match the format defined in `Path` above, e.g. "group" or "group.subgroup".
|
|
31
32
|
*
|
|
32
33
|
* @example
|
|
33
|
-
*
|
|
34
|
-
* type G = WithPath<RootGroup, "group.subgroup">;
|
|
35
|
-
* ```
|
|
34
|
+
* type G = WithPath<RootGroup, "group.subgroup">;
|
|
36
35
|
*/
|
|
37
36
|
export type GroupAt<
|
|
38
37
|
Group,
|
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)
|
|
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_,
|
|
@@ -123,7 +129,10 @@ export type WithName<
|
|
|
123
129
|
Name_ extends Name<Group>,
|
|
124
130
|
> = Extract<Group, { readonly name: Name_ }>;
|
|
125
131
|
|
|
126
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Assigns a segment name to a leaf group created with {@link make} for typing
|
|
134
|
+
* and refs.
|
|
135
|
+
*/
|
|
127
136
|
export type NamedAt<Group extends Any, Name_ extends string> = Omit<
|
|
128
137
|
Group,
|
|
129
138
|
"name"
|
|
@@ -154,22 +163,27 @@ export type AddGroups<
|
|
|
154
163
|
const Proto = {
|
|
155
164
|
[TypeId]: TypeId,
|
|
156
165
|
|
|
166
|
+
get middlewareSpecs() {
|
|
167
|
+
return this.middlewareAttachments.map(({ spec }) => spec);
|
|
168
|
+
},
|
|
169
|
+
|
|
157
170
|
addFunction<Function extends FunctionSpec.AnyWithProps>(
|
|
158
171
|
this: Any,
|
|
159
172
|
function_: Function,
|
|
160
173
|
) {
|
|
161
174
|
const this_ = this as AnyWithProps;
|
|
162
175
|
|
|
163
|
-
const overlapping = function_.
|
|
164
|
-
(functionMiddlewareSpec) =>
|
|
165
|
-
|
|
166
|
-
|
|
176
|
+
const overlapping = function_.middlewareAttachments.find(
|
|
177
|
+
({ spec: functionMiddlewareSpec }) =>
|
|
178
|
+
!("options" in functionMiddlewareSpec) &&
|
|
179
|
+
this_.middlewareAttachments.some(
|
|
180
|
+
({ spec: groupMiddlewareSpec }) =>
|
|
167
181
|
groupMiddlewareSpec.key === functionMiddlewareSpec.key,
|
|
168
182
|
),
|
|
169
183
|
);
|
|
170
184
|
if (overlapping !== undefined) {
|
|
171
185
|
throw new Error(
|
|
172
|
-
`Middleware "${overlapping.key}" is attached to both function "${function_.name}" and its group`,
|
|
186
|
+
`Middleware "${overlapping.spec.key}" is attached to both function "${function_.name}" and its group`,
|
|
173
187
|
);
|
|
174
188
|
}
|
|
175
189
|
|
|
@@ -178,7 +192,7 @@ const Proto = {
|
|
|
178
192
|
name: this_.name,
|
|
179
193
|
functions: Record.set(this_.functions, function_.name, function_),
|
|
180
194
|
groups: this_.groups,
|
|
181
|
-
|
|
195
|
+
middlewareAttachments: this_.middlewareAttachments,
|
|
182
196
|
});
|
|
183
197
|
},
|
|
184
198
|
|
|
@@ -191,7 +205,7 @@ const Proto = {
|
|
|
191
205
|
name: this_.name,
|
|
192
206
|
functions: this_.functions,
|
|
193
207
|
groups: Record.set(this_.groups, group_.name, group_),
|
|
194
|
-
|
|
208
|
+
middlewareAttachments: this_.middlewareAttachments,
|
|
195
209
|
});
|
|
196
210
|
},
|
|
197
211
|
|
|
@@ -204,19 +218,21 @@ const Proto = {
|
|
|
204
218
|
name: this_.name,
|
|
205
219
|
functions: this_.functions,
|
|
206
220
|
groups: Record.set(this_.groups, name, withName(name, group_)),
|
|
207
|
-
|
|
221
|
+
middlewareAttachments: this_.middlewareAttachments,
|
|
208
222
|
});
|
|
209
223
|
},
|
|
210
224
|
|
|
211
225
|
middleware<MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec>(
|
|
212
226
|
this: Any,
|
|
213
227
|
middlewareSpec: MiddlewareSpec_,
|
|
228
|
+
...options: ReadonlyArray<unknown>
|
|
214
229
|
) {
|
|
215
230
|
const this_ = this as AnyWithProps;
|
|
216
231
|
|
|
217
232
|
if (
|
|
218
|
-
|
|
219
|
-
|
|
233
|
+
!("options" in middlewareSpec) &&
|
|
234
|
+
this_.middlewareAttachments.some(
|
|
235
|
+
({ spec }) => spec.key === middlewareSpec.key,
|
|
220
236
|
)
|
|
221
237
|
) {
|
|
222
238
|
throw new Error(
|
|
@@ -226,8 +242,9 @@ const Proto = {
|
|
|
226
242
|
|
|
227
243
|
for (const function_ of Object.values(this_.functions)) {
|
|
228
244
|
if (
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
!("options" in middlewareSpec) &&
|
|
246
|
+
function_.middlewareAttachments.some(
|
|
247
|
+
({ spec }) => spec.key === middlewareSpec.key,
|
|
231
248
|
)
|
|
232
249
|
) {
|
|
233
250
|
throw new Error(
|
|
@@ -241,37 +258,53 @@ const Proto = {
|
|
|
241
258
|
name: this_.name,
|
|
242
259
|
functions: this_.functions,
|
|
243
260
|
groups: this_.groups,
|
|
244
|
-
|
|
261
|
+
middlewareAttachments: [
|
|
262
|
+
...this_.middlewareAttachments,
|
|
263
|
+
{ spec: middlewareSpec, options: options[0] },
|
|
264
|
+
],
|
|
245
265
|
});
|
|
246
266
|
},
|
|
247
|
-
}
|
|
267
|
+
} satisfies ThisType<AnyWithProps>;
|
|
248
268
|
|
|
249
269
|
const makeProto = <
|
|
250
270
|
Runtime extends RuntimeAndFunctionType.Runtime,
|
|
251
271
|
Name_ extends string,
|
|
252
272
|
Functions_ extends FunctionSpec.AnyWithPropsWithRuntime<Runtime>,
|
|
253
273
|
Groups_ extends AnyWithPropsWithRuntime<Runtime>,
|
|
254
|
-
|
|
274
|
+
MiddlewareAttachments_ extends
|
|
275
|
+
ReadonlyArray<MiddlewareAttachment.MiddlewareAttachment>,
|
|
255
276
|
>({
|
|
256
277
|
runtime,
|
|
257
278
|
name,
|
|
258
279
|
functions,
|
|
259
280
|
groups,
|
|
260
|
-
|
|
281
|
+
middlewareAttachments,
|
|
261
282
|
}: {
|
|
262
283
|
runtime: Runtime;
|
|
263
284
|
name: Name_;
|
|
264
285
|
functions: Record.ReadonlyRecord<string, Functions_>;
|
|
265
286
|
groups: Record.ReadonlyRecord<string, Groups_>;
|
|
266
|
-
|
|
267
|
-
}): GroupSpec<
|
|
287
|
+
middlewareAttachments: MiddlewareAttachments_;
|
|
288
|
+
}): GroupSpec<
|
|
289
|
+
Runtime,
|
|
290
|
+
Name_,
|
|
291
|
+
Functions_,
|
|
292
|
+
Groups_,
|
|
293
|
+
MiddlewareAttachments_[number]["spec"]
|
|
294
|
+
> =>
|
|
268
295
|
Object.assign(Object.create(Proto), {
|
|
269
296
|
runtime,
|
|
270
297
|
name,
|
|
271
298
|
functions,
|
|
272
299
|
groups,
|
|
273
|
-
|
|
274
|
-
}) as GroupSpec<
|
|
300
|
+
middlewareAttachments,
|
|
301
|
+
}) as GroupSpec<
|
|
302
|
+
Runtime,
|
|
303
|
+
Name_,
|
|
304
|
+
Functions_,
|
|
305
|
+
Groups_,
|
|
306
|
+
MiddlewareAttachments_[number]["spec"]
|
|
307
|
+
>;
|
|
275
308
|
|
|
276
309
|
export const make = (): GroupSpec<"Convex", ""> =>
|
|
277
310
|
makeProto({
|
|
@@ -279,7 +312,7 @@ export const make = (): GroupSpec<"Convex", ""> =>
|
|
|
279
312
|
name: "",
|
|
280
313
|
functions: Record.empty(),
|
|
281
314
|
groups: Record.empty(),
|
|
282
|
-
|
|
315
|
+
middlewareAttachments: [],
|
|
283
316
|
});
|
|
284
317
|
|
|
285
318
|
export const makeAt = <const Name_ extends string>(
|
|
@@ -292,7 +325,7 @@ export const makeAt = <const Name_ extends string>(
|
|
|
292
325
|
name,
|
|
293
326
|
functions: Record.empty(),
|
|
294
327
|
groups: Record.empty(),
|
|
295
|
-
|
|
328
|
+
middlewareAttachments: [],
|
|
296
329
|
});
|
|
297
330
|
};
|
|
298
331
|
|
|
@@ -302,7 +335,7 @@ export const makeNode = (): GroupSpec<"Node", ""> =>
|
|
|
302
335
|
name: "",
|
|
303
336
|
functions: Record.empty(),
|
|
304
337
|
groups: Record.empty(),
|
|
305
|
-
|
|
338
|
+
middlewareAttachments: [],
|
|
306
339
|
});
|
|
307
340
|
|
|
308
341
|
export const makeNodeAt = <const Name_ extends string>(
|
|
@@ -315,14 +348,15 @@ export const makeNodeAt = <const Name_ extends string>(
|
|
|
315
348
|
name,
|
|
316
349
|
functions: Record.empty(),
|
|
317
350
|
groups: Record.empty(),
|
|
318
|
-
|
|
351
|
+
middlewareAttachments: [],
|
|
319
352
|
});
|
|
320
353
|
};
|
|
321
354
|
|
|
322
|
-
export
|
|
355
|
+
export function withName<const Name_ extends string, Group extends Any>(
|
|
323
356
|
name: Name_,
|
|
324
|
-
group:
|
|
325
|
-
):
|
|
357
|
+
group: Group,
|
|
358
|
+
): NamedAt<Group, Name_>;
|
|
359
|
+
export function withName(name: string, group: Any): AnyWithProps {
|
|
326
360
|
validateConfectFunctionIdentifier(name);
|
|
327
361
|
const group_ = group as AnyWithProps;
|
|
328
362
|
|
|
@@ -335,6 +369,24 @@ export const withName = <const Name_ extends string>(
|
|
|
335
369
|
name,
|
|
336
370
|
functions: group_.functions,
|
|
337
371
|
groups: group_.groups,
|
|
338
|
-
|
|
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);
|
|
339
392
|
});
|
|
340
|
-
};
|
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,
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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,21 +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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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 lazy `Table`
|
|
18
|
-
* `Doc`), so there is no chance of the 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.
|
|
19
17
|
*/
|
|
20
18
|
export const defineProperty = <T extends object, K extends PropertyKey>(
|
|
21
19
|
target: T,
|