@gaganref/confect-core 10.3.1-fork.71 → 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/src/App.ts CHANGED
@@ -1,12 +1,11 @@
1
- import type { ComponentDefinition, EnvDefinition } from "convex/server";
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<Definition, infer O>
121
- ? O extends { readonly env: infer B }
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 Readonly<Record<string, AnyMount>>,
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, Readonly<Record<string, AnyMount>>>;
50
+ export type Any = App<Env.Any, ReadonlyArray<Mount.Any>>;
147
51
 
148
52
  /**
149
- * Declare a backend, in `confect/app.ts`: its env and its mounted Convex
150
- * components by name. Set `kind: "component"` when declaring a component; the
151
- * default is `"app"`. Codegen rejects a kind that differs from its target.
152
- * Codegen writes `convex/convex.config.ts` from it. A mount that binds another
153
- * backend's env entry fails to typecheck.
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
- * mounts: {
159
- * email: App.mount(email, {
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 Readonly<Record<string, AnyMount>> = {},
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
- mounts: options.mounts ?? ({} as M),
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
- * Every mount with its name, in declaration order.
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: Any,
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: { readonly httpPrefix: App.HttpPrefix },
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}` | { readonly httpPrefix: App.HttpPrefix },
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 === "object"
99
- ? pathOrMount.httpPrefix.replace(/\/+$/, "") + path
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 });
@@ -39,11 +39,13 @@ export interface Generate {
39
39
 
40
40
  /**
41
41
  * What codegen hands a section's `summarize`: the `confect/` files its
42
- * `generate` matched, and each function's description by path.
42
+ * `generate` matched, each function's description by path, and the spec file
43
+ * declaring each function by path, relative to `confect/`.
43
44
  */
44
45
  export interface SummarizeInput<Value = unknown> {
45
46
  readonly modules: GenerateInput["modules"];
46
47
  readonly functions: Readonly<Record<string, Value>>;
48
+ readonly sources: Readonly<Record<string, string>>;
47
49
  }
48
50
 
49
51
  export interface ManifestSection<
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";