@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.
Files changed (99) hide show
  1. package/README.md +204 -723
  2. package/dist/binding-compiler.d.ts +12 -2
  3. package/dist/binding-compiler.d.ts.map +1 -1
  4. package/dist/binding-compiler.js +25 -0
  5. package/dist/cli-coercion.d.ts +1 -1
  6. package/dist/cli-coercion.d.ts.map +1 -1
  7. package/dist/cli-coercion.js +14 -29
  8. package/dist/cli-compiler.d.ts +4 -17
  9. package/dist/cli-compiler.d.ts.map +1 -1
  10. package/dist/cli-compiler.js +78 -39
  11. package/dist/cli-help-projection.d.ts.map +1 -1
  12. package/dist/cli-help-projection.js +41 -36
  13. package/dist/cli-model.d.ts +50 -65
  14. package/dist/cli-model.d.ts.map +1 -1
  15. package/dist/cli-model.js +26 -34
  16. package/dist/cli-tree.d.ts +6 -3
  17. package/dist/cli-tree.d.ts.map +1 -1
  18. package/dist/cli-tree.js +161 -387
  19. package/dist/cli.d.ts +1 -5
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +1 -5
  22. package/dist/codecs.d.ts +20 -18
  23. package/dist/codecs.d.ts.map +1 -1
  24. package/dist/codecs.js +21 -31
  25. package/dist/contract.d.ts.map +1 -1
  26. package/dist/contract.js +8 -3
  27. package/dist/definition-error.d.ts +5 -0
  28. package/dist/definition-error.d.ts.map +1 -0
  29. package/dist/definition-error.js +8 -0
  30. package/dist/factory.d.ts +25 -11
  31. package/dist/factory.d.ts.map +1 -1
  32. package/dist/factory.js +28 -18
  33. package/dist/http-compiler.d.ts +15 -0
  34. package/dist/http-compiler.d.ts.map +1 -0
  35. package/dist/http-compiler.js +37 -0
  36. package/dist/http-model.d.ts +42 -0
  37. package/dist/http-model.d.ts.map +1 -0
  38. package/dist/http-model.js +20 -0
  39. package/dist/http-tree.d.ts +7 -0
  40. package/dist/http-tree.d.ts.map +1 -0
  41. package/dist/http-tree.js +181 -0
  42. package/dist/http.d.ts +3 -0
  43. package/dist/http.d.ts.map +1 -0
  44. package/dist/http.js +2 -0
  45. package/dist/index.d.ts +2 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1 -1
  48. package/dist/input-compiler.d.ts +13 -0
  49. package/dist/input-compiler.d.ts.map +1 -0
  50. package/dist/input-compiler.js +122 -0
  51. package/dist/introspection.d.ts +1 -1
  52. package/dist/introspection.d.ts.map +1 -1
  53. package/dist/introspection.js +30 -16
  54. package/dist/mcp.d.ts +25 -24
  55. package/dist/mcp.d.ts.map +1 -1
  56. package/dist/mcp.js +87 -65
  57. package/dist/model.d.ts +44 -42
  58. package/dist/model.d.ts.map +1 -1
  59. package/dist/model.js +44 -9
  60. package/dist/owned.d.ts +12 -0
  61. package/dist/owned.d.ts.map +1 -0
  62. package/dist/owned.js +61 -0
  63. package/dist/registration.d.ts +41 -0
  64. package/dist/registration.d.ts.map +1 -0
  65. package/dist/registration.js +14 -0
  66. package/dist/runtime.d.ts +2 -35
  67. package/dist/runtime.d.ts.map +1 -1
  68. package/dist/runtime.js +2 -127
  69. package/dist/schema.d.ts +11 -8
  70. package/dist/schema.d.ts.map +1 -1
  71. package/dist/schema.js +110 -47
  72. package/dist/string-coercion.d.ts +4 -0
  73. package/dist/string-coercion.d.ts.map +1 -0
  74. package/dist/string-coercion.js +24 -0
  75. package/dist/tree-internal.d.ts +4 -2
  76. package/dist/tree-internal.d.ts.map +1 -1
  77. package/dist/tree-internal.js +5 -2
  78. package/dist/values.d.ts +1 -6
  79. package/dist/values.d.ts.map +1 -1
  80. package/dist/values.js +1 -6
  81. package/examples/README.md +28 -0
  82. package/examples/book-search.ts +151 -0
  83. package/examples/greeting.ts +193 -0
  84. package/package.json +9 -4
  85. package/dist/cli-application.d.ts +0 -40
  86. package/dist/cli-application.d.ts.map +0 -1
  87. package/dist/cli-application.js +0 -90
  88. package/dist/cli-executor.d.ts +0 -25
  89. package/dist/cli-executor.d.ts.map +0 -1
  90. package/dist/cli-executor.js +0 -44
  91. package/dist/cli-presentation.d.ts +0 -38
  92. package/dist/cli-presentation.d.ts.map +0 -1
  93. package/dist/cli-presentation.js +0 -301
  94. package/dist/cli-result-envelope.d.ts +0 -104
  95. package/dist/cli-result-envelope.d.ts.map +0 -1
  96. package/dist/cli-result-envelope.js +0 -137
  97. package/dist/cli-schema.d.ts +0 -4
  98. package/dist/cli-schema.d.ts.map +0 -1
  99. package/dist/cli-schema.js +0 -7
package/dist/schema.d.ts CHANGED
@@ -1,14 +1,17 @@
1
1
  import type { EntryChannelBinding, EntryTreeDefinition, EntryTreeEndpoint, EntryTreeNode } from "./model.js";
2
- export declare class EntryTreeDefinitionError extends Error {
3
- readonly issues: readonly string[];
4
- constructor(issues: readonly string[]);
5
- }
2
+ import type { EntryTreeSource } from "./registration.js";
3
+ export { EntryTreeDefinitionError } from "./definition-error.js";
6
4
  export interface EntryTreeVisitor {
7
- visitNode(node: EntryTreeNode, path: readonly string[]): void;
8
- visitEndpoint(endpoint: EntryTreeEndpoint, node: EntryTreeNode, path: readonly string[]): void;
5
+ readonly visitNode: (node: EntryTreeNode, path: readonly string[], root: boolean) => void;
6
+ readonly visitEndpoint: (endpoint: EntryTreeEndpoint, node: EntryTreeNode, path: readonly string[], root: boolean) => void;
9
7
  }
10
- export declare function visitEntryTree(tree: EntryTreeDefinition, visitor: EntryTreeVisitor): void;
11
- export declare function validateEntryTreeStructure(tree: EntryTreeDefinition): readonly string[];
8
+ export declare function isEntryTreeDefinition(source: EntryTreeSource): source is EntryTreeDefinition;
9
+ export declare function visitEntryTreeSource(source: EntryTreeSource, visitor: EntryTreeVisitor): void;
10
+ export declare function visitEntryTree(tree: EntryTreeDefinition, visitor: Omit<EntryTreeVisitor, "visitNode" | "visitEndpoint"> & {
11
+ readonly visitNode: (node: EntryTreeNode, path: readonly string[]) => void;
12
+ readonly visitEndpoint: (endpoint: EntryTreeEndpoint, node: EntryTreeNode, path: readonly string[]) => void;
13
+ }): void;
14
+ export declare function validateEntryTreeStructure(source: EntryTreeSource): readonly string[];
12
15
  export declare function findChannelBinding(endpoint: EntryTreeEndpoint, channel: EntryChannelBinding["channel"]): EntryChannelBinding | null;
13
16
  export declare function formatTreePath(path: readonly string[]): string;
14
17
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,qBAAa,wBAAyB,SAAQ,KAAK;IACrC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE;gBAAzB,MAAM,EAAE,SAAS,MAAM,EAAE;CAI/C;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAC9D,aAAa,CACX,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,IAAI,CAAC;CACT;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAsBN;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,mBAAmB,GACxB,SAAS,MAAM,EAAE,CAyCnB;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,GACtC,mBAAmB,GAAG,IAAI,CAI5B;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAE9D"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,CAClB,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,OAAO,KACV,IAAI,CAAC;IACV,QAAQ,CAAC,aAAa,EAAE,CACtB,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,OAAO,KACV,IAAI,CAAC;CACX;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,eAAe,GACtB,MAAM,IAAI,mBAAmB,CAE/B;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,gBAAgB,GACxB,IAAI,CA8BN;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,WAAW,GAAG,eAAe,CAAC,GAAG;IAC/D,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3E,QAAQ,CAAC,aAAa,EAAE,CACtB,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,SAAS,MAAM,EAAE,KACpB,IAAI,CAAC;CACX,GACA,IAAI,CAON;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,eAAe,GACtB,SAAS,MAAM,EAAE,CA4CnB;AAsED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,GACtC,mBAAmB,GAAG,IAAI,CAI5B;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAE9D"}
package/dist/schema.js CHANGED
@@ -1,67 +1,130 @@
1
- export class EntryTreeDefinitionError extends Error {
2
- issues;
3
- constructor(issues) {
4
- super(`Invalid entry tree definition:\n${issues.join("\n")}`);
5
- this.issues = issues;
6
- this.name = EntryTreeDefinitionError.name;
7
- }
1
+ export { EntryTreeDefinitionError } from "./definition-error.js";
2
+ export function isEntryTreeDefinition(source) {
3
+ return Object.hasOwn(source, "root");
8
4
  }
9
- export function visitEntryTree(tree, visitor) {
10
- const stack = [{ node: tree.root, path: [] }];
5
+ export function visitEntryTreeSource(source, visitor) {
6
+ const tree = isEntryTreeDefinition(source);
7
+ const root = tree ? source.root : source;
8
+ const stack = [
9
+ {
10
+ node: root,
11
+ path: tree ? [] : [root.name],
12
+ root: true,
13
+ },
14
+ ];
11
15
  while (stack.length > 0) {
12
16
  const current = stack.pop();
13
- if (!current) {
17
+ if (current === undefined) {
14
18
  continue;
15
19
  }
16
- const path = [...current.path, current.node.name];
17
- visitor.visitNode(current.node, path);
20
+ visitor.visitNode(current.node, current.path, current.root);
18
21
  for (const endpoint of current.node.endpoints) {
19
- visitor.visitEndpoint(endpoint, current.node, path);
22
+ visitor.visitEndpoint(endpoint, current.node, current.path, current.root);
20
23
  }
21
24
  for (let index = current.node.children.length - 1; index >= 0; index -= 1) {
22
25
  const child = current.node.children[index];
23
- if (child) {
24
- stack.push({ node: child, path });
26
+ if (child !== undefined) {
27
+ stack.push({
28
+ node: child,
29
+ path: [...current.path, child.name],
30
+ root: false,
31
+ });
25
32
  }
26
33
  }
27
34
  }
28
35
  }
29
- export function validateEntryTreeStructure(tree) {
36
+ export function visitEntryTree(tree, visitor) {
37
+ visitEntryTreeSource(tree, {
38
+ visitNode: (node, path) => visitor.visitNode(node, [tree.root.name, ...path]),
39
+ visitEndpoint: (endpoint, node, path) => visitor.visitEndpoint(endpoint, node, [tree.root.name, ...path]),
40
+ });
41
+ }
42
+ export function validateEntryTreeStructure(source) {
43
+ const root = isEntryTreeDefinition(source) ? source.root : source;
30
44
  const issues = [];
31
- visitEntryTree(tree, {
32
- visitNode(node, path) {
33
- const location = formatTreePath(path);
34
- if (node.name.length === 0) {
35
- issues.push(`${location}: node name must not be empty`);
36
- }
37
- const childNames = new Set();
38
- for (const child of node.children) {
39
- if (childNames.has(child.name)) {
40
- issues.push(`${location}: duplicate child name ${JSON.stringify(child.name)}`);
41
- }
42
- childNames.add(child.name);
43
- }
44
- const overrideChannels = new Set();
45
- for (const override of node.segments) {
46
- if (overrideChannels.has(override.channel)) {
47
- issues.push(`${location}: duplicate ${override.channel} segment override`);
48
- }
49
- overrideChannels.add(override.channel);
50
- }
51
- },
52
- visitEndpoint(endpoint, _node, path) {
53
- const location = `${formatTreePath(path)}#${endpoint.entry.name}`;
54
- const channels = new Set();
55
- for (const channelBinding of endpoint.channels) {
56
- if (channels.has(channelBinding.channel)) {
57
- issues.push(`${location}: duplicate ${channelBinding.channel} channel binding`);
58
- }
59
- channels.add(channelBinding.channel);
45
+ const placements = new Map();
46
+ const active = new Set();
47
+ const stack = [
48
+ { kind: STRUCTURE_FRAME_KIND.enter, node: root, parent: null },
49
+ ];
50
+ while (stack.length > 0) {
51
+ const frame = stack.pop();
52
+ if (frame === undefined) {
53
+ continue;
54
+ }
55
+ if (frame.kind === STRUCTURE_FRAME_KIND.exit) {
56
+ active.delete(frame.node);
57
+ continue;
58
+ }
59
+ const placement = { node: frame.node, parent: frame.parent };
60
+ const firstPlacement = placements.get(frame.node);
61
+ if (firstPlacement !== undefined) {
62
+ const issue = active.has(frame.node)
63
+ ? "TreeNode cycle"
64
+ : "TreeNode is placed more than once";
65
+ issues.push(`${formatPlacement(placement)}: ${issue}; first placement is ${formatPlacement(firstPlacement)}`);
66
+ continue;
67
+ }
68
+ placements.set(frame.node, placement);
69
+ active.add(frame.node);
70
+ validateNode(placement, issues);
71
+ stack.push({ kind: STRUCTURE_FRAME_KIND.exit, node: frame.node });
72
+ for (let index = frame.node.children.length - 1; index >= 0; index -= 1) {
73
+ const child = frame.node.children[index];
74
+ if (child !== undefined) {
75
+ stack.push({
76
+ kind: STRUCTURE_FRAME_KIND.enter,
77
+ node: child,
78
+ parent: placement,
79
+ });
60
80
  }
61
- },
62
- });
81
+ }
82
+ }
63
83
  return issues;
64
84
  }
85
+ const STRUCTURE_FRAME_KIND = {
86
+ enter: "enter",
87
+ exit: "exit",
88
+ };
89
+ function validateNode(placement, issues) {
90
+ const { node } = placement;
91
+ const location = formatPlacement(placement);
92
+ if (node.name.length === 0) {
93
+ issues.push(`${location}: node name must not be empty`);
94
+ }
95
+ const childNames = new Set();
96
+ for (const child of node.children) {
97
+ if (childNames.has(child.name)) {
98
+ issues.push(`${location}: duplicate child name ${JSON.stringify(child.name)}; merge same-name nodes explicitly`);
99
+ }
100
+ childNames.add(child.name);
101
+ }
102
+ const overrideChannels = new Set();
103
+ for (const override of node.segments) {
104
+ if (overrideChannels.has(override.channel)) {
105
+ issues.push(`${location}: duplicate ${override.channel} segment override`);
106
+ }
107
+ overrideChannels.add(override.channel);
108
+ }
109
+ for (const endpoint of node.endpoints) {
110
+ const channels = new Set();
111
+ for (const channelBinding of endpoint.channels) {
112
+ if (channels.has(channelBinding.channel)) {
113
+ issues.push(`${location}#${endpoint.entry.name}: duplicate ${channelBinding.channel} channel binding`);
114
+ }
115
+ channels.add(channelBinding.channel);
116
+ }
117
+ }
118
+ }
119
+ function formatPlacement(placement) {
120
+ const path = [];
121
+ let current = placement;
122
+ while (current !== null) {
123
+ path.push(current.node.name);
124
+ current = current.parent;
125
+ }
126
+ return formatTreePath(path.reverse());
127
+ }
65
128
  export function findChannelBinding(endpoint, channel) {
66
129
  return (endpoint.channels.find((candidate) => candidate.channel === channel) ?? null);
67
130
  }
@@ -0,0 +1,4 @@
1
+ import type { JsonValue } from "@alpacakit/core";
2
+ import type { ValueShape } from "./model.js";
3
+ export declare function coerceStringValue(shape: ValueShape, rawValue: unknown): JsonValue;
4
+ //# sourceMappingURL=string-coercion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string-coercion.d.ts","sourceRoot":"","sources":["../src/string-coercion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,OAAO,GAChB,SAAS,CAMX"}
@@ -0,0 +1,24 @@
1
+ import { VALUE_SHAPE_KIND } from "./values.js";
2
+ export function coerceStringValue(shape, rawValue) {
3
+ if (shape.kind === VALUE_SHAPE_KIND.array) {
4
+ const values = Array.isArray(rawValue) ? rawValue : [rawValue];
5
+ return values.map((value) => coerceStringScalarValue(shape.element, value));
6
+ }
7
+ return coerceStringScalarValue(shape, rawValue);
8
+ }
9
+ function coerceStringScalarValue(shape, value) {
10
+ if (shape.kind === VALUE_SHAPE_KIND.boolean) {
11
+ if (value === "true") {
12
+ return true;
13
+ }
14
+ if (value === "false") {
15
+ return false;
16
+ }
17
+ }
18
+ if (shape.kind === VALUE_SHAPE_KIND.positiveInteger &&
19
+ typeof value === "string" &&
20
+ /^[+-]?\d+$/.test(value)) {
21
+ return Number(value);
22
+ }
23
+ return value;
24
+ }
@@ -3,12 +3,14 @@ import type { Channel } from "./values.js";
3
3
  export declare const PAYLOAD_KIND: {
4
4
  readonly cliBinding: "cli_binding";
5
5
  readonly cliSegment: "cli_segment";
6
+ readonly httpBinding: "http_binding";
7
+ readonly httpSegment: "http_segment";
6
8
  readonly mcpBinding: "mcp_binding";
7
9
  };
8
10
  type PayloadKind = (typeof PAYLOAD_KIND)[keyof typeof PAYLOAD_KIND];
9
11
  export declare function registerChannelPayload(kind: PayloadKind, payload: object): void;
10
12
  export declare function isChannelPayload(kind: PayloadKind, value: unknown): boolean;
11
- export declare function createChannelSegmentOverride(channel: Channel, segment: unknown): ChannelSegmentOverride;
12
- export declare function createEntryChannelBinding<const ChannelName extends Channel, const ParameterName extends string>(channel: ChannelName, binding: unknown): EntryChannelBinding<ChannelName, ParameterName>;
13
+ export declare function createChannelSegmentOverride<const ChannelName extends Channel, Segment>(channel: ChannelName, segment: Segment): ChannelSegmentOverride<ChannelName, Segment>;
14
+ export declare function createEntryChannelBinding<const ChannelName extends Channel, Binding>(channel: ChannelName, binding: Binding): EntryChannelBinding<ChannelName, Binding>;
13
15
  export {};
14
16
  //# sourceMappingURL=tree-internal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree-internal.d.ts","sourceRoot":"","sources":["../src/tree-internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC9E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,eAAO,MAAM,YAAY;;;;CAIf,CAAC;AAEX,KAAK,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAIpE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,MAAM,GACd,IAAI,CAEN;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAM3E;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,GACf,sBAAsB,CAExB;AAED,wBAAgB,yBAAyB,CACvC,KAAK,CAAC,WAAW,SAAS,OAAO,EACjC,KAAK,CAAC,aAAa,SAAS,MAAM,EAElC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,OAAO,GACf,mBAAmB,CAAC,WAAW,EAAE,aAAa,CAAC,CAKjD"}
1
+ {"version":3,"file":"tree-internal.d.ts","sourceRoot":"","sources":["../src/tree-internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE9E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAEX,KAAK,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAIpE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,MAAM,GACd,IAAI,CAEN;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAM3E;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,CAAC,WAAW,SAAS,OAAO,EACjC,OAAO,EAEP,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,OAAO,GACf,sBAAsB,CAAC,WAAW,EAAE,OAAO,CAAC,CAK9C;AAED,wBAAgB,yBAAyB,CACvC,KAAK,CAAC,WAAW,SAAS,OAAO,EACjC,OAAO,EAEP,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,OAAO,GACf,mBAAmB,CAAC,WAAW,EAAE,OAAO,CAAC,CAK3C"}
@@ -1,6 +1,9 @@
1
+ import { ownedRecord } from "./owned.js";
1
2
  export const PAYLOAD_KIND = {
2
3
  cliBinding: "cli_binding",
3
4
  cliSegment: "cli_segment",
5
+ httpBinding: "http_binding",
6
+ httpSegment: "http_segment",
4
7
  mcpBinding: "mcp_binding",
5
8
  };
6
9
  const REGISTERED_PAYLOADS = new WeakMap();
@@ -13,8 +16,8 @@ export function isChannelPayload(kind, value) {
13
16
  REGISTERED_PAYLOADS.get(value) === kind);
14
17
  }
15
18
  export function createChannelSegmentOverride(channel, segment) {
16
- return { channel, segment };
19
+ return ownedRecord({ channel, segment });
17
20
  }
18
21
  export function createEntryChannelBinding(channel, binding) {
19
- return { channel, binding };
22
+ return ownedRecord({ channel, binding });
20
23
  }
package/dist/values.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export declare const CHANNEL: {
2
2
  readonly cli: "cli";
3
+ readonly http: "http";
3
4
  readonly mcp: "mcp";
4
5
  };
5
6
  export type Channel = (typeof CHANNEL)[keyof typeof CHANNEL];
@@ -15,12 +16,6 @@ export declare const VALUE_SHAPE_KIND: {
15
16
  readonly stringEnum: "string_enum";
16
17
  readonly array: "array";
17
18
  };
18
- export declare const ENTRY_DECODE_RESULT_KIND: {
19
- readonly resolved: "resolved";
20
- readonly invalidInput: "invalid_input";
21
- readonly invalidParameter: "invalid_parameter";
22
- readonly internalError: "internal_error";
23
- };
24
19
  export declare const ENTRY_INPUT_TARGET_KIND: {
25
20
  readonly entry: "entry";
26
21
  readonly parameter: "parameter";
@@ -1 +1 @@
1
- {"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../src/values.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO;;;CAGV,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;AAE7D,eAAO,MAAM,0BAA0B;;;;CAI7B,CAAC;AAEX,eAAO,MAAM,gBAAgB;;;;;;CAMnB,CAAC;AAEX,eAAO,MAAM,wBAAwB;;;;;CAK3B,CAAC;AAEX,eAAO,MAAM,uBAAuB;;;CAG1B,CAAC;AAEX,eAAO,MAAM,6BAA6B;;;CAGhC,CAAC"}
1
+ {"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../src/values.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO;;;;CAIV,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;AAE7D,eAAO,MAAM,0BAA0B;;;;CAI7B,CAAC;AAEX,eAAO,MAAM,gBAAgB;;;;;;CAMnB,CAAC;AAEX,eAAO,MAAM,uBAAuB;;;CAG1B,CAAC;AAEX,eAAO,MAAM,6BAA6B;;;CAGhC,CAAC"}
package/dist/values.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export const CHANNEL = {
2
2
  cli: "cli",
3
+ http: "http",
3
4
  mcp: "mcp",
4
5
  };
5
6
  export const PARAMETER_REQUIREMENT_KIND = {
@@ -14,12 +15,6 @@ export const VALUE_SHAPE_KIND = {
14
15
  stringEnum: "string_enum",
15
16
  array: "array",
16
17
  };
17
- export const ENTRY_DECODE_RESULT_KIND = {
18
- resolved: "resolved",
19
- invalidInput: "invalid_input",
20
- invalidParameter: "invalid_parameter",
21
- internalError: "internal_error",
22
- };
23
18
  export const ENTRY_INPUT_TARGET_KIND = {
24
19
  entry: "entry",
25
20
  parameter: "parameter",
@@ -0,0 +1,28 @@
1
+ # External-adapter examples
2
+
3
+ [`greeting.ts`](./greeting.ts) is the canonical copy-ready Hono, MCP SDK, and
4
+ Commander example. The packed-distribution verifier copies this exact shipped
5
+ file, type-checks it, and invokes all three host paths so the documentation and
6
+ executable fixture cannot drift apart.
7
+
8
+ ## Book search
9
+
10
+ [`book-search.ts`](./book-search.ts) defines one Entry, one shared handler, one
11
+ Endpoint, and one Tree. `collectCliCommands`, `collectHttpRoutes`, and
12
+ `collectMcpTools` expose registration views for an application-owned parser,
13
+ router, or SDK.
14
+
15
+ The example deliberately does not parse argv, start a server, open an MCP
16
+ transport, choose response envelopes, or manage process exit. Those jobs belong
17
+ to the host. It does show the two valid input paths:
18
+
19
+ - CLI and HTTP convert their external string forms with `parseOptions` and
20
+ `parseQuery`.
21
+ - An MCP SDK can apply `input.schema` and then call `input.project` exactly
22
+ once. A host without SDK validation can call `input.parse` instead.
23
+
24
+ All three views retain the exact `searchBooks` function reference and require
25
+ the host-created `BookSearchContext` at invocation time.
26
+
27
+ Host adapters deliberately live in the consumer: channels never starts a
28
+ server, connects a transport, parses `argv`, or chooses a response envelope.
@@ -0,0 +1,151 @@
1
+ import {
2
+ codecs,
3
+ createOutputContract,
4
+ defineEntry,
5
+ defineEntryEndpoint,
6
+ defineEntryNode,
7
+ defineEntryTree,
8
+ defineParameter,
9
+ optional,
10
+ required,
11
+ verifyEntryOutput,
12
+ } from "@alpacakit/channels";
13
+ import {
14
+ bindCliEntry,
15
+ CLI_ARG_KIND,
16
+ collectCliCommands,
17
+ } from "@alpacakit/channels/cli";
18
+ import { bindHttpEntry, collectHttpRoutes } from "@alpacakit/channels/http";
19
+ import { bindMcpEntry, collectMcpTools } from "@alpacakit/channels/mcp";
20
+ import { z } from "zod";
21
+
22
+ const BookSearchDataSchema = z
23
+ .object({
24
+ books: z.array(
25
+ z.object({ id: z.string(), title: z.string(), author: z.string() }),
26
+ ),
27
+ })
28
+ .readonly();
29
+
30
+ export type BookSearchData = z.output<typeof BookSearchDataSchema>;
31
+ export type Book = BookSearchData["books"][number];
32
+
33
+ export const BookSearchEntry = defineEntry({
34
+ name: "book-search",
35
+ parameters: [
36
+ defineParameter({
37
+ name: "query",
38
+ codec: codecs.string(),
39
+ requirement: required(),
40
+ description: "Text to find in a title or author",
41
+ }),
42
+ defineParameter({
43
+ name: "limit",
44
+ codec: codecs.positiveInteger({ max: 50 }),
45
+ requirement: optional({ default: 10 }),
46
+ description: "Maximum number of books to return",
47
+ }),
48
+ ],
49
+ project: ({ values }) => ({ query: values.query, limit: values.limit }),
50
+ output: createOutputContract(BookSearchDataSchema),
51
+ });
52
+
53
+ export type BookSearchInput = ReturnType<typeof BookSearchEntry.project>;
54
+ export type BookSearchContext = {
55
+ readonly canSearch: boolean;
56
+ readonly findBooks: (query: string) => Promise<readonly Book[]>;
57
+ };
58
+
59
+ export type BookSearchOutcome =
60
+ | { readonly kind: "found"; readonly data: BookSearchData }
61
+ | { readonly kind: "forbidden" };
62
+
63
+ /** The shared business handler; channel collectors retain this exact reference. */
64
+ export async function searchBooks(
65
+ input: BookSearchInput,
66
+ context: BookSearchContext,
67
+ ): Promise<BookSearchOutcome> {
68
+ if (!context.canSearch) {
69
+ return { kind: "forbidden" };
70
+ }
71
+ const books = await context.findBooks(input.query);
72
+ return {
73
+ kind: "found",
74
+ data: verifyEntryOutput(BookSearchEntry, {
75
+ books: books.slice(0, input.limit),
76
+ }),
77
+ };
78
+ }
79
+
80
+ export const BookSearchEndpoint = defineEntryEndpoint(BookSearchEntry, {
81
+ handler: searchBooks,
82
+ channels: [
83
+ bindCliEntry({
84
+ parameters: [
85
+ { kind: CLI_ARG_KIND.positional, parameterName: "query" },
86
+ { kind: CLI_ARG_KIND.option, parameterName: "limit" },
87
+ ],
88
+ }),
89
+ bindMcpEntry({ name: "books_search" }),
90
+ bindHttpEntry({ method: "GET" }),
91
+ ],
92
+ });
93
+
94
+ export const BookSearchTree = defineEntryTree(
95
+ defineEntryNode({
96
+ name: "books",
97
+ description: "Book catalog",
98
+ children: [
99
+ defineEntryNode({
100
+ name: "search",
101
+ description: "Search books",
102
+ endpoints: [BookSearchEndpoint],
103
+ }),
104
+ ],
105
+ }),
106
+ );
107
+
108
+ export const bookSearchCliCommands = collectCliCommands(BookSearchTree);
109
+ export const bookSearchHttpRoutes = collectHttpRoutes(BookSearchTree);
110
+ export const bookSearchMcpTools = collectMcpTools(BookSearchTree);
111
+
112
+ /**
113
+ * A small host-owned invocation demonstration. Real applications register the
114
+ * same views with their CLI parser, HTTP router, and MCP SDK.
115
+ */
116
+ export async function runBookSearchDemo(): Promise<{
117
+ readonly cli: BookSearchOutcome;
118
+ readonly http: BookSearchOutcome;
119
+ readonly mcp: BookSearchOutcome;
120
+ }> {
121
+ const context: BookSearchContext = {
122
+ canSearch: true,
123
+ findBooks: async () => [
124
+ {
125
+ id: "1",
126
+ title: "The Left Hand of Darkness",
127
+ author: "Ursula K. Le Guin",
128
+ },
129
+ { id: "2", title: "Kindred", author: "Octavia E. Butler" },
130
+ ],
131
+ };
132
+ const tasks: Promise<BookSearchOutcome>[] = [];
133
+ bookSearchCliCommands.root.children[0]?.commands.forEach((command) => {
134
+ const input = command.input.parseOptions({ query: "kind", limit: "1" });
135
+ tasks.push(Promise.resolve(command.handler(input, context)));
136
+ });
137
+ bookSearchHttpRoutes.forEach((route) => {
138
+ const input = route.input.parseQuery({ query: "kind", limit: "1" });
139
+ tasks.push(Promise.resolve(route.handler(input, context)));
140
+ });
141
+ bookSearchMcpTools.forEach((tool) => {
142
+ const values = tool.input.schema.parse({ query: "kind", limit: 1 });
143
+ const input = tool.input.project(values);
144
+ tasks.push(Promise.resolve(tool.handler(input, context)));
145
+ });
146
+ const [cli, http, mcp] = await Promise.all(tasks);
147
+ if (cli === undefined || http === undefined || mcp === undefined) {
148
+ throw new Error("Book search adapters were not collected");
149
+ }
150
+ return { cli, http, mcp };
151
+ }