@alpacakit/channels 0.1.0-beta.37 → 0.2.0-beta.2
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/README.md +204 -723
- package/dist/binding-compiler.d.ts +12 -2
- package/dist/binding-compiler.d.ts.map +1 -1
- package/dist/binding-compiler.js +25 -0
- package/dist/cli-coercion.d.ts +1 -1
- package/dist/cli-coercion.d.ts.map +1 -1
- package/dist/cli-coercion.js +14 -29
- package/dist/cli-compiler.d.ts +4 -17
- package/dist/cli-compiler.d.ts.map +1 -1
- package/dist/cli-compiler.js +78 -39
- package/dist/cli-help-projection.d.ts.map +1 -1
- package/dist/cli-help-projection.js +41 -36
- package/dist/cli-model.d.ts +50 -65
- package/dist/cli-model.d.ts.map +1 -1
- package/dist/cli-model.js +26 -34
- package/dist/cli-tree.d.ts +6 -3
- package/dist/cli-tree.d.ts.map +1 -1
- package/dist/cli-tree.js +161 -387
- package/dist/cli.d.ts +1 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -5
- package/dist/codecs.d.ts +20 -18
- package/dist/codecs.d.ts.map +1 -1
- package/dist/codecs.js +21 -31
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +8 -3
- package/dist/definition-error.d.ts +5 -0
- package/dist/definition-error.d.ts.map +1 -0
- package/dist/definition-error.js +8 -0
- package/dist/factory.d.ts +25 -11
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +28 -18
- package/dist/http-compiler.d.ts +15 -0
- package/dist/http-compiler.d.ts.map +1 -0
- package/dist/http-compiler.js +37 -0
- package/dist/http-model.d.ts +42 -0
- package/dist/http-model.d.ts.map +1 -0
- package/dist/http-model.js +20 -0
- package/dist/http-tree.d.ts +7 -0
- package/dist/http-tree.d.ts.map +1 -0
- package/dist/http-tree.js +181 -0
- package/dist/http.d.ts +3 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/input-compiler.d.ts +13 -0
- package/dist/input-compiler.d.ts.map +1 -0
- package/dist/input-compiler.js +122 -0
- package/dist/introspection.d.ts +1 -1
- package/dist/introspection.d.ts.map +1 -1
- package/dist/introspection.js +30 -16
- package/dist/mcp.d.ts +25 -24
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +87 -65
- package/dist/model.d.ts +44 -42
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +44 -9
- package/dist/owned.d.ts +12 -0
- package/dist/owned.d.ts.map +1 -0
- package/dist/owned.js +61 -0
- package/dist/registration.d.ts +41 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/registration.js +14 -0
- package/dist/runtime.d.ts +2 -35
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -127
- package/dist/schema.d.ts +11 -8
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +110 -47
- package/dist/string-coercion.d.ts +4 -0
- package/dist/string-coercion.d.ts.map +1 -0
- package/dist/string-coercion.js +24 -0
- package/dist/tree-internal.d.ts +4 -2
- package/dist/tree-internal.d.ts.map +1 -1
- package/dist/tree-internal.js +5 -2
- package/dist/values.d.ts +1 -6
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +1 -6
- package/examples/README.md +28 -0
- package/examples/book-search.ts +151 -0
- package/examples/greeting.ts +193 -0
- package/package.json +9 -4
- package/dist/cli-application.d.ts +0 -40
- package/dist/cli-application.d.ts.map +0 -1
- package/dist/cli-application.js +0 -90
- package/dist/cli-executor.d.ts +0 -25
- package/dist/cli-executor.d.ts.map +0 -1
- package/dist/cli-executor.js +0 -44
- package/dist/cli-presentation.d.ts +0 -38
- package/dist/cli-presentation.d.ts.map +0 -1
- package/dist/cli-presentation.js +0 -301
- package/dist/cli-result-envelope.d.ts +0 -104
- package/dist/cli-result-envelope.d.ts.map +0 -1
- package/dist/cli-result-envelope.js +0 -137
- package/dist/cli-schema.d.ts +0 -4
- package/dist/cli-schema.d.ts.map +0 -1
- package/dist/cli-schema.js +0 -7
package/dist/codecs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codecs.d.ts","sourceRoot":"","sources":["../src/codecs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"codecs.d.ts","sourceRoot":"","sources":["../src/codecs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI5D,KAAK,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,SAAS,GAAG,GAAG,GAAG,SAAS,CAAC;AAE9D,iBAAS,gBAAgB,CAAC,GAAG,EAAE,KAAK,SAAS,SAAS,EAAE,KAAK,EAAE;IAC7D,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;CAC/C,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAEzB;AAYD,iBAAS,WAAW,IAAI,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAKjD;AAED,iBAAS,YAAY,IAAI,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAKpD;AAED,iBAAS,oBAAoB,CAC3B,KAAK,GAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAkB,GACrD,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAM5B;AAED,iBAAS,eAAe,CAAC,KAAK,CAAC,MAAM,SAAS,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,EAC1E,MAAM,EAAE,MAAM,GACb,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAM5C;AAED,iBAAS,mBAAmB,CAC1B,KAAK,CAAC,MAAM,SAAS,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,EAEnD,MAAM,EAAE,MAAM,GACb,UAAU,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAYlE;AAED,iBAAS,eAAe,IAAI,UAAU,CAAC,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC,CAS3E;AAED,eAAO,MAAM,MAAM;;;;;;;;CAQT,CAAC"}
|
package/dist/codecs.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { createSchemaRuntimeCodec, readonlyArraySchema } from "@alpacakit/core";
|
|
2
1
|
import { z } from "zod";
|
|
2
|
+
import { ownedArray, ownedRecord, snapshotValueShape } from "./owned.js";
|
|
3
3
|
import { VALUE_SHAPE_KIND } from "./values.js";
|
|
4
4
|
function defineValueCodec(input) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
5
|
+
return makeValueCodec(input.schema, input.shape);
|
|
6
|
+
}
|
|
7
|
+
function makeValueCodec(schema, shape) {
|
|
8
|
+
return ownedRecord({
|
|
9
|
+
schema,
|
|
10
|
+
shape: snapshotValueShape(shape),
|
|
11
|
+
});
|
|
12
12
|
}
|
|
13
13
|
function stringCodec() {
|
|
14
14
|
return defineValueCodec({
|
|
@@ -25,42 +25,32 @@ function booleanCodec() {
|
|
|
25
25
|
function positiveIntegerCodec(input = { max: null }) {
|
|
26
26
|
const base = z.number().int().positive();
|
|
27
27
|
return defineValueCodec({
|
|
28
|
-
schema:
|
|
29
|
-
? base
|
|
30
|
-
: base.max(input.max)),
|
|
28
|
+
schema: input.max === null ? base : base.max(input.max),
|
|
31
29
|
shape: { kind: VALUE_SHAPE_KIND.positiveInteger },
|
|
32
30
|
});
|
|
33
31
|
}
|
|
34
32
|
function stringEnumCodec(values) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
values,
|
|
40
|
-
},
|
|
33
|
+
const snapshot = ownedArray(values);
|
|
34
|
+
return makeValueCodec(z.enum(snapshot), {
|
|
35
|
+
kind: VALUE_SHAPE_KIND.stringEnum,
|
|
36
|
+
values: snapshot,
|
|
41
37
|
});
|
|
42
38
|
}
|
|
43
39
|
function stringEnumListCodec(values) {
|
|
44
40
|
const element = stringEnumCodec(values);
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
kind: VALUE_SHAPE_KIND.
|
|
49
|
-
|
|
50
|
-
kind: VALUE_SHAPE_KIND.stringEnum,
|
|
51
|
-
values,
|
|
52
|
-
},
|
|
41
|
+
return makeValueCodec(z.array(element.schema).readonly(), {
|
|
42
|
+
kind: VALUE_SHAPE_KIND.array,
|
|
43
|
+
element: {
|
|
44
|
+
kind: VALUE_SHAPE_KIND.stringEnum,
|
|
45
|
+
values,
|
|
53
46
|
},
|
|
54
47
|
});
|
|
55
48
|
}
|
|
56
49
|
function stringListCodec() {
|
|
57
50
|
const element = stringCodec();
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
kind: VALUE_SHAPE_KIND.array,
|
|
62
|
-
element: element.shape,
|
|
63
|
-
},
|
|
51
|
+
return makeValueCodec(z.array(element.schema).readonly(), {
|
|
52
|
+
kind: VALUE_SHAPE_KIND.array,
|
|
53
|
+
element: element.shape,
|
|
64
54
|
});
|
|
65
55
|
}
|
|
66
56
|
export const codecs = {
|
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EACV,eAAe,EACf,yBAAyB,
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EACV,eAAe,EACf,yBAAyB,EAEzB,cAAc,EACd,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,wBAAgB,oBAAoB,CAAC,KAAK,SAAS,SAAS,EAC1D,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GACvB,cAAc,CAAC,KAAK,CAAC,CAEvB;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAE/C,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE;gBADzB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,MAAM,EAAE;CAKrC;AAED,wBAAgB,kBAAkB,CAChC,KAAK,CAAC,IAAI,SAAS,MAAM,EACzB,KAAK,CAAC,UAAU,SAAS,YAAY,EACrC,UAAU,EACV,MAAM,SAAS,SAAS,EAExB,KAAK,EAAE,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,GAC3D,cAAc,CAAC,MAAM,CAAC,CAKxB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,CAAC,IAAI,SAAS,MAAM,EACzB,KAAK,CAAC,UAAU,SAAS,YAAY,EACrC,UAAU,EACV,MAAM,SAAS,SAAS,EAExB,KAAK,EAAE,yBAAyB,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,EACtE,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,GACrB,MAAM,CAER"}
|
package/dist/contract.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { CODEC_DECODE_RESULT_KIND, createSchemaRuntimeCodec, } from "@alpacakit/core";
|
|
2
2
|
export function createOutputContract(schema) {
|
|
3
|
-
|
|
4
|
-
return { schema, decode };
|
|
3
|
+
return Object.freeze({ schema });
|
|
5
4
|
}
|
|
6
5
|
export class EntryOutputContractError extends Error {
|
|
7
6
|
entryName;
|
|
@@ -20,7 +19,13 @@ export function requireEntryOutput(entry) {
|
|
|
20
19
|
return entry.output;
|
|
21
20
|
}
|
|
22
21
|
export function verifyEntryOutput(entry, value) {
|
|
23
|
-
|
|
22
|
+
return verifyEntryOutputValue(entry, value);
|
|
23
|
+
}
|
|
24
|
+
function verifyEntryOutputValue(entry, value) {
|
|
25
|
+
if (entry.output === null) {
|
|
26
|
+
throw new Error(`Entry ${entry.name} does not declare an output contract`);
|
|
27
|
+
}
|
|
28
|
+
const decoded = createSchemaRuntimeCodec(entry.output.schema).decode(value);
|
|
24
29
|
if (decoded.kind === CODEC_DECODE_RESULT_KIND.invalid) {
|
|
25
30
|
throw new EntryOutputContractError(entry.name, decoded.issues);
|
|
26
31
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition-error.d.ts","sourceRoot":"","sources":["../src/definition-error.ts"],"names":[],"mappings":"AAAA,qBAAa,wBAAyB,SAAQ,KAAK;IACrC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE;gBAAzB,MAAM,EAAE,SAAS,MAAM,EAAE;CAI/C"}
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export declare function defineEntryTree(root: EntryTreeNode): EntryTreeDefinition;
|
|
5
|
-
export declare function defineEntryNode(input: {
|
|
1
|
+
import type { Callable, ChannelSegmentOverride, CommandDocumentationInput, EndpointsOfNode, EntryChannelBinding, EntryTreeDefinition, EntryTreeEndpoint, EntryTreeNode, ErasedEntryDefinition } from "./model.js";
|
|
2
|
+
export declare function defineEntryTree<const Node extends EntryTreeNode>(root: Node): EntryTreeDefinition<EndpointsOfNode<Node>>;
|
|
3
|
+
export declare function defineEntryNode<const Endpoints extends readonly EntryTreeEndpoint[] = readonly [], const Children extends readonly EntryTreeNode[] = readonly []>(input: {
|
|
6
4
|
readonly name: string;
|
|
7
5
|
readonly description: string;
|
|
8
6
|
readonly documentation?: CommandDocumentationInput;
|
|
9
7
|
readonly segments?: readonly ChannelSegmentOverride[];
|
|
10
|
-
readonly endpoints?:
|
|
11
|
-
readonly children?:
|
|
12
|
-
}): EntryTreeNode
|
|
13
|
-
export declare function mergeEntryTreeNodes
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
readonly endpoints?: Endpoints;
|
|
9
|
+
readonly children?: Children;
|
|
10
|
+
}): EntryTreeNode<Endpoints[number] | EndpointsOfNode<Children[number]>>;
|
|
11
|
+
export declare function mergeEntryTreeNodes<const Nodes extends readonly EntryTreeNode[]>(nodes: Nodes): readonly EntryTreeNode<EndpointsOfNode<Nodes[number]>>[];
|
|
12
|
+
/**
|
|
13
|
+
* Bundles an Entry definition object with channel bindings. This factory does
|
|
14
|
+
* not register or execute an application handler.
|
|
15
|
+
*/
|
|
16
|
+
type CheckedHandlerDefinition<Arguments extends readonly unknown[]> = number extends Arguments["length"] ? {
|
|
17
|
+
readonly handler: never;
|
|
18
|
+
} : Exclude<Arguments["length"], 0 | 1 | 2> extends never ? unknown : {
|
|
19
|
+
readonly handler: never;
|
|
20
|
+
};
|
|
21
|
+
type HandlerArgumentsFor<Projection, Arguments extends readonly unknown[]> = {
|
|
22
|
+
readonly [Index in keyof Arguments]: Index extends "0" ? Projection : Arguments[Index];
|
|
23
|
+
};
|
|
24
|
+
type RegisteredHandler<Arguments extends readonly unknown[], Result> = (...args: Arguments) => Result;
|
|
25
|
+
export declare function defineEntryEndpoint<const Entry extends ErasedEntryDefinition, const Handler extends Callable, const Arguments extends readonly unknown[], Result, const Channels extends readonly EntryChannelBinding[]>(entry: Entry, definition: {
|
|
26
|
+
readonly handler: Handler & RegisteredHandler<Arguments, Result> & RegisteredHandler<HandlerArgumentsFor<ReturnType<Entry["project"]>, Arguments>, Result>;
|
|
27
|
+
readonly channels: Channels;
|
|
28
|
+
} & CheckedHandlerDefinition<Arguments>): EntryTreeEndpoint<Entry, Handler, Channels>;
|
|
29
|
+
export {};
|
|
16
30
|
//# sourceMappingURL=factory.d.ts.map
|
package/dist/factory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,sBAAsB,EAEtB,yBAAyB,EACzB,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAcpB,wBAAgB,eAAe,CAAC,KAAK,CAAC,IAAI,SAAS,aAAa,EAC9D,IAAI,EAAE,IAAI,GACT,mBAAmB,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAI5C;AAED,wBAAgB,eAAe,CAC7B,KAAK,CAAC,SAAS,SAAS,SAAS,iBAAiB,EAAE,GAAG,SAAS,EAAE,EAClE,KAAK,CAAC,QAAQ,SAAS,SAAS,aAAa,EAAE,GAAG,SAAS,EAAE,EAC7D,KAAK,EAAE;IACP,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,yBAAyB,CAAC;IACnD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACtD,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CAC9B,GAAG,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAWvE;AAED,wBAAgB,mBAAmB,CACjC,KAAK,CAAC,KAAK,SAAS,SAAS,aAAa,EAAE,EAC5C,KAAK,EAAE,KAAK,GAAG,SAAS,aAAa,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAqFxE;AA+JD;;;GAGG;AACH,KAAK,wBAAwB,CAAC,SAAS,SAAS,SAAS,OAAO,EAAE,IAChE,MAAM,SAAS,SAAS,CAAC,QAAQ,CAAC,GAC9B;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;CAAE,GAC3B,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,KAAK,GACnD,OAAO,GACP;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;CAAE,CAAC;AAEpC,KAAK,mBAAmB,CAAC,UAAU,EAAE,SAAS,SAAS,SAAS,OAAO,EAAE,IAAI;IAC3E,QAAQ,EAAE,KAAK,IAAI,MAAM,SAAS,GAAG,KAAK,SAAS,GAAG,GAClD,UAAU,GACV,SAAS,CAAC,KAAK,CAAC;CACrB,CAAC;AAEF,KAAK,iBAAiB,CAAC,SAAS,SAAS,SAAS,OAAO,EAAE,EAAE,MAAM,IAAI,CACrE,GAAG,IAAI,EAAE,SAAS,KACf,MAAM,CAAC;AAEZ,wBAAgB,mBAAmB,CACjC,KAAK,CAAC,KAAK,SAAS,qBAAqB,EACzC,KAAK,CAAC,OAAO,SAAS,QAAQ,EAC9B,KAAK,CAAC,SAAS,SAAS,SAAS,OAAO,EAAE,EAC1C,MAAM,EACN,KAAK,CAAC,QAAQ,SAAS,SAAS,mBAAmB,EAAE,EAErD,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE;IACV,QAAQ,CAAC,OAAO,EAAE,OAAO,GACvB,iBAAiB,CAAC,SAAS,EAAE,MAAM,CAAC,GACpC,iBAAiB,CACf,mBAAmB,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,EAC5D,MAAM,CACP,CAAC;IACJ,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B,GAAG,wBAAwB,CAAC,SAAS,CAAC,GACtC,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAW7C"}
|
package/dist/factory.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
+
import { ownedArray, ownedRecord } from "./owned.js";
|
|
1
2
|
import { EntryTreeDefinitionError } from "./schema.js";
|
|
2
3
|
const ENTRY_TREE_MERGE_ISSUE = {
|
|
3
4
|
conflictingDocumentation: "conflicting node documentation",
|
|
4
5
|
};
|
|
5
|
-
const EMPTY_COMMAND_DOCUMENTATION = {
|
|
6
|
-
remarks: [],
|
|
7
|
-
relatedDocs: [],
|
|
8
|
-
};
|
|
6
|
+
const EMPTY_COMMAND_DOCUMENTATION = ownedRecord({
|
|
7
|
+
remarks: ownedArray([]),
|
|
8
|
+
relatedDocs: ownedArray([]),
|
|
9
|
+
});
|
|
9
10
|
export function defineEntryTree(root) {
|
|
10
|
-
return { root };
|
|
11
|
+
return ownedRecord({ root });
|
|
11
12
|
}
|
|
12
13
|
export function defineEntryNode(input) {
|
|
13
|
-
return {
|
|
14
|
+
return ownedRecord({
|
|
14
15
|
name: input.name,
|
|
15
16
|
description: input.description,
|
|
16
17
|
documentation: normalizeCommandDocumentation(input.documentation),
|
|
17
|
-
segments: input.segments ?? [],
|
|
18
|
-
endpoints: input.endpoints ?? [],
|
|
19
|
-
children: input.children ?? [],
|
|
20
|
-
};
|
|
18
|
+
segments: ownedArray(input.segments ?? []),
|
|
19
|
+
endpoints: ownedArray(input.endpoints ?? []),
|
|
20
|
+
children: ownedArray(input.children ?? []),
|
|
21
|
+
});
|
|
21
22
|
}
|
|
22
23
|
export function mergeEntryTreeNodes(nodes) {
|
|
23
24
|
const rootFrames = groupNodes(nodes).map((group) => createMergeFrame(group, ""));
|
|
@@ -75,7 +76,7 @@ export function mergeEntryTreeNodes(nodes) {
|
|
|
75
76
|
}),
|
|
76
77
|
}));
|
|
77
78
|
}
|
|
78
|
-
return rootFrames.map((frame) => {
|
|
79
|
+
return ownedArray(rootFrames.map((frame) => {
|
|
79
80
|
const node = merged.get(frame);
|
|
80
81
|
if (!node) {
|
|
81
82
|
throw new EntryTreeDefinitionError([
|
|
@@ -83,7 +84,7 @@ export function mergeEntryTreeNodes(nodes) {
|
|
|
83
84
|
]);
|
|
84
85
|
}
|
|
85
86
|
return node;
|
|
86
|
-
});
|
|
87
|
+
}));
|
|
87
88
|
}
|
|
88
89
|
function createMergeFrame(nodes, parentPath) {
|
|
89
90
|
const first = nodes[0];
|
|
@@ -102,10 +103,10 @@ function normalizeCommandDocumentation(input) {
|
|
|
102
103
|
if (input === undefined) {
|
|
103
104
|
return EMPTY_COMMAND_DOCUMENTATION;
|
|
104
105
|
}
|
|
105
|
-
return {
|
|
106
|
-
remarks: input.remarks ?? [],
|
|
107
|
-
relatedDocs: input.relatedDocs ?? [],
|
|
108
|
-
};
|
|
106
|
+
return ownedRecord({
|
|
107
|
+
remarks: ownedArray(input.remarks ?? []),
|
|
108
|
+
relatedDocs: ownedArray(input.relatedDocs ?? []),
|
|
109
|
+
});
|
|
109
110
|
}
|
|
110
111
|
function mergeCommandDocumentation(nodes, path, issues) {
|
|
111
112
|
const claims = nodes
|
|
@@ -183,6 +184,15 @@ function mergeSegments(nodes, path, issues) {
|
|
|
183
184
|
}
|
|
184
185
|
return [...byChannel.values()];
|
|
185
186
|
}
|
|
186
|
-
export function defineEntryEndpoint(entry,
|
|
187
|
-
|
|
187
|
+
export function defineEntryEndpoint(entry, definition) {
|
|
188
|
+
if (definition.handler.length > 2) {
|
|
189
|
+
throw new EntryTreeDefinitionError([
|
|
190
|
+
`${entry.name}: handler must accept zero to two parameters`,
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
return ownedRecord({
|
|
194
|
+
entry,
|
|
195
|
+
handler: definition.handler,
|
|
196
|
+
channels: ownedArray(definition.channels),
|
|
197
|
+
});
|
|
188
198
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { compileParameterExclusions } from "./binding-compiler.js";
|
|
2
|
+
import type { HttpEntryBinding, HttpSegment } from "./http-model.js";
|
|
3
|
+
import { type ParameterDescription } from "./introspection.js";
|
|
4
|
+
import type { EntryTreeEndpoint } from "./model.js";
|
|
5
|
+
export type CompiledHttpEndpoint = {
|
|
6
|
+
readonly endpoint: EntryTreeEndpoint;
|
|
7
|
+
readonly binding: HttpEntryBinding;
|
|
8
|
+
readonly parameterBindings: ReturnType<typeof compileParameterExclusions>["bindings"];
|
|
9
|
+
readonly parameterNames: readonly string[];
|
|
10
|
+
readonly parameters: readonly ParameterDescription[];
|
|
11
|
+
};
|
|
12
|
+
export declare function compileHttpBinding(endpoint: EntryTreeEndpoint, binding: HttpEntryBinding, location: string, issues: string[]): CompiledHttpEndpoint;
|
|
13
|
+
export declare function isHttpEntryBinding(value: unknown): value is HttpEntryBinding;
|
|
14
|
+
export declare function isHttpSegment(value: unknown): value is HttpSegment;
|
|
15
|
+
//# sourceMappingURL=http-compiler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-compiler.d.ts","sourceRoot":"","sources":["../src/http-compiler.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAG3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAIpD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,iBAAiB,EAAE,UAAU,CACpC,OAAO,0BAA0B,CAClC,CAAC,UAAU,CAAC,CAAC;IACd,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACtD,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAAE,GACf,oBAAoB,CAyBtB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { compileParameterExclusions, PARAMETER_BINDING_ISSUE_KIND, } from "./binding-compiler.js";
|
|
2
|
+
import { describeEntry } from "./introspection.js";
|
|
3
|
+
import { ownedArray, ownedRecord } from "./owned.js";
|
|
4
|
+
import { isChannelPayload, PAYLOAD_KIND } from "./tree-internal.js";
|
|
5
|
+
export function compileHttpBinding(endpoint, binding, location, issues) {
|
|
6
|
+
const compilation = compileParameterExclusions(endpoint.entry, binding.exclude);
|
|
7
|
+
issues.push(...compilation.issues.map((issue) => formatHttpParameterBindingIssue(location, issue)));
|
|
8
|
+
const parameterNames = compilation.bindings.map((compiled) => compiled.parameter.name);
|
|
9
|
+
const exposed = new Set(parameterNames);
|
|
10
|
+
return ownedRecord({
|
|
11
|
+
endpoint,
|
|
12
|
+
binding,
|
|
13
|
+
parameterBindings: compilation.bindings,
|
|
14
|
+
parameterNames: ownedArray(parameterNames),
|
|
15
|
+
parameters: ownedArray(describeEntry(endpoint.entry).parameters.filter((parameter) => exposed.has(parameter.name))),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function isHttpEntryBinding(value) {
|
|
19
|
+
return isChannelPayload(PAYLOAD_KIND.httpBinding, value);
|
|
20
|
+
}
|
|
21
|
+
export function isHttpSegment(value) {
|
|
22
|
+
return isChannelPayload(PAYLOAD_KIND.httpSegment, value);
|
|
23
|
+
}
|
|
24
|
+
function formatHttpParameterBindingIssue(location, issue) {
|
|
25
|
+
switch (issue.kind) {
|
|
26
|
+
case PARAMETER_BINDING_ISSUE_KIND.duplicate:
|
|
27
|
+
return `${location}: duplicate exposed HTTP parameter ${issue.parameterName}`;
|
|
28
|
+
case PARAMETER_BINDING_ISSUE_KIND.duplicateExclusion:
|
|
29
|
+
return `${location}: duplicate HTTP exclusion for parameter ${issue.parameterName}`;
|
|
30
|
+
case PARAMETER_BINDING_ISSUE_KIND.nonexistentExclusion:
|
|
31
|
+
return `${location}: HTTP binding excludes nonexistent parameter ${issue.parameterName}`;
|
|
32
|
+
case PARAMETER_BINDING_ISSUE_KIND.nonexistent:
|
|
33
|
+
return `${location}: HTTP exposes nonexistent parameter ${issue.parameterName}`;
|
|
34
|
+
case PARAMETER_BINDING_ISSUE_KIND.requiredUnreachable:
|
|
35
|
+
return `${location}: required parameter ${issue.parameterName} is unreachable from HTTP`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ParameterDescription } from "./introspection.js";
|
|
2
|
+
import type { ChannelSegmentOverride, EntryChannelBinding, RawValueMap } from "./model.js";
|
|
3
|
+
import type { InputPlan, Registration } from "./registration.js";
|
|
4
|
+
import { CHANNEL } from "./values.js";
|
|
5
|
+
export declare const HTTP_METHOD: {
|
|
6
|
+
readonly get: "GET";
|
|
7
|
+
readonly post: "POST";
|
|
8
|
+
};
|
|
9
|
+
export type HttpMethod = string;
|
|
10
|
+
export type HttpEntryBinding<Method extends string = string, Excluded extends readonly string[] = readonly string[]> = {
|
|
11
|
+
readonly method: Method;
|
|
12
|
+
readonly exclude: Excluded;
|
|
13
|
+
};
|
|
14
|
+
export type HttpSegment = {
|
|
15
|
+
readonly segment: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function bindHttpEntry<const Method extends string = typeof HTTP_METHOD.post, const Excluded extends readonly string[] = readonly []>(input?: {
|
|
18
|
+
readonly method?: Method;
|
|
19
|
+
readonly exclude?: Excluded;
|
|
20
|
+
}): EntryChannelBinding<typeof CHANNEL.http, HttpEntryBinding<Method, Excluded>>;
|
|
21
|
+
export declare function overrideHttpSegment(segment: {
|
|
22
|
+
readonly segment: string;
|
|
23
|
+
}): ChannelSegmentOverride<typeof CHANNEL.http, HttpSegment>;
|
|
24
|
+
export type HttpQueryMap = {
|
|
25
|
+
readonly [key: string]: string | readonly string[];
|
|
26
|
+
};
|
|
27
|
+
export type HttpInputPlan<Values extends RawValueMap, Input> = InputPlan<Values, Input> & {
|
|
28
|
+
readonly parseQuery: (raw: HttpQueryMap) => Input;
|
|
29
|
+
};
|
|
30
|
+
export type HttpRegistration<Values extends RawValueMap, Input, Context, Output> = Omit<Registration<Values, Input, Context, Output>, "binding" | "input"> & {
|
|
31
|
+
readonly binding: EntryChannelBinding<typeof CHANNEL.http, HttpEntryBinding>;
|
|
32
|
+
readonly method: string;
|
|
33
|
+
readonly path: string;
|
|
34
|
+
readonly segments: readonly string[];
|
|
35
|
+
readonly parameters: readonly ParameterDescription[];
|
|
36
|
+
readonly input: HttpInputPlan<Values, Input>;
|
|
37
|
+
};
|
|
38
|
+
export interface HttpRouteCollection<Context, Output> {
|
|
39
|
+
readonly length: number;
|
|
40
|
+
readonly forEach: (visit: <Values extends RawValueMap, Input>(item: HttpRegistration<Values, Input, Context, Output>) => void) => void;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=http-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-model.d.ts","sourceRoot":"","sources":["../src/http-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ownedArray, ownedRecord } from "./owned.js";
|
|
2
|
+
import { createChannelSegmentOverride, createEntryChannelBinding, PAYLOAD_KIND, registerChannelPayload, } from "./tree-internal.js";
|
|
3
|
+
import { CHANNEL } from "./values.js";
|
|
4
|
+
export const HTTP_METHOD = {
|
|
5
|
+
get: "GET",
|
|
6
|
+
post: "POST",
|
|
7
|
+
};
|
|
8
|
+
export function bindHttpEntry(input) {
|
|
9
|
+
const payload = ownedRecord({
|
|
10
|
+
method: input?.method ?? HTTP_METHOD.post,
|
|
11
|
+
exclude: ownedArray(input?.exclude ?? []),
|
|
12
|
+
});
|
|
13
|
+
registerChannelPayload(PAYLOAD_KIND.httpBinding, payload);
|
|
14
|
+
return createEntryChannelBinding(CHANNEL.http, payload);
|
|
15
|
+
}
|
|
16
|
+
export function overrideHttpSegment(segment) {
|
|
17
|
+
const payload = ownedRecord({ segment: segment.segment });
|
|
18
|
+
registerChannelPayload(PAYLOAD_KIND.httpSegment, payload);
|
|
19
|
+
return createChannelSegmentOverride(CHANNEL.http, payload);
|
|
20
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HttpRouteCollection } from "./http-model.js";
|
|
2
|
+
import { type ContextFor, type EndpointForChannel, type EndpointsOfSource, type EntryTreeSource, type OutputFor } from "./registration.js";
|
|
3
|
+
import { CHANNEL } from "./values.js";
|
|
4
|
+
type HttpEndpointFor<Source extends EntryTreeSource> = EndpointForChannel<EndpointsOfSource<Source>, typeof CHANNEL.http>;
|
|
5
|
+
export declare function collectHttpRoutes<const Source extends EntryTreeSource>(source: Source): HttpRouteCollection<ContextFor<HttpEndpointFor<Source>>, OutputFor<HttpEndpointFor<Source>>>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=http-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-tree.d.ts","sourceRoot":"","sources":["../src/http-tree.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,mBAAmB,EACpB,MAAM,iBAAiB,CAAC;AASzB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAU3B,OAAO,EACL,OAAO,EAGR,MAAM,aAAa,CAAC;AAErB,KAAK,eAAe,CAAC,MAAM,SAAS,eAAe,IAAI,kBAAkB,CACvE,iBAAiB,CAAC,MAAM,CAAC,EACzB,OAAO,OAAO,CAAC,IAAI,CACpB,CAAC;AAQF,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,MAAM,SAAS,eAAe,EACpE,MAAM,EAAE,MAAM,GACb,mBAAmB,CACpB,UAAU,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EACnC,SAAS,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CACnC,CAgDA"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { compileHttpBinding, isHttpEntryBinding, isHttpSegment, } from "./http-compiler.js";
|
|
2
|
+
import { compileInputPlan } from "./input-compiler.js";
|
|
3
|
+
import { ownedArray, ownedRecord } from "./owned.js";
|
|
4
|
+
import { packRegistrationCollection, } from "./registration.js";
|
|
5
|
+
import { EntryInputError } from "./runtime.js";
|
|
6
|
+
import { EntryTreeDefinitionError, findChannelBinding, formatTreePath, isEntryTreeDefinition, validateEntryTreeStructure, } from "./schema.js";
|
|
7
|
+
import { coerceStringValue } from "./string-coercion.js";
|
|
8
|
+
import { CHANNEL, ENTRY_INPUT_TARGET_KIND, VALUE_SHAPE_KIND, } from "./values.js";
|
|
9
|
+
export function collectHttpRoutes(source) {
|
|
10
|
+
const structuralIssues = validateEntryTreeStructure(source);
|
|
11
|
+
if (structuralIssues.length > 0) {
|
|
12
|
+
throw new EntryTreeDefinitionError(structuralIssues);
|
|
13
|
+
}
|
|
14
|
+
const issues = [];
|
|
15
|
+
const registrations = [];
|
|
16
|
+
const tree = isEntryTreeDefinition(source);
|
|
17
|
+
const root = sourceRoot(source);
|
|
18
|
+
const rootSegments = tree ? [] : [httpSegmentFor(root, [root.name], issues)];
|
|
19
|
+
const pending = [
|
|
20
|
+
{
|
|
21
|
+
node: root,
|
|
22
|
+
logicalPath: [root.name],
|
|
23
|
+
segments: rootSegments,
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
while (pending.length > 0) {
|
|
27
|
+
const frame = pending.pop();
|
|
28
|
+
if (frame === undefined) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
compileNodeRoutes(frame, registrations, issues);
|
|
32
|
+
for (let index = frame.node.children.length - 1; index >= 0; index -= 1) {
|
|
33
|
+
const child = frame.node.children[index];
|
|
34
|
+
if (child !== undefined) {
|
|
35
|
+
const logicalPath = [...frame.logicalPath, child.name];
|
|
36
|
+
pending.push({
|
|
37
|
+
node: child,
|
|
38
|
+
logicalPath,
|
|
39
|
+
segments: [
|
|
40
|
+
...frame.segments,
|
|
41
|
+
httpSegmentFor(child, logicalPath, issues),
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const unique = deduplicateRoutes(registrations, issues);
|
|
48
|
+
if (issues.length > 0) {
|
|
49
|
+
throw new EntryTreeDefinitionError(issues);
|
|
50
|
+
}
|
|
51
|
+
return packRegistrationCollection(unique);
|
|
52
|
+
}
|
|
53
|
+
function compileNodeRoutes(frame, output, issues) {
|
|
54
|
+
const endpoints = [...new Set(frame.node.endpoints)];
|
|
55
|
+
for (const endpoint of endpoints) {
|
|
56
|
+
const channelBinding = findChannelBinding(endpoint, CHANNEL.http);
|
|
57
|
+
if (channelBinding === null) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const location = `${formatTreePath(frame.logicalPath)}#${endpoint.entry.name}`;
|
|
61
|
+
if (!isHttpChannelBinding(channelBinding)) {
|
|
62
|
+
issues.push(`${location}: invalid HTTP endpoint binding`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const method = channelBinding.binding.method;
|
|
66
|
+
if (method.length === 0 || /\s/u.test(method)) {
|
|
67
|
+
issues.push(`${location}: HTTP method must be non-empty and contain no whitespace`);
|
|
68
|
+
}
|
|
69
|
+
const compiled = compileHttpBinding(endpoint, channelBinding.binding, location, issues);
|
|
70
|
+
output.push(createHttpRegistration(endpoint, channelBinding, compiled, frame.segments));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function createHttpRegistration(endpoint, channelBinding, compiled, segments) {
|
|
74
|
+
const input = compileInputPlan({
|
|
75
|
+
entry: endpoint.entry,
|
|
76
|
+
bindings: compiled.parameterBindings,
|
|
77
|
+
});
|
|
78
|
+
const parseQuery = (raw) => input.parseCoerced(raw, coerceHttpQueryValue);
|
|
79
|
+
return ownedRecord({
|
|
80
|
+
endpoint,
|
|
81
|
+
entry: endpoint.entry,
|
|
82
|
+
binding: channelBinding,
|
|
83
|
+
method: compiled.binding.method,
|
|
84
|
+
path: `/${segments.join("/")}`,
|
|
85
|
+
segments: ownedArray(segments),
|
|
86
|
+
parameters: compiled.parameters,
|
|
87
|
+
input: ownedRecord({
|
|
88
|
+
schema: input.schema,
|
|
89
|
+
parse: input.parse,
|
|
90
|
+
project: input.project,
|
|
91
|
+
parseQuery,
|
|
92
|
+
}),
|
|
93
|
+
handler: endpoint.handler,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function coerceHttpQueryValue(parameter, raw) {
|
|
97
|
+
if (parameter.codec.shape.kind === VALUE_SHAPE_KIND.array) {
|
|
98
|
+
if (!isHttpQueryValue(raw)) {
|
|
99
|
+
throw parameterInputError(parameter.name, "HTTP query value must be a string or string array");
|
|
100
|
+
}
|
|
101
|
+
return coerceStringValue(parameter.codec.shape, raw);
|
|
102
|
+
}
|
|
103
|
+
const scalar = scalarQueryValue(parameter.name, raw);
|
|
104
|
+
return coerceStringValue(parameter.codec.shape, scalar);
|
|
105
|
+
}
|
|
106
|
+
function scalarQueryValue(parameterName, raw) {
|
|
107
|
+
if (typeof raw === "string") {
|
|
108
|
+
return raw;
|
|
109
|
+
}
|
|
110
|
+
if (!Array.isArray(raw) || !raw.every((item) => typeof item === "string")) {
|
|
111
|
+
throw parameterInputError(parameterName, "HTTP query scalar must be a string or one-element array");
|
|
112
|
+
}
|
|
113
|
+
if (raw.length !== 1) {
|
|
114
|
+
throw parameterInputError(parameterName, raw.length === 0
|
|
115
|
+
? "HTTP query scalar must not be an empty array"
|
|
116
|
+
: "Duplicate HTTP query scalar");
|
|
117
|
+
}
|
|
118
|
+
const value = raw[0];
|
|
119
|
+
if (value === undefined) {
|
|
120
|
+
throw parameterInputError(parameterName, "HTTP query scalar is missing");
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
function parameterInputError(parameterName, issue) {
|
|
125
|
+
return new EntryInputError([issue], {
|
|
126
|
+
kind: ENTRY_INPUT_TARGET_KIND.parameter,
|
|
127
|
+
parameterName,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function isHttpQueryValue(raw) {
|
|
131
|
+
return (typeof raw === "string" ||
|
|
132
|
+
(Array.isArray(raw) && raw.every((item) => typeof item === "string")));
|
|
133
|
+
}
|
|
134
|
+
function httpSegmentFor(node, path, issues) {
|
|
135
|
+
const override = node.segments.find((row) => row.channel === CHANNEL.http);
|
|
136
|
+
let segment = node.name;
|
|
137
|
+
if (override !== undefined) {
|
|
138
|
+
if (!isHttpSegment(override.segment)) {
|
|
139
|
+
issues.push(`${formatTreePath(path)}: invalid HTTP segment override`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
segment = override.segment.segment;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (segment.length === 0 ||
|
|
146
|
+
segment.includes("/") ||
|
|
147
|
+
/\s/u.test(segment) ||
|
|
148
|
+
containsControlCharacter(segment)) {
|
|
149
|
+
issues.push(`${formatTreePath(path)}: HTTP segment must be non-empty and contain no slash, whitespace, or control character`);
|
|
150
|
+
}
|
|
151
|
+
return segment;
|
|
152
|
+
}
|
|
153
|
+
function containsControlCharacter(value) {
|
|
154
|
+
for (const character of value) {
|
|
155
|
+
const codePoint = character.codePointAt(0);
|
|
156
|
+
if (codePoint !== undefined && (codePoint <= 31 || codePoint === 127)) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
function deduplicateRoutes(routes, issues) {
|
|
163
|
+
const indexed = new Map();
|
|
164
|
+
for (const route of routes) {
|
|
165
|
+
const key = JSON.stringify([route.method, route.path]);
|
|
166
|
+
const existing = indexed.get(key);
|
|
167
|
+
if (existing === undefined) {
|
|
168
|
+
indexed.set(key, route);
|
|
169
|
+
}
|
|
170
|
+
else if (existing.endpoint !== route.endpoint) {
|
|
171
|
+
issues.push(`HTTP route ${route.method} ${route.path} is owned by different endpoints`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return [...indexed.values()];
|
|
175
|
+
}
|
|
176
|
+
function sourceRoot(source) {
|
|
177
|
+
return isEntryTreeDefinition(source) ? source.root : source;
|
|
178
|
+
}
|
|
179
|
+
function isHttpChannelBinding(binding) {
|
|
180
|
+
return (binding.channel === CHANNEL.http && isHttpEntryBinding(binding.binding));
|
|
181
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/http.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export * from "./codecs.js";
|
|
2
|
-
export
|
|
2
|
+
export { createOutputContract, EntryOutputContractError, requireEntryOutput, verifyEntryOutput, } from "./contract.js";
|
|
3
3
|
export * from "./factory.js";
|
|
4
4
|
export * from "./introspection.js";
|
|
5
5
|
export * from "./model.js";
|
|
6
|
+
export type { CollectionContext, ContextArgs, ContextFor, ContextOf, HandlerView, InputPlan, OutputFor, ReadonlyEndpointMetadata, ReadonlyEntryMetadata, Registration, } from "./registration.js";
|
|
6
7
|
export { EntryInputError, type EntryInputTarget } from "./runtime.js";
|
|
7
8
|
export { EntryTreeDefinitionError } from "./schema.js";
|
|
8
9
|
export * from "./values.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,EACT,SAAS,EACT,wBAAwB,EACxB,qBAAqB,EACrB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACvD,cAAc,aAAa,CAAC"}
|