@gaganref/confect-core 10.3.1-fork.72 → 10.3.1-fork.73
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/dist/App.d.ts +27 -88
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +23 -36
- package/dist/App.js.map +1 -1
- package/dist/Auth.d.ts +2 -3
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +2 -1
- package/dist/Auth.js.map +1 -1
- package/dist/Component.d.ts +39 -0
- package/dist/Component.d.ts.map +1 -1
- package/dist/Component.js +22 -4
- package/dist/Component.js.map +1 -1
- package/dist/GenericId.js +3 -3
- package/dist/Http.js +1 -1
- package/dist/Mount.d.ts +182 -0
- package/dist/Mount.d.ts.map +1 -0
- package/dist/Mount.js +57 -0
- package/dist/Mount.js.map +1 -0
- package/dist/PaginationError.js +1 -1
- package/dist/Ref.js +2 -2
- package/dist/Refs.js +1 -1
- package/dist/SchemaToValidator.js +6 -6
- package/dist/SystemFields.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/package.json +1 -1
- package/src/App.ts +32 -130
- package/src/Auth.ts +6 -5
- package/src/Component.ts +50 -0
- package/src/Mount.ts +284 -0
- package/src/index.ts +1 -0
package/src/App.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type { ComponentDefinition
|
|
2
|
-
import type { Infer, Validator } from "convex/values";
|
|
1
|
+
import type { ComponentDefinition } from "convex/server";
|
|
3
2
|
import type * as HttpApi from "effect/http-api/HttpApi";
|
|
4
3
|
import * as Predicate from "effect/Predicate";
|
|
5
4
|
import type * as Env from "./Env.js";
|
|
6
5
|
import type * as MiddlewareSpec from "./MiddlewareSpec.js";
|
|
6
|
+
import * as Mount from "./Mount.js";
|
|
7
7
|
|
|
8
8
|
const TypeId = "~@confect/core/App";
|
|
9
|
-
const MountTypeId = "~@confect/core/App/Mount";
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* The default export of a Convex component's `convex.config` module.
|
|
@@ -20,107 +19,11 @@ export type HttpPrefix = `/${string}`;
|
|
|
20
19
|
*/
|
|
21
20
|
export type Api = HttpApi.HttpApi<string, any>;
|
|
22
21
|
|
|
23
|
-
type EnvOf<D extends Definition> = D["__env"];
|
|
24
|
-
|
|
25
|
-
type IsOptional<V> = V extends Validator<any, "optional", any> ? true : false;
|
|
26
|
-
|
|
27
|
-
type RequiredKeys<E extends EnvDefinition> = {
|
|
28
|
-
[K in keyof E]: IsOptional<E[K]> extends true ? never : K;
|
|
29
|
-
}[keyof E];
|
|
30
|
-
|
|
31
|
-
type SlotValue<V extends Validator<any, any, any>> = Extract<Infer<V>, string>;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* What one component env slot accepts: a constant, or an entry whose Convex
|
|
35
|
-
* value fits. A required slot takes only a required entry.
|
|
36
|
-
*/
|
|
37
|
-
type Slot<V extends Validator<any, any, any>> =
|
|
38
|
-
IsOptional<V> extends true
|
|
39
|
-
? SlotValue<V> | undefined | Env.Entry<string, any, SlotValue<V>, boolean>
|
|
40
|
-
: SlotValue<V> | Env.Entry<string, any, SlotValue<V>, false>;
|
|
41
|
-
|
|
42
|
-
export type Bindings<E extends EnvDefinition> = {
|
|
43
|
-
readonly [K in RequiredKeys<E>]: Slot<E[K]>;
|
|
44
|
-
} & {
|
|
45
|
-
readonly [K in Exclude<keyof E, RequiredKeys<E>>]?: Slot<E[K]>;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
type HttpOptions = { readonly httpPrefix?: HttpPrefix };
|
|
49
|
-
|
|
50
|
-
export type Options<D extends Definition> = HttpOptions &
|
|
51
|
-
([RequiredKeys<EnvOf<D>>] extends [never]
|
|
52
|
-
? { readonly env?: Bindings<EnvOf<D>> }
|
|
53
|
-
: { readonly env: Bindings<EnvOf<D>> });
|
|
54
|
-
|
|
55
|
-
type Exact<D extends Definition, O> = {
|
|
56
|
-
readonly [K in Exclude<keyof O, "env" | "httpPrefix">]: never;
|
|
57
|
-
} & (O extends { readonly env: infer B }
|
|
58
|
-
? {
|
|
59
|
-
readonly env: { readonly [K in Exclude<keyof B, keyof EnvOf<D>>]: never };
|
|
60
|
-
}
|
|
61
|
-
: unknown);
|
|
62
|
-
|
|
63
|
-
export interface MountData {
|
|
64
|
-
readonly definition: Definition;
|
|
65
|
-
readonly httpPrefix: HttpPrefix | undefined;
|
|
66
|
-
readonly env: Readonly<Record<string, string | Env.AnyEntry | undefined>>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* A mounted Convex component. `O` keeps the options as written, so {@link make}
|
|
71
|
-
* can see which backends the bound entries come from.
|
|
72
|
-
*/
|
|
73
|
-
export interface Mount<out D extends Definition, out O> extends MountData {
|
|
74
|
-
readonly [MountTypeId]: { readonly definition: D; readonly options: O };
|
|
75
|
-
readonly httpPrefix: O extends {
|
|
76
|
-
readonly httpPrefix: infer P extends HttpPrefix;
|
|
77
|
-
}
|
|
78
|
-
? P
|
|
79
|
-
: undefined;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export type AnyMount = Mount<Definition, unknown>;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Mount a Convex component: the default export of its `convex.config` module,
|
|
86
|
-
* official or Confect-authored. `env` is typed against the component's own
|
|
87
|
-
* `defineComponent({ env })` declaration, so a missing required binding, an
|
|
88
|
-
* optional entry in a required slot, a wrong literal and an unknown slot fail
|
|
89
|
-
* to typecheck.
|
|
90
|
-
*/
|
|
91
|
-
export function mount<const D extends Definition>(
|
|
92
|
-
definition: D &
|
|
93
|
-
([RequiredKeys<EnvOf<D>>] extends [never]
|
|
94
|
-
? unknown
|
|
95
|
-
: "App.mount: this component declares required env; pass { env }"),
|
|
96
|
-
): Mount<D, {}>;
|
|
97
|
-
export function mount<const D extends Definition, const O extends Options<D>>(
|
|
98
|
-
definition: D,
|
|
99
|
-
options: O & Exact<NoInfer<D>, NoInfer<O>>,
|
|
100
|
-
): Mount<D, O>;
|
|
101
|
-
export function mount(
|
|
102
|
-
definition: Definition,
|
|
103
|
-
options?: {
|
|
104
|
-
readonly httpPrefix?: HttpPrefix;
|
|
105
|
-
readonly env?: MountData["env"];
|
|
106
|
-
},
|
|
107
|
-
): AnyMount {
|
|
108
|
-
return {
|
|
109
|
-
[MountTypeId]: { definition, options },
|
|
110
|
-
definition,
|
|
111
|
-
httpPrefix: options?.httpPrefix,
|
|
112
|
-
env: options?.env ?? {},
|
|
113
|
-
// The overloads above carry the precise types; this body serves both.
|
|
114
|
-
} as AnyMount;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
22
|
type OwnerOf<V> = V extends Env.Entry<infer N, any, any, any> ? N : never;
|
|
118
23
|
|
|
119
24
|
type Owners<M> =
|
|
120
|
-
M extends Mount<
|
|
121
|
-
?
|
|
122
|
-
? { [K in keyof B]: OwnerOf<B[K]> }[keyof B]
|
|
123
|
-
: never
|
|
25
|
+
M extends Mount.Mount<any, any, Mount.Declared<any, infer B>>
|
|
26
|
+
? { [K in keyof B]: OwnerOf<B[K]> }[keyof B]
|
|
124
27
|
: never;
|
|
125
28
|
|
|
126
29
|
type OwnedBy<N extends string, M> = {
|
|
@@ -133,53 +36,56 @@ export type Kind = "app" | "component";
|
|
|
133
36
|
|
|
134
37
|
export interface App<
|
|
135
38
|
E extends Env.Any,
|
|
136
|
-
M extends
|
|
39
|
+
M extends ReadonlyArray<Mount.Any>,
|
|
137
40
|
K extends Kind = Kind,
|
|
138
41
|
> {
|
|
139
42
|
readonly [TypeId]: typeof TypeId;
|
|
140
43
|
readonly kind: K;
|
|
141
44
|
readonly env: E;
|
|
45
|
+
readonly components: ReadonlyArray<Definition>;
|
|
142
46
|
readonly mounts: M;
|
|
143
47
|
readonly requiresMiddleware: ReadonlyArray<MiddlewareSpec.AnyMiddlewareSpec>;
|
|
144
48
|
}
|
|
145
49
|
|
|
146
|
-
export type Any = App<Env.Any,
|
|
50
|
+
export type Any = App<Env.Any, ReadonlyArray<Mount.Any>>;
|
|
147
51
|
|
|
148
52
|
/**
|
|
149
|
-
* Declare a backend, in `confect/app.ts`: its env
|
|
150
|
-
* components
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
53
|
+
* Declare a backend, in `confect/app.ts`: its env, the `convex.config`
|
|
54
|
+
* definitions of the Convex components it mounts, and its mounts. Set `kind:
|
|
55
|
+
* "component"` when declaring a component; the default is `"app"`. Codegen
|
|
56
|
+
* rejects a kind that differs from its target, and writes
|
|
57
|
+
* `convex/convex.config.ts` with one `app.use` per mount, in list order, using
|
|
58
|
+
* the definition whose `defineComponent` name is the mount's component name. A
|
|
59
|
+
* mount that binds another backend's env entry fails to typecheck.
|
|
154
60
|
*
|
|
155
61
|
* ```ts
|
|
62
|
+
* import email from "@acme/email/convex.config";
|
|
63
|
+
* import workpool from "@convex-dev/workpool/convex.config";
|
|
64
|
+
*
|
|
156
65
|
* export default App.make({
|
|
157
66
|
* env,
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* httpPrefix: "/email",
|
|
161
|
-
* env: { FROM: env.EMAIL_FROM, KEY: env.RESEND_API_KEY },
|
|
162
|
-
* }),
|
|
163
|
-
* importsPool: App.mount(workpool),
|
|
164
|
-
* },
|
|
67
|
+
* components: [email, workpool],
|
|
68
|
+
* mounts: [BulkEmail, TransactionalEmail, ImportsPool],
|
|
165
69
|
* });
|
|
166
70
|
* ```
|
|
167
71
|
*/
|
|
168
72
|
export const make = <
|
|
169
73
|
const E extends Env.Any,
|
|
170
|
-
const M extends
|
|
74
|
+
const M extends ReadonlyArray<Mount.Any> = [],
|
|
171
75
|
const K extends Kind = "app",
|
|
172
76
|
>(
|
|
173
77
|
options: {
|
|
174
78
|
readonly env: E;
|
|
175
79
|
readonly requiresMiddleware?: ReadonlyArray<MiddlewareSpec.AnyMiddlewareSpec>;
|
|
80
|
+
readonly components?: ReadonlyArray<Definition>;
|
|
176
81
|
readonly mounts?: M & OwnedBy<NoInfer<Env.Name<E>>, NoInfer<M>>;
|
|
177
82
|
} & ([K] extends ["app"] ? { readonly kind?: K } : { readonly kind: K }),
|
|
178
83
|
): App<E, M, K> => ({
|
|
179
84
|
[TypeId]: TypeId,
|
|
180
85
|
kind: options.kind ?? ("app" as K),
|
|
181
86
|
env: options.env,
|
|
182
|
-
|
|
87
|
+
components: options.components ?? [],
|
|
88
|
+
mounts: options.mounts ?? ([] as unknown as M),
|
|
183
89
|
requiresMiddleware: options.requiresMiddleware ?? [],
|
|
184
90
|
});
|
|
185
91
|
|
|
@@ -189,20 +95,16 @@ export const isApp = (u: unknown): u is Any =>
|
|
|
189
95
|
Predicate.hasProperty(u, "kind") &&
|
|
190
96
|
(u.kind === "app" || u.kind === "component");
|
|
191
97
|
|
|
192
|
-
export const isMount = (u: unknown): u is AnyMount =>
|
|
193
|
-
Predicate.hasProperty(u, MountTypeId);
|
|
194
|
-
|
|
195
98
|
export const env = <A extends Any>(app: A): A["env"] => app.env;
|
|
196
99
|
|
|
197
100
|
/**
|
|
198
|
-
*
|
|
101
|
+
* The `convex.config` definitions the backend lists, in list order.
|
|
102
|
+
*/
|
|
103
|
+
export const components = (app: Any): ReadonlyArray<Definition> =>
|
|
104
|
+
app.components;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* What each mount was declared with, in list order.
|
|
199
108
|
*/
|
|
200
|
-
export const mounts = (
|
|
201
|
-
app
|
|
202
|
-
): ReadonlyArray<MountData & { readonly name: string }> =>
|
|
203
|
-
Object.entries(app.mounts).map(([name, installed]) => ({
|
|
204
|
-
name,
|
|
205
|
-
definition: installed.definition,
|
|
206
|
-
httpPrefix: installed.httpPrefix,
|
|
207
|
-
env: installed.env,
|
|
208
|
-
}));
|
|
109
|
+
export const mounts = (app: Any): ReadonlyArray<Mount.Data> =>
|
|
110
|
+
app.mounts.map(Mount.data);
|
package/src/Auth.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { AuthProvider } from "convex/server";
|
|
|
2
2
|
import * as Predicate from "effect/Predicate";
|
|
3
3
|
import type * as App from "./App.js";
|
|
4
4
|
import type * as Env from "./Env.js";
|
|
5
|
+
import * as Mount from "./Mount.js";
|
|
5
6
|
import type { TypeError } from "./Types.js";
|
|
6
7
|
|
|
7
8
|
const TypeId = "~@confect/core/Auth";
|
|
@@ -85,19 +86,19 @@ export type Any = Auth<App.Any>;
|
|
|
85
86
|
*/
|
|
86
87
|
export function siteUrl(path?: `/${string}`): SiteUrl;
|
|
87
88
|
export function siteUrl(
|
|
88
|
-
mount:
|
|
89
|
+
mount: Mount.Mount<any, string, Mount.Declared<App.HttpPrefix>>,
|
|
89
90
|
path?: `/${string}`,
|
|
90
91
|
): SiteUrl;
|
|
91
92
|
export function siteUrl(
|
|
92
|
-
pathOrMount?: `/${string}` |
|
|
93
|
+
pathOrMount?: `/${string}` | Mount.Any,
|
|
93
94
|
path: string = "",
|
|
94
95
|
): SiteUrl {
|
|
95
96
|
return {
|
|
96
97
|
[SiteUrlTypeId]: SiteUrlTypeId,
|
|
97
98
|
path:
|
|
98
|
-
typeof pathOrMount === "
|
|
99
|
-
? pathOrMount
|
|
100
|
-
: (pathOrMount ?? ""),
|
|
99
|
+
typeof pathOrMount === "string" || pathOrMount === undefined
|
|
100
|
+
? (pathOrMount ?? "")
|
|
101
|
+
: (Mount.data(pathOrMount).httpPrefix ?? "").replace(/\/+$/, "") + path,
|
|
101
102
|
};
|
|
102
103
|
}
|
|
103
104
|
|
package/src/Component.ts
CHANGED
|
@@ -418,3 +418,53 @@ export const schema = <
|
|
|
418
418
|
component[BoundTypeId].from,
|
|
419
419
|
component[BoundTypeId].scope,
|
|
420
420
|
);
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* A component's `init`: an internal Confect mutation whose args are the install
|
|
424
|
+
* args each mount passes.
|
|
425
|
+
*/
|
|
426
|
+
export type Init = Ref.AnyConfect & {
|
|
427
|
+
readonly "~RuntimeAndFunctionType": RuntimeAndFunctionType.ConvexMutation;
|
|
428
|
+
readonly "~FunctionVisibility": "internal";
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
export interface DescriptorOptions {
|
|
432
|
+
readonly init?: Init;
|
|
433
|
+
/**
|
|
434
|
+
* The component's `_generated/component` contract. A mount of a descriptor
|
|
435
|
+
* with one rebases a callback slot's args with `callbackArgs`.
|
|
436
|
+
*/
|
|
437
|
+
readonly contract?: Component<Spec.AnyWithProps, IdScope.IdScope, string>;
|
|
438
|
+
readonly env?: Readonly<Record<string, Schema.Top>>;
|
|
439
|
+
/**
|
|
440
|
+
* Builds the component's client from its native mount, then the mount's
|
|
441
|
+
* `options` when the client takes any.
|
|
442
|
+
*/
|
|
443
|
+
readonly client: (...args: never) => unknown;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export type Descriptor<
|
|
447
|
+
Name extends string = string,
|
|
448
|
+
Options extends DescriptorOptions = DescriptorOptions,
|
|
449
|
+
> = Options & { readonly name: Name };
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Describe a Convex component for `Mount.make`, as `defineComponent` does for
|
|
453
|
+
* Convex. A component with `init` takes install `args` at each mount, and one
|
|
454
|
+
* with `env` takes `env`. Export it from the package's `./client` entry.
|
|
455
|
+
*
|
|
456
|
+
* ```ts
|
|
457
|
+
* export default Component.make("email", {
|
|
458
|
+
* init: refs.internal.install.init,
|
|
459
|
+
* env: { RESEND_API_KEY: Schema.String },
|
|
460
|
+
* client: Client.make,
|
|
461
|
+
* });
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
export const make = <
|
|
465
|
+
const Name extends string,
|
|
466
|
+
const Options extends DescriptorOptions,
|
|
467
|
+
>(
|
|
468
|
+
name: Name,
|
|
469
|
+
options: Options,
|
|
470
|
+
): Descriptor<Name, Options> => ({ ...options, name });
|
package/src/Mount.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { componentsGeneric } from "convex/server";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
import * as SchemaAST from "effect/SchemaAST";
|
|
5
|
+
import type * as App from "./App.js";
|
|
6
|
+
import type * as Callback from "./Callback.js";
|
|
7
|
+
import * as Component from "./Component.js";
|
|
8
|
+
import type * as Env from "./Env.js";
|
|
9
|
+
import type * as GenericId from "./GenericId.js";
|
|
10
|
+
import type * as IdScope from "./IdScope.js";
|
|
11
|
+
import { CallbackContract } from "./internal/annotations.js";
|
|
12
|
+
import type * as Ref from "./Ref.js";
|
|
13
|
+
|
|
14
|
+
const TypeId = "~@confect/core/Mount";
|
|
15
|
+
|
|
16
|
+
type Handle = Ref.AnyConfect & {
|
|
17
|
+
readonly "~@confect/core/Callback/Handle": Callback.Id;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ContractOf<H extends Handle> = Callback.Contract<
|
|
21
|
+
H["~@confect/core/Callback/Handle"],
|
|
22
|
+
Ref.GetFunctionType<H>,
|
|
23
|
+
H["args"]["fields"],
|
|
24
|
+
H["returns"],
|
|
25
|
+
Extract<NonNullable<H["error"]>, Schema.Top>
|
|
26
|
+
>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* What a mount passes for `init`'s args: the same shape, with a host ref in
|
|
30
|
+
* each callback slot.
|
|
31
|
+
*/
|
|
32
|
+
type Input<T> = T extends Handle
|
|
33
|
+
? Ref.AnyConfect
|
|
34
|
+
: T extends object
|
|
35
|
+
? { readonly [K in keyof T]: Input<T[K]> }
|
|
36
|
+
: T;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `A` when every alternative of `A` fits `T`; otherwise a type that refuses
|
|
40
|
+
* each alternative that does not. A callback slot takes an internal host ref
|
|
41
|
+
* that satisfies the slot's contract. Whether a slot may be left `undefined` is
|
|
42
|
+
* `Input`'s to say.
|
|
43
|
+
*/
|
|
44
|
+
type Check<T, A> = A extends unknown ? Fit<T, A> : never;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Distributes over the members of `T`, so a value is checked against the
|
|
48
|
+
* members whose shape it has and fits when it fits one of them.
|
|
49
|
+
*/
|
|
50
|
+
type Fit<T, A> = T extends Handle
|
|
51
|
+
? A extends Ref.AnyConfect
|
|
52
|
+
? A &
|
|
53
|
+
Callback.Check<ContractOf<T>, A> &
|
|
54
|
+
(Ref.GetFunctionVisibility<A> extends "internal"
|
|
55
|
+
? unknown
|
|
56
|
+
: { readonly callbackMustBeInternal: never })
|
|
57
|
+
: never
|
|
58
|
+
: T extends object
|
|
59
|
+
? A extends Input<T>
|
|
60
|
+
? {
|
|
61
|
+
readonly [K in keyof A]: K extends keyof T
|
|
62
|
+
? Check<T[K], A[K]>
|
|
63
|
+
: never;
|
|
64
|
+
}
|
|
65
|
+
: never
|
|
66
|
+
: A extends T
|
|
67
|
+
? A
|
|
68
|
+
: never;
|
|
69
|
+
|
|
70
|
+
type ArgsOf<C> = C extends { readonly init: infer I extends Component.Init }
|
|
71
|
+
? Ref.Args<I>
|
|
72
|
+
: never;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* `[C]`, not a bare `C`, so the conditional is not distributive. A distributive
|
|
76
|
+
* one resolves against `Component.Descriptor` while `C` is inferred, drops `A`
|
|
77
|
+
* from the contextual type, and widens inline literals until `A` falls back to
|
|
78
|
+
* its constraint.
|
|
79
|
+
*/
|
|
80
|
+
type ArgsField<C, A> = [C] extends [
|
|
81
|
+
{ readonly init: infer I extends Component.Init },
|
|
82
|
+
]
|
|
83
|
+
? { readonly args: A & NoInfer<Check<Ref.Args<I>, A>> }
|
|
84
|
+
: { readonly args?: never };
|
|
85
|
+
|
|
86
|
+
type IsOptional<S> = S extends { readonly "~type.optionality": "optional" }
|
|
87
|
+
? true
|
|
88
|
+
: false;
|
|
89
|
+
|
|
90
|
+
type RequiredKeys<E> = {
|
|
91
|
+
[K in keyof E]: IsOptional<E[K]> extends true ? never : K;
|
|
92
|
+
}[keyof E];
|
|
93
|
+
|
|
94
|
+
type SlotValue<S> = Extract<Schema.Schema.Type<S & Schema.Top>, string>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* What one env slot accepts: a constant, or an entry of the host's env whose
|
|
98
|
+
* value fits. A required slot takes only a required entry.
|
|
99
|
+
*/
|
|
100
|
+
type Slot<S> =
|
|
101
|
+
IsOptional<S> extends true
|
|
102
|
+
? SlotValue<S> | undefined | Env.Entry<string, any, SlotValue<S>, boolean>
|
|
103
|
+
: SlotValue<S> | Env.Entry<string, any, SlotValue<S>, false>;
|
|
104
|
+
|
|
105
|
+
type Bindings<E> = { readonly [K in RequiredKeys<E>]: Slot<E[K]> } & {
|
|
106
|
+
readonly [K in Exclude<keyof E, RequiredKeys<E>>]?: Slot<E[K]>;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* `B` keeps the bindings as written, so `App.make` can see which backends their
|
|
111
|
+
* entries come from. `[C]` for the reason `ArgsField` gives: a bare `C` widens
|
|
112
|
+
* an inline constant to `string`.
|
|
113
|
+
*/
|
|
114
|
+
type EnvField<C, B> = [C] extends [{ readonly env: infer E }]
|
|
115
|
+
? [RequiredKeys<E>] extends [never]
|
|
116
|
+
? { readonly env?: B & Bindings<E> & NoExtra<B, E> }
|
|
117
|
+
: { readonly env: B & Bindings<E> & NoExtra<B, E> }
|
|
118
|
+
: { readonly env?: never };
|
|
119
|
+
|
|
120
|
+
type NoExtra<B, E> = { readonly [K in Exclude<keyof B, keyof E>]: never };
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The client constructor's parameters after the native mount decide whether
|
|
124
|
+
* `options` is absent, optional or required. A client that takes no native
|
|
125
|
+
* mount cannot be mounted. That branch is an object, not `never`, so the
|
|
126
|
+
* literal `name` and `args` beside it keep their types.
|
|
127
|
+
*/
|
|
128
|
+
type OptionsField<C extends Component.Descriptor> =
|
|
129
|
+
Parameters<C["client"]> extends [unknown, ...infer Rest]
|
|
130
|
+
? Rest extends []
|
|
131
|
+
? { readonly options?: never }
|
|
132
|
+
: Rest extends [infer O]
|
|
133
|
+
? { readonly options: O }
|
|
134
|
+
: Rest extends [options?: infer O]
|
|
135
|
+
? { readonly options?: Exclude<O, undefined> }
|
|
136
|
+
: never
|
|
137
|
+
: { readonly options: never };
|
|
138
|
+
|
|
139
|
+
export type Client<C extends Component.Descriptor> = ReturnType<C["client"]>;
|
|
140
|
+
|
|
141
|
+
export interface Data<C extends Component.Descriptor = Component.Descriptor> {
|
|
142
|
+
readonly component: C;
|
|
143
|
+
readonly name: string;
|
|
144
|
+
readonly httpPrefix?: App.HttpPrefix;
|
|
145
|
+
readonly env?: Readonly<Record<string, string | Env.AnyEntry | undefined>>;
|
|
146
|
+
readonly args?: unknown;
|
|
147
|
+
readonly options?: unknown;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* What a mount was declared with, as written: its HTTP prefix and env bindings.
|
|
152
|
+
*/
|
|
153
|
+
export interface Declared<
|
|
154
|
+
P extends App.HttpPrefix | undefined = App.HttpPrefix | undefined,
|
|
155
|
+
B = unknown,
|
|
156
|
+
> {
|
|
157
|
+
readonly httpPrefix: P;
|
|
158
|
+
readonly env: B;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* A mounted Convex component. Yield it for its client. Its default is the
|
|
163
|
+
* component's client over this mount, and `Effect.provideService` replaces it.
|
|
164
|
+
*/
|
|
165
|
+
export interface Mount<
|
|
166
|
+
C extends Component.Descriptor,
|
|
167
|
+
Name extends string,
|
|
168
|
+
D extends Declared = Declared,
|
|
169
|
+
> extends Context.Reference<Client<C>> {
|
|
170
|
+
readonly [TypeId]: Data<C> & { readonly name: Name };
|
|
171
|
+
readonly "~declared"?: D;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type Any = Mount<Component.Descriptor, string>;
|
|
175
|
+
|
|
176
|
+
export type NameOf<M> = M extends Mount<any, infer N, any> ? N : never;
|
|
177
|
+
|
|
178
|
+
type InitArgs<C> = C extends { readonly init: infer I extends Component.Init }
|
|
179
|
+
? Ref.Args<I>
|
|
180
|
+
: never;
|
|
181
|
+
|
|
182
|
+
type CallbackSlot<C> = {
|
|
183
|
+
[K in keyof InitArgs<C>]-?: NonNullable<InitArgs<C>[K]> extends Handle
|
|
184
|
+
? K
|
|
185
|
+
: never;
|
|
186
|
+
}[keyof InitArgs<C>] &
|
|
187
|
+
string;
|
|
188
|
+
|
|
189
|
+
type SlotArgs<C, K extends PropertyKey> = Extract<
|
|
190
|
+
NonNullable<InitArgs<C>[K & keyof InitArgs<C>]>,
|
|
191
|
+
Handle
|
|
192
|
+
>["args"]["fields"];
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* `callbackArgs`, on a mount whose descriptor carries its contract.
|
|
196
|
+
*/
|
|
197
|
+
type CallbackArgs<C, Name extends string> = C extends {
|
|
198
|
+
readonly contract: Component.Component<any, infer From, any>;
|
|
199
|
+
}
|
|
200
|
+
? {
|
|
201
|
+
/**
|
|
202
|
+
* A callback slot's contract args, with the component's ids typed for
|
|
203
|
+
* this mount, for the host function that answers the callback.
|
|
204
|
+
*/
|
|
205
|
+
readonly callbackArgs: <const K extends CallbackSlot<C>>(
|
|
206
|
+
slot: K,
|
|
207
|
+
) => GenericId.RebaseSchema<
|
|
208
|
+
Schema.Struct<SlotArgs<C, K>>,
|
|
209
|
+
From,
|
|
210
|
+
Component.MountScope<IdScope.App, Name>
|
|
211
|
+
>["fields"];
|
|
212
|
+
}
|
|
213
|
+
: unknown;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Mount a Convex component, as Convex's `app.use` does, in the host's
|
|
217
|
+
* `confect/<mount>.ts`. `args` is typed from the component's `init`, `env` from
|
|
218
|
+
* its env slots, and `options` from its client constructor. A handler reaches
|
|
219
|
+
* the client with `yield* BulkEmail`.
|
|
220
|
+
*
|
|
221
|
+
* ```ts
|
|
222
|
+
* export const BulkEmail = Mount.make(Email, {
|
|
223
|
+
* name: "bulkEmail",
|
|
224
|
+
* httpPrefix: "/bulk-email",
|
|
225
|
+
* env: { RESEND_API_KEY: env.BULK_RESEND_KEY },
|
|
226
|
+
* args: {
|
|
227
|
+
* fromName: "School office",
|
|
228
|
+
* onEvent: refs.internal.circulars.onEmailEvent,
|
|
229
|
+
* },
|
|
230
|
+
* });
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
export const make = <
|
|
234
|
+
C extends Component.Descriptor,
|
|
235
|
+
const Name extends string,
|
|
236
|
+
const A extends Input<ArgsOf<C>>,
|
|
237
|
+
const P extends App.HttpPrefix = never,
|
|
238
|
+
const B = {},
|
|
239
|
+
>(
|
|
240
|
+
component: C,
|
|
241
|
+
options: {
|
|
242
|
+
readonly name: Name;
|
|
243
|
+
readonly httpPrefix?: P;
|
|
244
|
+
} & EnvField<C, B> &
|
|
245
|
+
ArgsField<C, A> &
|
|
246
|
+
OptionsField<C>,
|
|
247
|
+
): Mount<C, Name, Declared<[P] extends [never] ? undefined : P, B>> &
|
|
248
|
+
CallbackArgs<C, Name> => {
|
|
249
|
+
const data = { ...(options as object), component } as Data<C> & {
|
|
250
|
+
readonly name: Name;
|
|
251
|
+
};
|
|
252
|
+
// The options were checked against the constructor's parameters above.
|
|
253
|
+
const client = component.client as unknown as (
|
|
254
|
+
native: unknown,
|
|
255
|
+
options?: unknown,
|
|
256
|
+
) => Client<C>;
|
|
257
|
+
const native = () => componentsGeneric()[data.name];
|
|
258
|
+
const mount = Context.Reference<Client<C>>(`mount/${data.name}`, {
|
|
259
|
+
defaultValue: () => client(native(), data.options),
|
|
260
|
+
});
|
|
261
|
+
const { contract, init } = component;
|
|
262
|
+
const callbackArgs = (slot: string) => {
|
|
263
|
+
const field = init!.args.fields[slot]!;
|
|
264
|
+
const callback = SchemaAST.resolveAt<Callback.Any>(CallbackContract)(
|
|
265
|
+
Schema.toEncoded(field).ast,
|
|
266
|
+
)!;
|
|
267
|
+
const bound = Component.bind(contract!, native() as never);
|
|
268
|
+
return Component.schema(bound, callback.functionProvenance.args).fields;
|
|
269
|
+
};
|
|
270
|
+
return Object.assign(
|
|
271
|
+
mount,
|
|
272
|
+
{ [TypeId]: data },
|
|
273
|
+
contract === undefined ? {} : { callbackArgs },
|
|
274
|
+
) as Mount<C, Name, Declared<[P] extends [never] ? undefined : P, B>> &
|
|
275
|
+
CallbackArgs<C, Name>;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* What a mount was declared with: its component, name, HTTP prefix, env, args
|
|
280
|
+
* and options.
|
|
281
|
+
*/
|
|
282
|
+
export const data = <C extends Component.Descriptor, Name extends string>(
|
|
283
|
+
mount: Mount<C, Name, Declared>,
|
|
284
|
+
): Data<C> & { readonly name: Name } => mount[TypeId];
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * as Identifier from "./Identifier.js";
|
|
|
15
15
|
export * as Lazy from "./Lazy.js";
|
|
16
16
|
export * as Manifest from "./Manifest.js";
|
|
17
17
|
export * as ManifestSection from "./ManifestSection.js";
|
|
18
|
+
export * as Mount from "./Mount.js";
|
|
18
19
|
export * as MiddlewareAttachment from "./MiddlewareAttachment.js";
|
|
19
20
|
export * as MiddlewareSpec from "./MiddlewareSpec.js";
|
|
20
21
|
export * as PaginationError from "./PaginationError.js";
|