@gaganref/confect-core 10.3.1-fork.7 → 10.3.1-fork.70
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/ActionCall.d.ts +18 -0
- package/dist/ActionCall.d.ts.map +1 -0
- package/dist/ActionCall.js +21 -0
- package/dist/ActionCall.js.map +1 -0
- package/dist/App.d.ts +6 -8
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +2 -3
- package/dist/App.js.map +1 -1
- package/dist/Auth.d.ts +31 -6
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +15 -1
- package/dist/Auth.js.map +1 -1
- package/dist/Callback.d.ts +7 -2
- package/dist/Callback.d.ts.map +1 -1
- package/dist/Callback.js +11 -6
- package/dist/Callback.js.map +1 -1
- package/dist/Component.d.ts +1 -1
- package/dist/Component.d.ts.map +1 -1
- package/dist/Component.js +12 -12
- package/dist/Component.js.map +1 -1
- package/dist/FunctionSpec.d.ts +365 -2
- package/dist/FunctionSpec.d.ts.map +1 -1
- package/dist/FunctionSpec.js +66 -3
- package/dist/FunctionSpec.js.map +1 -1
- package/dist/GroupSpec.js +1 -1
- package/dist/Http.d.ts +4 -8
- package/dist/Http.d.ts.map +1 -1
- package/dist/Http.js +14 -21
- package/dist/Http.js.map +1 -1
- package/dist/ManifestSection.d.ts +66 -0
- package/dist/ManifestSection.d.ts.map +1 -0
- package/dist/ManifestSection.js +7 -0
- package/dist/ManifestSection.js.map +1 -0
- package/dist/Profile.d.ts +95 -0
- package/dist/Profile.d.ts.map +1 -0
- package/dist/Profile.js +15 -0
- package/dist/Profile.js.map +1 -0
- package/dist/Ref.d.ts +4 -3
- package/dist/Ref.d.ts.map +1 -1
- package/dist/Ref.js +11 -17
- package/dist/Ref.js.map +1 -1
- package/dist/RefInternal.d.ts +3 -0
- package/dist/RefInternal.d.ts.map +1 -0
- package/dist/RefInternal.js +16 -0
- package/dist/RefInternal.js.map +1 -0
- package/dist/SchemaToValidator.d.ts +11 -2
- package/dist/SchemaToValidator.d.ts.map +1 -1
- package/dist/SchemaToValidator.js +40 -11
- package/dist/SchemaToValidator.js.map +1 -1
- package/dist/SystemFields.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -6
- package/dist/internal/annotations.d.ts +2 -0
- package/dist/internal/annotations.d.ts.map +1 -0
- package/dist/internal/annotations.js +6 -0
- package/dist/internal/annotations.js.map +1 -0
- package/dist/internal/scalarDocs.d.ts +5 -0
- package/dist/internal/scalarDocs.d.ts.map +1 -0
- package/dist/internal/scalarDocs.js +43 -0
- package/dist/internal/scalarDocs.js.map +1 -0
- package/package.json +3 -3
- package/src/ActionCall.ts +25 -0
- package/src/App.ts +7 -9
- package/src/Auth.ts +48 -6
- package/src/Callback.ts +17 -4
- package/src/Component.ts +8 -6
- package/src/FunctionSpec.ts +566 -5
- package/src/Http.ts +15 -37
- package/src/ManifestSection.ts +74 -0
- package/src/Profile.ts +141 -0
- package/src/Ref.ts +19 -26
- package/src/RefInternal.ts +26 -0
- package/src/SchemaToValidator.ts +47 -5
- package/src/index.ts +3 -0
- package/src/internal/annotations.ts +1 -0
- package/src/internal/scalarDocs.ts +72 -0
package/src/Http.ts
CHANGED
|
@@ -7,7 +7,7 @@ import * as SchemaAST from "effect/SchemaAST";
|
|
|
7
7
|
import type * as HttpApi from "effect/http-api/HttpApi";
|
|
8
8
|
import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
|
|
9
9
|
import type * as HttpApiGroup from "effect/http-api/HttpApiGroup";
|
|
10
|
-
import
|
|
10
|
+
import { scalarDocs } from "./internal/scalarDocs.js";
|
|
11
11
|
import * as App from "./App.js";
|
|
12
12
|
|
|
13
13
|
const TypeId = "~@confect/core/Http";
|
|
@@ -57,10 +57,7 @@ export const make = <
|
|
|
57
57
|
const routes =
|
|
58
58
|
options.docs === undefined
|
|
59
59
|
? api
|
|
60
|
-
: Layer.merge(
|
|
61
|
-
api,
|
|
62
|
-
HttpApiScalar.layerCdn(options.api, { path: options.docs }),
|
|
63
|
-
);
|
|
60
|
+
: Layer.merge(api, scalarDocs(options.api, options.docs));
|
|
64
61
|
const http: Http<HttpApi.HttpApi<Id, Groups>, typeof routes> = {
|
|
65
62
|
[TypeId]: TypeId,
|
|
66
63
|
api: options.api,
|
|
@@ -73,10 +70,7 @@ export const make = <
|
|
|
73
70
|
export const isHttp = (u: unknown): u is Any =>
|
|
74
71
|
Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId;
|
|
75
72
|
|
|
76
|
-
export type Owner =
|
|
77
|
-
| { readonly kind: "app" }
|
|
78
|
-
| { readonly kind: "docs" }
|
|
79
|
-
| { readonly kind: "mount"; readonly mount: string };
|
|
73
|
+
export type Owner = { readonly kind: "app" } | { readonly kind: "docs" };
|
|
80
74
|
|
|
81
75
|
export interface Route {
|
|
82
76
|
readonly method: string;
|
|
@@ -104,7 +98,7 @@ export class DuplicateRoute extends Data.TaggedError("DuplicateRoute")<{
|
|
|
104
98
|
readonly second: Owner;
|
|
105
99
|
}> {
|
|
106
100
|
override get message(): string {
|
|
107
|
-
return `${this.method} ${this.path} is declared twice (${
|
|
101
|
+
return `${this.method} ${this.path} is declared twice (${this.first.kind} and ${this.second.kind}).`;
|
|
108
102
|
}
|
|
109
103
|
}
|
|
110
104
|
|
|
@@ -124,9 +118,6 @@ export type RouteError =
|
|
|
124
118
|
| DuplicateRoute
|
|
125
119
|
| OverlappingMountPrefixes;
|
|
126
120
|
|
|
127
|
-
const ownerLabel = (owner: Owner): string =>
|
|
128
|
-
owner.kind === "mount" ? `mount "${owner.mount}"` : owner.kind;
|
|
129
|
-
|
|
130
121
|
const trimSlash = (prefix: string): string =>
|
|
131
122
|
prefix.length > 1 && prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
132
123
|
|
|
@@ -160,14 +151,10 @@ interface KeyedRoute {
|
|
|
160
151
|
readonly key: string;
|
|
161
152
|
}
|
|
162
153
|
|
|
163
|
-
const apiRoutes = (
|
|
164
|
-
api: App.Api,
|
|
165
|
-
prefix: string,
|
|
166
|
-
owner: Owner,
|
|
167
|
-
): ReadonlyArray<KeyedRoute> =>
|
|
154
|
+
const apiRoutes = (api: App.Api, owner: Owner): ReadonlyArray<KeyedRoute> =>
|
|
168
155
|
Object.values<HttpApiGroup.Top>(api.groups).flatMap((group) =>
|
|
169
156
|
Object.values(group.endpoints).map((endpoint) => {
|
|
170
|
-
const path =
|
|
157
|
+
const path = endpoint.path;
|
|
171
158
|
const declared = paramNames(endpoint.params);
|
|
172
159
|
return {
|
|
173
160
|
route: { method: endpoint.method, path, owner },
|
|
@@ -177,20 +164,19 @@ const apiRoutes = (
|
|
|
177
164
|
);
|
|
178
165
|
|
|
179
166
|
/**
|
|
180
|
-
*
|
|
181
|
-
* mount's
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* when two mount prefixes overlap.
|
|
167
|
+
* The backend's own HTTP routes and docs page. Fails when a route is at or
|
|
168
|
+
* under a mount's prefix (Convex accepts that and lets the app route silently
|
|
169
|
+
* shadow the component's), when two routes share a method and path, or when two
|
|
170
|
+
* mount prefixes overlap.
|
|
185
171
|
*/
|
|
186
172
|
export const routes = (
|
|
187
173
|
app: App.Any,
|
|
188
174
|
http?: Any,
|
|
189
175
|
): Result.Result<ReadonlyArray<Route>, RouteError> => {
|
|
190
|
-
const mounts = App.mounts(app).flatMap(({ name, httpPrefix
|
|
176
|
+
const mounts = App.mounts(app).flatMap(({ name, httpPrefix }) =>
|
|
191
177
|
httpPrefix === undefined
|
|
192
178
|
? []
|
|
193
|
-
: [{ name, httpPrefix: trimSlash(httpPrefix)
|
|
179
|
+
: [{ name, httpPrefix: trimSlash(httpPrefix) }],
|
|
194
180
|
);
|
|
195
181
|
|
|
196
182
|
for (const [index, first] of mounts.entries()) {
|
|
@@ -213,7 +199,7 @@ export const routes = (
|
|
|
213
199
|
http === undefined
|
|
214
200
|
? []
|
|
215
201
|
: [
|
|
216
|
-
...apiRoutes(http.api,
|
|
202
|
+
...apiRoutes(http.api, { kind: "app" }),
|
|
217
203
|
...(http.docs === undefined
|
|
218
204
|
? []
|
|
219
205
|
: [
|
|
@@ -244,16 +230,8 @@ export const routes = (
|
|
|
244
230
|
}
|
|
245
231
|
}
|
|
246
232
|
|
|
247
|
-
const all = [
|
|
248
|
-
...own,
|
|
249
|
-
...mounts.flatMap(({ name, httpPrefix, api }) =>
|
|
250
|
-
api === undefined
|
|
251
|
-
? []
|
|
252
|
-
: apiRoutes(api, httpPrefix, { kind: "mount", mount: name }),
|
|
253
|
-
),
|
|
254
|
-
];
|
|
255
233
|
const seen = new Map<string, Route>();
|
|
256
|
-
for (const { route, key } of
|
|
234
|
+
for (const { route, key } of own) {
|
|
257
235
|
const previous = seen.get(key);
|
|
258
236
|
if (previous !== undefined) {
|
|
259
237
|
return Result.fail(
|
|
@@ -267,5 +245,5 @@ export const routes = (
|
|
|
267
245
|
}
|
|
268
246
|
seen.set(key, route);
|
|
269
247
|
}
|
|
270
|
-
return Result.succeed(
|
|
248
|
+
return Result.succeed(own.map(({ route }) => route));
|
|
271
249
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type * as FunctionSpec from "./FunctionSpec.js";
|
|
2
|
+
import type * as MiddlewareSpec from "./MiddlewareSpec.js";
|
|
3
|
+
|
|
4
|
+
export interface Data<Value = unknown> {
|
|
5
|
+
readonly format: number;
|
|
6
|
+
readonly functions: Readonly<Record<string, Value>>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What codegen hands a section's `render`: the exports of each `confect/` file
|
|
11
|
+
* it matched, every registered function with the spec file declaring it, and
|
|
12
|
+
* each mounted component with the specifier of its definition.
|
|
13
|
+
*/
|
|
14
|
+
export interface GenerateInput {
|
|
15
|
+
readonly modules: ReadonlyArray<{
|
|
16
|
+
readonly relativePath: string;
|
|
17
|
+
readonly exports: Readonly<Record<string, unknown>>;
|
|
18
|
+
}>;
|
|
19
|
+
readonly functions: ReadonlyArray<{
|
|
20
|
+
readonly path: string;
|
|
21
|
+
readonly source: string;
|
|
22
|
+
}>;
|
|
23
|
+
readonly mounts: ReadonlyArray<{
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly definition: string;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Generate {
|
|
30
|
+
readonly file: string;
|
|
31
|
+
match(relativePath: string): boolean;
|
|
32
|
+
render(input: GenerateInput): {
|
|
33
|
+
readonly contents: string;
|
|
34
|
+
readonly errors: ReadonlyArray<string>;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* What codegen hands a section's `summarize`: the `confect/` files its
|
|
40
|
+
* `generate` matched, and each function's description by path.
|
|
41
|
+
*/
|
|
42
|
+
export interface SummarizeInput<Value = unknown> {
|
|
43
|
+
readonly modules: GenerateInput["modules"];
|
|
44
|
+
readonly functions: Readonly<Record<string, Value>>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ManifestSection<
|
|
48
|
+
Value = unknown,
|
|
49
|
+
Data_ extends { readonly format: number } = Data<Value>,
|
|
50
|
+
> {
|
|
51
|
+
readonly owner: string;
|
|
52
|
+
readonly format: number;
|
|
53
|
+
describe(input: {
|
|
54
|
+
readonly path: string;
|
|
55
|
+
readonly spec: FunctionSpec.AnyWithProps;
|
|
56
|
+
readonly middleware: ReadonlyArray<MiddlewareSpec.AnyMiddlewareSpec>;
|
|
57
|
+
}): Value;
|
|
58
|
+
summarize?(input: SummarizeInput<Value>): Data_;
|
|
59
|
+
check?(
|
|
60
|
+
next: Data_,
|
|
61
|
+
previous: unknown,
|
|
62
|
+
): {
|
|
63
|
+
readonly errors: ReadonlyArray<string>;
|
|
64
|
+
readonly notes: ReadonlyArray<string>;
|
|
65
|
+
};
|
|
66
|
+
readonly generate?: Generate;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type Any = ManifestSection<unknown, { readonly format: number }>;
|
|
70
|
+
|
|
71
|
+
export interface Entry {
|
|
72
|
+
readonly key: string;
|
|
73
|
+
readonly section: Any;
|
|
74
|
+
}
|
package/src/Profile.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type * as Schema from "effect/Schema";
|
|
2
|
+
import type * as FunctionProvenance from "./FunctionProvenance.js";
|
|
3
|
+
import type * as MiddlewareSpec from "./MiddlewareSpec.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A type-level function from a function's name and args fields to the fields a
|
|
7
|
+
* profile's builders require. Read `this["Name"]` and `this["Args"]` directly
|
|
8
|
+
* in `Type`, passing them to a type alias; a nested object type cannot name
|
|
9
|
+
* `this`.
|
|
10
|
+
*/
|
|
11
|
+
export interface Fields {
|
|
12
|
+
readonly Name: string;
|
|
13
|
+
// `unknown`, not `ArgsFields`: an index signature here would leak into
|
|
14
|
+
// `keyof this["Args"]`.
|
|
15
|
+
readonly Args: unknown;
|
|
16
|
+
readonly Type: object;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface NoFields extends Fields {
|
|
20
|
+
readonly Type: {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Apply<
|
|
24
|
+
Fields_ extends Fields,
|
|
25
|
+
Name_ extends string,
|
|
26
|
+
Args_,
|
|
27
|
+
> = (Fields_ & { readonly Name: Name_; readonly Args: Args_ })["Type"];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A base profile's fields and its extension's, for one function.
|
|
31
|
+
*/
|
|
32
|
+
export interface Both<
|
|
33
|
+
Base extends Fields,
|
|
34
|
+
Extension extends Fields,
|
|
35
|
+
> extends Fields {
|
|
36
|
+
readonly Type: Apply<Base, this["Name"], this["Args"]> &
|
|
37
|
+
Apply<Extension, this["Name"], this["Args"]>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What a profile callback sees. `args` stays a thunk.
|
|
42
|
+
*/
|
|
43
|
+
export interface Info<FieldsType> {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly description?: string;
|
|
46
|
+
readonly args: () => FunctionProvenance.ArgsFields;
|
|
47
|
+
readonly fields: "description" extends keyof FieldsType
|
|
48
|
+
? Omit<FieldsType, "description">
|
|
49
|
+
: FieldsType;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Attachment<
|
|
53
|
+
MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec,
|
|
54
|
+
FieldsType,
|
|
55
|
+
> {
|
|
56
|
+
readonly spec: MiddlewareSpec_;
|
|
57
|
+
readonly options: (
|
|
58
|
+
info: Info<FieldsType>,
|
|
59
|
+
) => MiddlewareSpec.Options<MiddlewareSpec_>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A middleware without options has none to compute and is attached at most
|
|
64
|
+
* once, so it joins a profile's list directly.
|
|
65
|
+
*/
|
|
66
|
+
type TakesOptions<MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec> = [
|
|
67
|
+
MiddlewareSpec.WithoutOptions<MiddlewareSpec_>,
|
|
68
|
+
] extends [never]
|
|
69
|
+
? unknown
|
|
70
|
+
: MiddlewareSpec.AttachmentError<`Middleware "${MiddlewareSpec.Key<MiddlewareSpec_>}" takes no options, so list it without Profile.attach`>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Attach a middleware that takes options to every function a profile builds,
|
|
74
|
+
* with options computed from each function's fields.
|
|
75
|
+
*/
|
|
76
|
+
export const attach = <
|
|
77
|
+
MiddlewareSpec_ extends MiddlewareSpec.AnyMiddlewareSpec,
|
|
78
|
+
FieldsType,
|
|
79
|
+
>(
|
|
80
|
+
spec: MiddlewareSpec_ & TakesOptions<MiddlewareSpec_>,
|
|
81
|
+
options: (
|
|
82
|
+
info: Info<FieldsType>,
|
|
83
|
+
) => MiddlewareSpec.Options<NoInfer<MiddlewareSpec_>>,
|
|
84
|
+
): Attachment<MiddlewareSpec_, FieldsType> => ({ spec, options });
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A type function for a schema wrapper, applied to each declared codec.
|
|
88
|
+
*/
|
|
89
|
+
export interface SchemaTransform {
|
|
90
|
+
readonly Input: Schema.Codec<unknown, unknown>;
|
|
91
|
+
readonly Type: Schema.Codec<any, any>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Args wrappers transform the declared field map, before Struct composition.
|
|
96
|
+
*/
|
|
97
|
+
export interface ArgsTransform {
|
|
98
|
+
readonly Input: unknown;
|
|
99
|
+
readonly Type: FunctionProvenance.ArgsFields;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface Transforms {
|
|
103
|
+
readonly args?: ArgsTransform;
|
|
104
|
+
readonly returns?: SchemaTransform;
|
|
105
|
+
readonly item?: SchemaTransform;
|
|
106
|
+
readonly error?: SchemaTransform;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type Transform<T, Input> = (T & { readonly Input: Input })["Type" &
|
|
110
|
+
keyof T];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Error wrappers opt into Schema.Never when no error thunk was declared.
|
|
114
|
+
*/
|
|
115
|
+
export type Wrappers<T extends Transforms, FieldsType> = {
|
|
116
|
+
readonly [K in keyof T]-?: K extends keyof Transforms
|
|
117
|
+
? <
|
|
118
|
+
Input extends (K extends "args"
|
|
119
|
+
? FunctionProvenance.ArgsFields
|
|
120
|
+
: Schema.Codec<any, any>),
|
|
121
|
+
>(
|
|
122
|
+
thunk: () => Input,
|
|
123
|
+
info: Info<FieldsType>,
|
|
124
|
+
) => () => Transform<T[K], Input>
|
|
125
|
+
: never;
|
|
126
|
+
} & { readonly [K in Exclude<keyof Transforms, keyof T>]?: never };
|
|
127
|
+
|
|
128
|
+
type NonemptyError<T extends Transforms> = T extends {
|
|
129
|
+
readonly error: infer E extends SchemaTransform;
|
|
130
|
+
}
|
|
131
|
+
? [Transform<E, typeof Schema.Never>["Type"]] extends [never]
|
|
132
|
+
? MiddlewareSpec.AttachmentError<"Profile error wrapper must add an error to Schema.Never">
|
|
133
|
+
: unknown
|
|
134
|
+
: unknown;
|
|
135
|
+
|
|
136
|
+
export type SchemaConfig<T extends Transforms, FieldsType> = ([
|
|
137
|
+
keyof T,
|
|
138
|
+
] extends [never]
|
|
139
|
+
? { readonly schemas?: never }
|
|
140
|
+
: { readonly schemas: Wrappers<T, FieldsType> }) &
|
|
141
|
+
NonemptyError<T>;
|
package/src/Ref.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
PaginationOptions,
|
|
7
7
|
PaginationResult,
|
|
8
8
|
} from "convex/server";
|
|
9
|
-
import { getFunctionAddress
|
|
9
|
+
import { getFunctionAddress } from "convex/server";
|
|
10
10
|
import type { Value } from "convex/values";
|
|
11
11
|
import { ConvexError } from "convex/values";
|
|
12
12
|
import * as Effect from "effect/Effect";
|
|
@@ -18,6 +18,7 @@ import type * as FunctionProvenance from "./FunctionProvenance.js";
|
|
|
18
18
|
import type * as FunctionSpec from "./FunctionSpec.js";
|
|
19
19
|
import * as Lazy from "./Lazy.js";
|
|
20
20
|
import * as MiddlewareSpec from "./MiddlewareSpec.js";
|
|
21
|
+
import { getFunctionReference } from "./RefInternal.js";
|
|
21
22
|
import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType.js";
|
|
22
23
|
|
|
23
24
|
export const TypeId = "~@confect/core/Ref";
|
|
@@ -41,7 +42,9 @@ export interface Base<
|
|
|
41
42
|
: (args: Args_) => void;
|
|
42
43
|
readonly "~Returns": Returns_;
|
|
43
44
|
readonly "~Error": Error_;
|
|
45
|
+
readonly functionType?: RuntimeAndFunctionType_["functionType"];
|
|
44
46
|
readonly convexFunctionName: string;
|
|
47
|
+
readonly reservedArgument?: string;
|
|
45
48
|
/**
|
|
46
49
|
* Preserved for component references and function handles, which have no
|
|
47
50
|
* local name.
|
|
@@ -447,14 +450,19 @@ export const make = <
|
|
|
447
450
|
({
|
|
448
451
|
[TypeId]: TypeId,
|
|
449
452
|
_tag: "Convex",
|
|
453
|
+
functionType: functionSpec.runtimeAndFunctionType.functionType,
|
|
450
454
|
convexFunctionName,
|
|
451
455
|
}) as Any,
|
|
452
456
|
),
|
|
453
457
|
Match.tag("Confect", (provenance): Any =>
|
|
454
|
-
fromProvenance(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
+
fromProvenance(
|
|
459
|
+
convexFunctionName,
|
|
460
|
+
provenance,
|
|
461
|
+
[...groupMiddlewareAttachments, ...functionSpec.middlewareAttachments],
|
|
462
|
+
undefined,
|
|
463
|
+
functionSpec.runtimeAndFunctionType.functionType,
|
|
464
|
+
functionSpec.reservedArgument,
|
|
465
|
+
),
|
|
458
466
|
),
|
|
459
467
|
Match.exhaustive,
|
|
460
468
|
) as FromFunctionSpec<
|
|
@@ -476,11 +484,15 @@ export const fromProvenance = (
|
|
|
476
484
|
provenance: FunctionProvenance.AnyConfect,
|
|
477
485
|
middlewareAttachments: ReadonlyArray<MiddlewareAttachment.MiddlewareAttachment>,
|
|
478
486
|
functionReference?: ConvexFunctionReference<any, any>,
|
|
487
|
+
functionType?: "query" | "mutation" | "action",
|
|
488
|
+
reservedArgument?: string,
|
|
479
489
|
): AnyConfect => {
|
|
480
490
|
const ref = {
|
|
481
491
|
[TypeId]: TypeId as TypeId,
|
|
482
492
|
_tag: "Confect" as const,
|
|
493
|
+
...(functionType === undefined ? {} : { functionType }),
|
|
483
494
|
convexFunctionName,
|
|
495
|
+
...(reservedArgument === undefined ? {} : { reservedArgument }),
|
|
484
496
|
...(functionReference === undefined ? {} : { functionReference }),
|
|
485
497
|
kind: provenance.kind,
|
|
486
498
|
middlewareAttachments,
|
|
@@ -501,27 +513,6 @@ export const fromProvenance = (
|
|
|
501
513
|
export const getConvexFunctionName = (ref: Any): string =>
|
|
502
514
|
ref.convexFunctionName;
|
|
503
515
|
|
|
504
|
-
const functionReferenceCache = new Map<string, FunctionReference<Any>>();
|
|
505
|
-
|
|
506
|
-
export const getFunctionReference = <Ref_ extends Any>(
|
|
507
|
-
ref: Ref_,
|
|
508
|
-
): FunctionReference<Ref_> => {
|
|
509
|
-
if (ref.functionReference !== undefined) {
|
|
510
|
-
return ref.functionReference as FunctionReference<Ref_>;
|
|
511
|
-
}
|
|
512
|
-
const convexFunctionName = getConvexFunctionName(ref);
|
|
513
|
-
|
|
514
|
-
const cached = functionReferenceCache.get(convexFunctionName);
|
|
515
|
-
if (cached !== undefined) {
|
|
516
|
-
return cached as FunctionReference<Ref_>;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
const functionReference = makeFunctionReference(convexFunctionName);
|
|
520
|
-
functionReferenceCache.set(convexFunctionName, functionReference);
|
|
521
|
-
|
|
522
|
-
return functionReference as FunctionReference<Ref_>;
|
|
523
|
-
};
|
|
524
|
-
|
|
525
516
|
/**
|
|
526
517
|
* Adapt a native reference without assigning Confect codecs or typed errors.
|
|
527
518
|
*/
|
|
@@ -532,6 +523,7 @@ export const fromFunctionReference = <
|
|
|
532
523
|
Returns_,
|
|
533
524
|
>(
|
|
534
525
|
functionReference: ConvexFunctionReference<Type, Visibility, Args_, Returns_>,
|
|
526
|
+
functionType?: NoInfer<Type>,
|
|
535
527
|
): ConvexRef<
|
|
536
528
|
Extract<
|
|
537
529
|
RuntimeAndFunctionType.RuntimeAndFunctionType,
|
|
@@ -545,6 +537,7 @@ export const fromFunctionReference = <
|
|
|
545
537
|
return {
|
|
546
538
|
[TypeId]: TypeId,
|
|
547
539
|
_tag: "Convex",
|
|
540
|
+
...(functionType === undefined ? {} : { functionType }),
|
|
548
541
|
convexFunctionName:
|
|
549
542
|
address.name ??
|
|
550
543
|
address.reference ??
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { makeFunctionReference } from "convex/server";
|
|
2
|
+
import type * as Ref from "./Ref.js";
|
|
3
|
+
|
|
4
|
+
const functionReferenceCache = new Map<
|
|
5
|
+
string,
|
|
6
|
+
Ref.FunctionReference<Ref.Any>
|
|
7
|
+
>();
|
|
8
|
+
|
|
9
|
+
export const getFunctionReference = <Ref_ extends Ref.Any>(
|
|
10
|
+
ref: Ref_,
|
|
11
|
+
): Ref.FunctionReference<Ref_> => {
|
|
12
|
+
if (ref.functionReference !== undefined) {
|
|
13
|
+
return ref.functionReference as Ref.FunctionReference<Ref_>;
|
|
14
|
+
}
|
|
15
|
+
const convexFunctionName = ref.convexFunctionName;
|
|
16
|
+
|
|
17
|
+
const cached = functionReferenceCache.get(convexFunctionName);
|
|
18
|
+
if (cached !== undefined) {
|
|
19
|
+
return cached as Ref.FunctionReference<Ref_>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const functionReference = makeFunctionReference(convexFunctionName);
|
|
23
|
+
functionReferenceCache.set(convexFunctionName, functionReference);
|
|
24
|
+
|
|
25
|
+
return functionReference as Ref.FunctionReference<Ref_>;
|
|
26
|
+
};
|
package/src/SchemaToValidator.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as IdScope from "./IdScope.js";
|
|
2
|
+
import type * as Callback from "./Callback.js";
|
|
3
|
+
import { CallbackContract } from "./internal/annotations.js";
|
|
2
4
|
import type {
|
|
3
5
|
GenericId as ConvexGenericId,
|
|
4
6
|
PropertyValidators,
|
|
@@ -46,10 +48,24 @@ import type {
|
|
|
46
48
|
export const compileArgsSchema = <ConfectValue, ConvexValue>(
|
|
47
49
|
argsSchema: Schema.Codec<ConfectValue, ConvexValue>,
|
|
48
50
|
scope: IdScope.IdScope = IdScope.app,
|
|
51
|
+
reservedArgument?: string,
|
|
52
|
+
functionName?: string,
|
|
49
53
|
): PropertyValidators => {
|
|
50
54
|
const ast = Schema.toEncoded(argsSchema).ast;
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
const validators = Effect.runSync(compileArgsAst(ast, scope));
|
|
57
|
+
if (reservedArgument === undefined) return validators;
|
|
58
|
+
if (Object.hasOwn(validators, reservedArgument)) {
|
|
59
|
+
throw new ReservedArgumentCollision({
|
|
60
|
+
functionName,
|
|
61
|
+
argument: reservedArgument,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return { ...validators, [reservedArgument]: v.optional(v.any()) };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const compileArgsAst = (ast: SchemaAST.AST, scope: IdScope.IdScope) =>
|
|
68
|
+
pipe(
|
|
53
69
|
ast,
|
|
54
70
|
Match.value,
|
|
55
71
|
Match.tag("Objects", (objectsAst) =>
|
|
@@ -58,9 +74,18 @@ export const compileArgsSchema = <ConfectValue, ConvexValue>(
|
|
|
58
74
|
: Effect.fail(new IndexSignaturesAreNotSupportedError()),
|
|
59
75
|
),
|
|
60
76
|
Match.orElse(() => Effect.fail(new TopLevelMustBeObjectError())),
|
|
61
|
-
Effect.runSync,
|
|
62
77
|
);
|
|
63
|
-
|
|
78
|
+
|
|
79
|
+
export class ReservedArgumentCollision extends Data.TaggedError(
|
|
80
|
+
"ReservedArgumentCollision",
|
|
81
|
+
)<{
|
|
82
|
+
readonly functionName: string | undefined;
|
|
83
|
+
readonly argument: string;
|
|
84
|
+
}> {
|
|
85
|
+
override get message() {
|
|
86
|
+
return `${this.functionName} declares the argument "${this.argument}", which its profile reserves. Rename the argument.`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
64
89
|
|
|
65
90
|
// Returns
|
|
66
91
|
|
|
@@ -313,6 +338,7 @@ export const compileAst = (
|
|
|
313
338
|
scope: IdScope.IdScope = IdScope.app,
|
|
314
339
|
): Effect.Effect<
|
|
315
340
|
Validator<any, any, any>,
|
|
341
|
+
| TopLevelMustBeObjectError
|
|
316
342
|
| UnsupportedSchemaTypeError
|
|
317
343
|
| UnsupportedPropertySignatureKeyTypeError
|
|
318
344
|
| IndexSignaturesAreNotSupportedError
|
|
@@ -320,8 +346,23 @@ export const compileAst = (
|
|
|
320
346
|
| OptionalTupleElementsAreNotSupportedError
|
|
321
347
|
| EmptyTupleIsNotSupportedError
|
|
322
348
|
| EmptyUnionIsNotSupportedError
|
|
323
|
-
> =>
|
|
324
|
-
|
|
349
|
+
> => {
|
|
350
|
+
const contract = SchemaAST.resolveAt<Callback.Any>(CallbackContract)(ast);
|
|
351
|
+
if (contract !== undefined) {
|
|
352
|
+
return Effect.gen(function* () {
|
|
353
|
+
const args = yield* compileArgsAst(
|
|
354
|
+
Schema.toEncoded(contract.functionProvenance.args).ast,
|
|
355
|
+
scope,
|
|
356
|
+
);
|
|
357
|
+
const returns = yield* compileAst(
|
|
358
|
+
Schema.toEncoded(contract.functionProvenance.returns).ast,
|
|
359
|
+
false,
|
|
360
|
+
scope,
|
|
361
|
+
);
|
|
362
|
+
return v.function({ fnType: contract.functionType, args, returns });
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
return isRecursive(ast)
|
|
325
366
|
? Effect.succeed(v.any())
|
|
326
367
|
: pipe(
|
|
327
368
|
ast,
|
|
@@ -400,6 +441,7 @@ export const compileAst = (
|
|
|
400
441
|
),
|
|
401
442
|
Match.exhaustive,
|
|
402
443
|
);
|
|
444
|
+
};
|
|
403
445
|
|
|
404
446
|
const handleUnion = (
|
|
405
447
|
{ types }: SchemaAST.Union,
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * as ActionCall from "./ActionCall.js";
|
|
1
2
|
export * as Callback from "./Callback.js";
|
|
2
3
|
export * as FunctionProvenance from "./FunctionProvenance.js";
|
|
3
4
|
export * as FunctionSpec from "./FunctionSpec.js";
|
|
@@ -13,11 +14,13 @@ export * as GroupSpec from "./GroupSpec.js";
|
|
|
13
14
|
export * as Identifier from "./Identifier.js";
|
|
14
15
|
export * as Lazy from "./Lazy.js";
|
|
15
16
|
export * as Manifest from "./Manifest.js";
|
|
17
|
+
export * as ManifestSection from "./ManifestSection.js";
|
|
16
18
|
export * as MiddlewareAttachment from "./MiddlewareAttachment.js";
|
|
17
19
|
export * as MiddlewareSpec from "./MiddlewareSpec.js";
|
|
18
20
|
export * as PaginationError from "./PaginationError.js";
|
|
19
21
|
export * as PaginationOptions from "./PaginationOptions.js";
|
|
20
22
|
export * as PaginationResult from "./PaginationResult.js";
|
|
23
|
+
export * as Profile from "./Profile.js";
|
|
21
24
|
export * as Ref from "./Ref.js";
|
|
22
25
|
export * as Refs from "./Refs.js";
|
|
23
26
|
export * as RuntimeAndFunctionType from "./RuntimeAndFunctionType.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CallbackContract = "~@confect/core/Callback/Contract";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as HttpRouter from "effect/http/HttpRouter";
|
|
3
|
+
import * as HttpServerResponse from "effect/http/HttpServerResponse";
|
|
4
|
+
import type * as HttpApi from "effect/http-api/HttpApi";
|
|
5
|
+
import type * as HttpApiGroup from "effect/http-api/HttpApiGroup";
|
|
6
|
+
import * as OpenApi from "effect/http-api/OpenApi";
|
|
7
|
+
|
|
8
|
+
const escape = (value: string) =>
|
|
9
|
+
value
|
|
10
|
+
.replaceAll("&", "&")
|
|
11
|
+
.replaceAll("<", "<")
|
|
12
|
+
.replaceAll(">", ">");
|
|
13
|
+
|
|
14
|
+
const escapeAttribute = (value: string) =>
|
|
15
|
+
escape(value).replaceAll('"', """).replaceAll("'", "'");
|
|
16
|
+
|
|
17
|
+
const escapeJson = (value: unknown) =>
|
|
18
|
+
JSON.stringify(value)
|
|
19
|
+
.replaceAll("<", "\\u003c")
|
|
20
|
+
.replaceAll("\u2028", "\\u2028")
|
|
21
|
+
.replaceAll("\u2029", "\\u2029");
|
|
22
|
+
|
|
23
|
+
// WORKAROUND: Effect 4.0.6's HttpApiScalar module imports its inline Scalar
|
|
24
|
+
// script dynamically, and Convex's bundler then splits a schema that reaches it
|
|
25
|
+
// into an imported chunk, which Convex refuses. This copies `layerCdn`'s page
|
|
26
|
+
// byte for byte. Remove when `checkSchemaBundle.mjs` passes with
|
|
27
|
+
// `HttpApiScalar.layerCdn` restored.
|
|
28
|
+
export const scalarDocs = <
|
|
29
|
+
Id extends string,
|
|
30
|
+
Groups extends HttpApiGroup.Constraint,
|
|
31
|
+
>(
|
|
32
|
+
api: HttpApi.HttpApi<Id, Groups>,
|
|
33
|
+
path: `/${string}`,
|
|
34
|
+
) =>
|
|
35
|
+
HttpRouter.use((router) => {
|
|
36
|
+
let response: HttpServerResponse.HttpServerResponse | undefined;
|
|
37
|
+
return router.add(
|
|
38
|
+
"GET",
|
|
39
|
+
path,
|
|
40
|
+
Effect.sync(() => {
|
|
41
|
+
if (response !== undefined) return response;
|
|
42
|
+
const spec = OpenApi.fromApi(api);
|
|
43
|
+
const description = (tag: string) =>
|
|
44
|
+
!spec.info.description
|
|
45
|
+
? ""
|
|
46
|
+
: `<meta name="${tag}" content="${escapeAttribute(spec.info.description)}"/>`;
|
|
47
|
+
response = HttpServerResponse.html(`<!doctype html>
|
|
48
|
+
<html>
|
|
49
|
+
<head>
|
|
50
|
+
<meta charset="utf-8" />
|
|
51
|
+
<title>${escape(spec.info.title)}</title>
|
|
52
|
+
${description("description")}
|
|
53
|
+
${description("og:description")}
|
|
54
|
+
<meta
|
|
55
|
+
name="viewport"
|
|
56
|
+
content="width=device-width, initial-scale=1" />
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<div id="api-reference-container"></div>
|
|
60
|
+
<script src="${escapeAttribute("https://cdn.jsdelivr.net/npm/@scalar/api-reference@latest/dist/browser/standalone.min.js")}" crossorigin></script>
|
|
61
|
+
<script>
|
|
62
|
+
window.Scalar.createApiReference(document.getElementById('api-reference-container'), {
|
|
63
|
+
...${escapeJson({ _integration: "html" })},
|
|
64
|
+
content: ${escapeJson(spec)}
|
|
65
|
+
})
|
|
66
|
+
</script>
|
|
67
|
+
</body>
|
|
68
|
+
</html>`);
|
|
69
|
+
return response;
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
});
|