apps 1.0.0-beta.0 → 1.0.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.
@@ -1,198 +0,0 @@
1
- import type { AuthMethod, ParamSpec, Registry } from "../authoring/index";
2
-
3
- type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
4
- readonly [key: string]: JsonValue;
5
- };
6
- type JsonSchemaObject = Readonly<Record<string, JsonValue>>;
7
-
8
- interface CollectedClaim {
9
- readonly origin: string;
10
- readonly reach: readonly string[];
11
- readonly params: Readonly<Record<string, ParamSpec>>;
12
- readonly auth: Readonly<Record<string, AuthMethod>>;
13
- }
14
-
15
- interface CollectedRequirement {
16
- readonly role: string;
17
- readonly service: string;
18
- readonly arity: "one" | "optional" | "many";
19
- readonly grant?: string;
20
- readonly description?: string;
21
- }
22
-
23
- interface CollectedTool {
24
- readonly address: string;
25
- readonly name: string;
26
- readonly description: string;
27
- readonly inputSchema: JsonSchemaObject;
28
- readonly annotations: {
29
- readonly readOnly?: boolean;
30
- readonly destructive?: boolean;
31
- readonly scopes?: readonly string[];
32
- };
33
- readonly requires: readonly CollectedRequirement[];
34
- readonly file: string;
35
- }
36
-
37
- interface CollectedManifest {
38
- readonly app: { readonly key: string; readonly label: string; readonly description: string };
39
- readonly services: readonly CollectedClaim[];
40
- readonly tools: readonly CollectedTool[];
41
- }
42
-
43
- /** Result of compiling one authored input schema during registry projection. */
44
- export type InputSchemaCompilation =
45
- | { readonly value: JsonSchemaObject }
46
- | { readonly problem: string };
47
-
48
- /** Registry projection before the deployment-specific digest implementation. */
49
- export type RegistryCollection =
50
- | { readonly problems: readonly string[] }
51
- | {
52
- readonly problems: readonly [];
53
- readonly manifest: CollectedManifest;
54
- readonly canonical: string;
55
- };
56
-
57
- /**
58
- * Validate and project an executed authoring registry into deterministic data.
59
- *
60
- * This function is deliberately closure-free: the workerd runner injects its
61
- * source into the guest while the local manifest calls the same function
62
- * directly. Schema compilation is supplied because each runtime owns its
63
- * boundary decoder.
64
- */
65
- export const collectRegistry = (
66
- registry: Registry,
67
- compileInputSchema: (input: object, where: string) => InputSchemaCompilation,
68
- ): RegistryCollection => {
69
- const requirementKey = Symbol.for("executor/apps/requirement");
70
- const problems = registry.problems.map((problem) => problem.message);
71
- const app = registry.app;
72
-
73
- const requirementSpecOf = (value: unknown) =>
74
- typeof value === "object" && value !== null && requirementKey in value
75
- ? (value as Record<PropertyKey, unknown>)[requirementKey] as {
76
- readonly service: {
77
- readonly origin: string;
78
- readonly auth?: Readonly<Record<string, unknown>>;
79
- };
80
- readonly arity: "one" | "optional" | "many";
81
- readonly grant?: string;
82
- readonly description?: string;
83
- }
84
- : undefined;
85
-
86
- const canonicalJson = (value: unknown): string => {
87
- const sortKeys = (node: unknown): unknown => {
88
- if (Array.isArray(node)) return node.map(sortKeys);
89
- if (typeof node === "object" && node !== null)
90
- return Object.fromEntries(
91
- Object.entries(node as Record<string, unknown>)
92
- .sort(([left], [right]) => left.localeCompare(right))
93
- .map(([key, child]) => [key, sortKeys(child)]),
94
- );
95
- return node;
96
- };
97
- return JSON.stringify(sortKeys(value));
98
- };
99
-
100
- const claims = new Map<string, CollectedClaim>();
101
- for (const [origin, registered] of registry.services) {
102
- if (registered.file !== "app.ts") continue;
103
- const { decl } = registered;
104
- claims.set(origin, {
105
- origin,
106
- reach: decl.reach ?? [],
107
- params: decl.params ?? {},
108
- auth: decl.auth ?? {},
109
- });
110
- }
111
-
112
- const collectRequirement = (
113
- role: string,
114
- requirement: unknown,
115
- where: string,
116
- ): CollectedRequirement | undefined => {
117
- const spec = requirementSpecOf(requirement);
118
- if (spec === undefined) {
119
- problems.push(`${where}: requirement '${role}' is not a service/requirement value`);
120
- return undefined;
121
- }
122
- const origin = spec.service.origin;
123
- if (!claims.has(origin))
124
- problems.push(`${where}: requirement '${role}' names ${origin}, which app.ts does not declare`);
125
- if (spec.grant !== undefined && !(spec.grant in (spec.service.auth ?? {})))
126
- problems.push(
127
- `${where}: requirement '${role}' pins grant '${spec.grant}', not a auth method on ${origin}`,
128
- );
129
- return {
130
- role,
131
- service: origin,
132
- arity: spec.arity,
133
- ...(spec.grant === undefined ? {} : { grant: spec.grant }),
134
- ...(spec.description === undefined ? {} : { description: spec.description }),
135
- };
136
- };
137
-
138
- if (app === undefined) problems.push("app.ts must call defineApp({...})");
139
- else if (typeof app.description !== "string" || app.description.trim().length === 0)
140
- problems.push("app.ts: app description must not be empty");
141
-
142
- for (const [file, summary] of registry.toolFiles) {
143
- if (summary.definitions > 1 && summary.hasUnnamedTool)
144
- problems.push(`${file}: multi-tool files must name every tool`);
145
- }
146
-
147
- const tools: CollectedTool[] = [];
148
- if (app !== undefined) {
149
- for (const [name, registered] of registry.tools) {
150
- const { spec, file } = registered;
151
- const address = `${app.key}/${name}`;
152
- if (typeof spec.description !== "string" || spec.description.trim().length === 0)
153
- problems.push(`${file}: tool '${name}' description must not be empty`);
154
- const requires = Object.entries(spec.requirements ?? {})
155
- .flatMap(([role, requirement]) => {
156
- const collected = collectRequirement(role, requirement, address);
157
- return collected === undefined ? [] : [collected];
158
- })
159
- .sort((left, right) => left.role.localeCompare(right.role));
160
- let compiled: InputSchemaCompilation;
161
- try {
162
- compiled = compileInputSchema(spec.input, address);
163
- } catch (error) {
164
- compiled = {
165
- problem: `${address}: input schema could not compile to JSON schema (${
166
- error instanceof Error ? error.message : String(error)
167
- })`,
168
- };
169
- }
170
- if ("problem" in compiled) {
171
- problems.push(compiled.problem);
172
- continue;
173
- }
174
- tools.push({
175
- address,
176
- name,
177
- description: spec.description,
178
- inputSchema: compiled.value,
179
- annotations: spec.annotations ?? {},
180
- requires,
181
- file,
182
- });
183
- }
184
- }
185
-
186
- if (problems.length > 0 || app === undefined) return { problems };
187
- const services = [...claims.values()].sort((left, right) =>
188
- left.origin.localeCompare(right.origin),
189
- );
190
- const sortedTools = [...tools].sort((left, right) => left.address.localeCompare(right.address));
191
- const appResult = { key: app.key, label: app.label, description: app.description };
192
- const manifest = { app: appResult, services, tools: sortedTools } satisfies CollectedManifest;
193
- return {
194
- problems: [],
195
- manifest,
196
- canonical: canonicalJson(manifest),
197
- };
198
- };