@modelprofile.com/flexharness 10.16.0 → 10.17.0

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 (38) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.flexharness.d.ts +3 -0
  3. package/dist_ts/classes.flexharness.js +36 -15
  4. package/dist_ts/classes.stores.js +40 -8
  5. package/dist_ts/classes.toolcatalog.d.ts +9 -0
  6. package/dist_ts/classes.toolcatalog.js +98 -8
  7. package/dist_ts/interfaces.d.ts +9 -1
  8. package/dist_ts/interfaces.js +4 -2
  9. package/dist_ts/utils.storerules.d.ts +5 -0
  10. package/dist_ts/utils.storerules.js +10 -1
  11. package/dist_ts_stores_nosqldb/classes.nosqldbstores.js +27 -2
  12. package/dist_ts_stores_nosqldb/classes.recordstore.d.ts +2 -0
  13. package/dist_ts_stores_nosqldb/classes.recordstore.js +7 -1
  14. package/dist_ts_typedrequest/catalog.d.ts +28 -0
  15. package/dist_ts_typedrequest/catalog.js +79 -0
  16. package/dist_ts_typedrequest/describe.d.ts +10 -0
  17. package/dist_ts_typedrequest/describe.js +189 -0
  18. package/dist_ts_typedrequest/index.d.ts +3 -0
  19. package/dist_ts_typedrequest/index.js +4 -0
  20. package/dist_ts_typedrequest/interfaces.d.ts +107 -0
  21. package/dist_ts_typedrequest/interfaces.js +2 -0
  22. package/dist_ts_typedrequest/plugins.d.ts +7 -0
  23. package/dist_ts_typedrequest/plugins.js +11 -0
  24. package/package.json +14 -4
  25. package/ts/00_commitinfo_data.ts +1 -1
  26. package/ts/classes.flexharness.ts +40 -14
  27. package/ts/classes.stores.ts +49 -6
  28. package/ts/classes.toolcatalog.ts +90 -7
  29. package/ts/interfaces.ts +10 -2
  30. package/ts/utils.storerules.ts +15 -0
  31. package/ts_stores_nosqldb/classes.nosqldbstores.ts +29 -0
  32. package/ts_stores_nosqldb/classes.recordstore.ts +8 -0
  33. package/ts_typedrequest/catalog.ts +91 -0
  34. package/ts_typedrequest/describe.ts +211 -0
  35. package/ts_typedrequest/index.ts +3 -0
  36. package/ts_typedrequest/interfaces.ts +124 -0
  37. package/ts_typedrequest/plugins.ts +15 -0
  38. package/ts_typedrequest/readme.md +66 -0
@@ -0,0 +1,189 @@
1
+ import * as plugins from './plugins.js';
2
+ const componentsPrefix = '#/components/schemas/';
3
+ const definitionsPrefix = '#/$defs/';
4
+ const maxSummaryCharacters = 120;
5
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ /** A schema whose references name `#/$defs/<type>` instead of the OpenAPI components. */
7
+ const withDefinitionRefs = (value) => {
8
+ if (Array.isArray(value))
9
+ return value.map(withDefinitionRefs);
10
+ if (!isRecord(value))
11
+ return value;
12
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [
13
+ key,
14
+ key === '$ref' && typeof child === 'string' && child.startsWith(componentsPrefix)
15
+ ? `${definitionsPrefix}${child.slice(componentsPrefix.length)}`
16
+ : withDefinitionRefs(child),
17
+ ]));
18
+ };
19
+ /** The type a `#/$defs/<type>` reference names. */
20
+ const referencedType = (schema) => typeof schema.$ref === 'string' && schema.$ref.startsWith(definitionsPrefix)
21
+ ? schema.$ref.slice(definitionsPrefix.length)
22
+ : undefined;
23
+ /**
24
+ * A request schema without the omitted properties: an object loses them, and a reference to a
25
+ * named type that holds one becomes the type's own schema without them.
26
+ */
27
+ const withoutOmitted = (schema, omit, types) => {
28
+ const target = referencedType(schema);
29
+ if (target !== undefined) {
30
+ const named = types[target];
31
+ return named !== undefined && holdsOmitted(named, omit, types, new Set([target]))
32
+ ? withoutOmitted(named, omit, types)
33
+ : schema;
34
+ }
35
+ const stripped = { ...schema };
36
+ if (isRecord(stripped.properties)) {
37
+ stripped.properties = Object.fromEntries(Object.entries(stripped.properties).filter(([key]) => !omit.has(key)));
38
+ }
39
+ if (Array.isArray(stripped.required)) {
40
+ stripped.required = stripped.required.filter((key) => typeof key !== 'string' || !omit.has(key));
41
+ }
42
+ for (const key of ['allOf', 'anyOf', 'oneOf']) {
43
+ const members = stripped[key];
44
+ if (Array.isArray(members)) {
45
+ stripped[key] = members.map((member) => isRecord(member) ? withoutOmitted(member, omit, types) : member);
46
+ }
47
+ }
48
+ return stripped;
49
+ };
50
+ /** Whether a schema has an omitted property, itself or through the schemas it composes or refers to. */
51
+ const holdsOmitted = (schema, omit, types, seen) => {
52
+ if (isRecord(schema.properties) && Object.keys(schema.properties).some((key) => omit.has(key)))
53
+ return true;
54
+ const target = referencedType(schema);
55
+ if (target !== undefined) {
56
+ if (seen.has(target) || types[target] === undefined)
57
+ return false;
58
+ seen.add(target);
59
+ return holdsOmitted(types[target], omit, types, seen);
60
+ }
61
+ return ['allOf', 'anyOf', 'oneOf'].some((key) => Array.isArray(schema[key])
62
+ && schema[key].some((member) => isRecord(member) && holdsOmitted(member, omit, types, seen)));
63
+ };
64
+ /** Whether a call may leave out every parameter: its schema requires none. */
65
+ const requiresNothing = (schema, types) => {
66
+ const target = referencedType(schema);
67
+ if (target !== undefined)
68
+ return types[target] === undefined || requiresNothing(types[target], types);
69
+ if (Array.isArray(schema.required) && schema.required.length > 0)
70
+ return false;
71
+ return !Array.isArray(schema.allOf)
72
+ || schema.allOf.every((member) => !isRecord(member) || requiresNothing(member, types));
73
+ };
74
+ /** The named types a schema refers to. */
75
+ const referencesIn = (value, found) => {
76
+ if (Array.isArray(value)) {
77
+ for (const entry of value)
78
+ referencesIn(entry, found);
79
+ }
80
+ else if (isRecord(value)) {
81
+ const target = referencedType(value);
82
+ if (target !== undefined)
83
+ found.add(target);
84
+ for (const child of Object.values(value))
85
+ referencesIn(child, found);
86
+ }
87
+ return found;
88
+ };
89
+ /** The first sentence of a contract's JSDoc, its tags left out, within the summary's length. */
90
+ const summaryOf = (documentation, method) => {
91
+ const prose = (documentation ?? '').split(/\n(?=@)/)[0].replace(/\s+/g, ' ').trim();
92
+ const sentence = prose.split(/(?<=[.!?])\s/)[0] ?? '';
93
+ if (!sentence)
94
+ return method;
95
+ return [...sentence].length <= maxSummaryCharacters
96
+ ? sentence
97
+ : `${[...sentence].slice(0, maxSummaryCharacters - 1).join('')}…`;
98
+ };
99
+ /** A JSDoc block of the given lines; `*\/` in them cannot end it early. */
100
+ const docBlock = (lines) => {
101
+ const text = lines.join('\n').replace(/\*\//g, '*\\/');
102
+ return `/**\n${text.split('\n').map((line) => (line ? ` * ${line}` : ' *')).join('\n')}\n */\n`;
103
+ };
104
+ /** The area of a contract when the options name none: its file's base name, an `index` file's folder. */
105
+ const defaultArea = (file) => {
106
+ const parsed = plugins.path.posix.parse(file);
107
+ return parsed.name === 'index' ? plugins.path.posix.basename(parsed.dir) || parsed.name : parsed.name;
108
+ };
109
+ /**
110
+ * Reads a typedrequest API's contracts from their TypeScript sources: each typed method's path
111
+ * (`<area>.<method>`), its summary and JSDoc, its request and response as JSON Schema for
112
+ * validation, and its TypeScript declaration as the contract writes it, for the model to read; and
113
+ * the named types they reach, each with its schema, its declaration, or both. Needs the optional
114
+ * peer `@api.global/typedopenapi`. Run it at build time and keep the JSON, or at startup where the
115
+ * sources are at hand.
116
+ */
117
+ export async function describeTypedRequests(options) {
118
+ let typedopenapi;
119
+ try {
120
+ typedopenapi = await plugins.importTypedOpenApi();
121
+ }
122
+ catch (error) {
123
+ throw new plugins.FlexHarnessValidationError('describeTypedRequests needs the optional peer @api.global/typedopenapi (0.8.0 or later).', { cause: error });
124
+ }
125
+ const omit = [...new Set(options.omit ?? [])].sort();
126
+ const source = {
127
+ tsconfig: options.tsconfig,
128
+ contracts: options.contracts,
129
+ ...(options.contractNamePattern ? { contractNamePattern: options.contractNamePattern } : {}),
130
+ };
131
+ const schemaSet = typedopenapi.generateContractSchemas(source);
132
+ const declarationSet = typedopenapi.generateContractDeclarations({ ...source, omit });
133
+ const schemas = withDefinitionRefs(schemaSet.schemas);
134
+ const declarations = new Map(declarationSet.contracts.map((contract) => [contract.name, contract]));
135
+ const omitted = new Set(omit);
136
+ const paths = new Set();
137
+ const methods = schemaSet.contracts.map((contract) => {
138
+ const declared = declarations.get(contract.name);
139
+ if (!declared) {
140
+ throw new plugins.FlexHarnessValidationError(`@api.global/typedopenapi declared no contract ${contract.name}, whose schemas it generated.`);
141
+ }
142
+ const area = options.area ? options.area({ name: contract.name, method: contract.method, file: declared.file }) : defaultArea(declared.file);
143
+ const name = plugins.FlexToolCatalog.identifierOf(contract.method);
144
+ const base = `${plugins.FlexToolCatalog.identifierOf(area)}.${name}`;
145
+ let path = base;
146
+ for (let suffix = 2; paths.has(path); suffix++)
147
+ path = `${base}_${suffix}`;
148
+ paths.add(path);
149
+ const inputSchema = withoutOmitted(withDefinitionRefs(contract.request), omitted, schemas);
150
+ const docLines = [
151
+ ...(declared.documentation ? [declared.documentation] : []),
152
+ ...(declared.request.documentation ? [`@param params ${declared.request.documentation}`] : []),
153
+ ...(declared.response.documentation ? [`@returns ${declared.response.documentation}`] : []),
154
+ ];
155
+ const functionName = path.slice(path.lastIndexOf('.') + 1);
156
+ const optional = requiresNothing(inputSchema, schemas) ? '?' : '';
157
+ return {
158
+ method: contract.method,
159
+ path,
160
+ summary: summaryOf(declared.documentation, contract.method),
161
+ ...(declared.documentation ? { description: declared.documentation } : {}),
162
+ ...(contract.deprecated ? { deprecated: true } : {}),
163
+ inputSchema,
164
+ outputSchema: withDefinitionRefs(contract.response),
165
+ declaration: `${docLines.length ? docBlock(docLines) : ''}function ${functionName}(params${optional}: ${declared.request.type}): Promise<${declared.response.type}>;`,
166
+ };
167
+ }).sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
168
+ // Only the schemas the methods still reach: an omitted property's types are gone with it.
169
+ const reached = new Set();
170
+ const pending = [...referencesIn(methods.map((method) => [method.inputSchema, method.outputSchema]), new Set())];
171
+ while (pending.length) {
172
+ const name = pending.pop();
173
+ if (reached.has(name) || schemas[name] === undefined)
174
+ continue;
175
+ reached.add(name);
176
+ pending.push(...referencesIn(schemas[name], new Set()));
177
+ }
178
+ const names = [...new Set([...reached, ...Object.keys(declarationSet.types)])].sort();
179
+ return {
180
+ methods,
181
+ types: names.map((name) => ({
182
+ name,
183
+ ...(reached.has(name) ? { schema: schemas[name] } : {}),
184
+ ...(declarationSet.types[name] !== undefined ? { declaration: declarationSet.types[name] } : {}),
185
+ })),
186
+ omit,
187
+ };
188
+ }
189
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVzY3JpYmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c190eXBlZHJlcXVlc3QvZGVzY3JpYmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFReEMsTUFBTSxnQkFBZ0IsR0FBRyx1QkFBdUIsQ0FBQztBQUNqRCxNQUFNLGlCQUFpQixHQUFHLFVBQVUsQ0FBQztBQUNyQyxNQUFNLG9CQUFvQixHQUFHLEdBQUcsQ0FBQztBQUVqQyxNQUFNLFFBQVEsR0FBRyxDQUFDLEtBQWMsRUFBb0MsRUFBRSxDQUNwRSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7QUFFdkUseUZBQXlGO0FBQ3pGLE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxLQUFjLEVBQVcsRUFBRTtJQUNyRCxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUMsR0FBRyxDQUFDLGtCQUFrQixDQUFDLENBQUM7SUFDL0QsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUNuQyxPQUFPLE1BQU0sQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsRUFBRSxFQUFFLENBQUM7UUFDcEUsR0FBRztRQUNILEdBQUcsS0FBSyxNQUFNLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxVQUFVLENBQUMsZ0JBQWdCLENBQUM7WUFDL0UsQ0FBQyxDQUFDLEdBQUcsaUJBQWlCLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsRUFBRTtZQUMvRCxDQUFDLENBQUMsa0JBQWtCLENBQUMsS0FBSyxDQUFDO0tBQzlCLENBQUMsQ0FBQyxDQUFDO0FBQ04sQ0FBQyxDQUFDO0FBRUYsbURBQW1EO0FBQ25ELE1BQU0sY0FBYyxHQUFHLENBQUMsTUFBK0IsRUFBc0IsRUFBRSxDQUM3RSxPQUFPLE1BQU0sQ0FBQyxJQUFJLEtBQUssUUFBUSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDO0lBQzFFLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLENBQUM7SUFDN0MsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUVoQjs7O0dBR0c7QUFDSCxNQUFNLGNBQWMsR0FBRyxDQUNyQixNQUErQixFQUMvQixJQUF5QixFQUN6QixLQUF3RCxFQUMvQixFQUFFO0lBQzNCLE1BQU0sTUFBTSxHQUFHLGNBQWMsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUN0QyxJQUFJLE1BQU0sS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUN6QixNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDNUIsT0FBTyxLQUFLLEtBQUssU0FBUyxJQUFJLFlBQVksQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLEdBQUcsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7WUFDL0UsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLEtBQUssQ0FBQztZQUNwQyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ2IsQ0FBQztJQUNELE1BQU0sUUFBUSxHQUE0QixFQUFFLEdBQUcsTUFBTSxFQUFFLENBQUM7SUFDeEQsSUFBSSxRQUFRLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7UUFDbEMsUUFBUSxDQUFDLFVBQVUsR0FBRyxNQUFNLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbEgsQ0FBQztJQUNELElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztRQUNyQyxRQUFRLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxPQUFPLEdBQUcsS0FBSyxRQUFRLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDbkcsQ0FBQztJQUNELEtBQUssTUFBTSxHQUFHLElBQUksQ0FBQyxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBVSxFQUFFLENBQUM7UUFDdkQsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzlCLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1lBQzNCLFFBQVEsQ0FBQyxHQUFHLENBQUMsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMzRyxDQUFDO0lBQ0gsQ0FBQztJQUNELE9BQU8sUUFBUSxDQUFDO0FBQ2xCLENBQUMsQ0FBQztBQUVGLHdHQUF3RztBQUN4RyxNQUFNLFlBQVksR0FBRyxDQUNuQixNQUErQixFQUMvQixJQUF5QixFQUN6QixLQUF3RCxFQUN4RCxJQUFpQixFQUNSLEVBQUU7SUFDWCxJQUFJLFFBQVEsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDNUcsTUFBTSxNQUFNLEdBQUcsY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ3RDLElBQUksTUFBTSxLQUFLLFNBQVMsRUFBRSxDQUFDO1FBQ3pCLElBQUksSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxLQUFLLENBQUMsTUFBTSxDQUFDLEtBQUssU0FBUztZQUFFLE9BQU8sS0FBSyxDQUFDO1FBQ2xFLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDakIsT0FBTyxZQUFZLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUNELE9BQVEsQ0FBQyxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7V0FDaEYsTUFBTSxDQUFDLEdBQUcsQ0FBZSxDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxJQUFJLFlBQVksQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakgsQ0FBQyxDQUFDO0FBRUYsOEVBQThFO0FBQzlFLE1BQU0sZUFBZSxHQUFHLENBQ3RCLE1BQStCLEVBQy9CLEtBQXdELEVBQy9DLEVBQUU7SUFDWCxNQUFNLE1BQU0sR0FBRyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDdEMsSUFBSSxNQUFNLEtBQUssU0FBUztRQUFFLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLFNBQVMsSUFBSSxlQUFlLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ3RHLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksTUFBTSxDQUFDLFFBQVEsQ0FBQyxNQUFNLEdBQUcsQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQy9FLE9BQU8sQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7V0FDOUIsTUFBTSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxJQUFJLGVBQWUsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztBQUMzRixDQUFDLENBQUM7QUFFRiwwQ0FBMEM7QUFDMUMsTUFBTSxZQUFZLEdBQUcsQ0FBQyxLQUFjLEVBQUUsS0FBa0IsRUFBZSxFQUFFO0lBQ3ZFLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3pCLEtBQUssTUFBTSxLQUFLLElBQUksS0FBSztZQUFFLFlBQVksQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDeEQsQ0FBQztTQUFNLElBQUksUUFBUSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDM0IsTUFBTSxNQUFNLEdBQUcsY0FBYyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3JDLElBQUksTUFBTSxLQUFLLFNBQVM7WUFBRSxLQUFLLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzVDLEtBQUssTUFBTSxLQUFLLElBQUksTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7WUFBRSxZQUFZLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ3ZFLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUMsQ0FBQztBQUVGLGdHQUFnRztBQUNoRyxNQUFNLFNBQVMsR0FBRyxDQUFDLGFBQWlDLEVBQUUsTUFBYyxFQUFVLEVBQUU7SUFDOUUsTUFBTSxLQUFLLEdBQUcsQ0FBQyxhQUFhLElBQUksRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDcEYsTUFBTSxRQUFRLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDdEQsSUFBSSxDQUFDLFFBQVE7UUFBRSxPQUFPLE1BQU0sQ0FBQztJQUM3QixPQUFPLENBQUMsR0FBRyxRQUFRLENBQUMsQ0FBQyxNQUFNLElBQUksb0JBQW9CO1FBQ2pELENBQUMsQ0FBQyxRQUFRO1FBQ1YsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLFFBQVEsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsb0JBQW9CLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUM7QUFDdEUsQ0FBQyxDQUFDO0FBRUYsMkVBQTJFO0FBQzNFLE1BQU0sUUFBUSxHQUFHLENBQUMsS0FBZSxFQUFVLEVBQUU7SUFDM0MsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQ3ZELE9BQU8sUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUM7QUFDbEcsQ0FBQyxDQUFDO0FBRUYseUdBQXlHO0FBQ3pHLE1BQU0sV0FBVyxHQUFHLENBQUMsSUFBWSxFQUFVLEVBQUU7SUFDM0MsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzlDLE9BQU8sTUFBTSxDQUFDLElBQUksS0FBSyxPQUFPLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQztBQUN4RyxDQUFDLENBQUM7QUFFRjs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxxQkFBcUIsQ0FBQyxPQUF3QztJQUNsRixJQUFJLFlBQW9FLENBQUM7SUFDekUsSUFBSSxDQUFDO1FBQ0gsWUFBWSxHQUFHLE1BQU0sT0FBTyxDQUFDLGtCQUFrQixFQUFFLENBQUM7SUFDcEQsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixNQUFNLElBQUksT0FBTyxDQUFDLDBCQUEwQixDQUMxQywwRkFBMEYsRUFDMUYsRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLENBQ2pCLENBQUM7SUFDSixDQUFDO0lBQ0QsTUFBTSxJQUFJLEdBQUcsQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNyRCxNQUFNLE1BQU0sR0FBRztRQUNiLFFBQVEsRUFBRSxPQUFPLENBQUMsUUFBUTtRQUMxQixTQUFTLEVBQUUsT0FBTyxDQUFDLFNBQVM7UUFDNUIsR0FBRyxDQUFDLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLENBQUMsRUFBRSxtQkFBbUIsRUFBRSxPQUFPLENBQUMsbUJBQW1CLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0tBQzdGLENBQUM7SUFDRixNQUFNLFNBQVMsR0FBRyxZQUFZLENBQUMsdUJBQXVCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDL0QsTUFBTSxjQUFjLEdBQUcsWUFBWSxDQUFDLDRCQUE0QixDQUFDLEVBQUUsR0FBRyxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUN0RixNQUFNLE9BQU8sR0FBRyxrQkFBa0IsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUE0QyxDQUFDO0lBQ2pHLE1BQU0sWUFBWSxHQUFHLElBQUksR0FBRyxDQUFDLGNBQWMsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3BHLE1BQU0sT0FBTyxHQUFHLElBQUksR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRTlCLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7SUFDaEMsTUFBTSxPQUFPLEdBQThCLFNBQVMsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7UUFDOUUsTUFBTSxRQUFRLEdBQUcsWUFBWSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDakQsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ2QsTUFBTSxJQUFJLE9BQU8sQ0FBQywwQkFBMEIsQ0FBQyxpREFBaUQsUUFBUSxDQUFDLElBQUksK0JBQStCLENBQUMsQ0FBQztRQUM5SSxDQUFDO1FBQ0QsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxRQUFRLENBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRSxRQUFRLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM3SSxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbkUsTUFBTSxJQUFJLEdBQUcsR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUNyRSxJQUFJLElBQUksR0FBRyxJQUFJLENBQUM7UUFDaEIsS0FBSyxJQUFJLE1BQU0sR0FBRyxDQUFDLEVBQUUsS0FBSyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUU7WUFBRSxJQUFJLEdBQUcsR0FBRyxJQUFJLElBQUksTUFBTSxFQUFFLENBQUM7UUFDM0UsS0FBSyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNoQixNQUFNLFdBQVcsR0FBRyxjQUFjLENBQUMsa0JBQWtCLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBNEIsRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDdEgsTUFBTSxRQUFRLEdBQUc7WUFDZixHQUFHLENBQUMsUUFBUSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUMzRCxHQUFHLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsaUJBQWlCLFFBQVEsQ0FBQyxPQUFPLENBQUMsYUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQzlGLEdBQUcsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxZQUFZLFFBQVEsQ0FBQyxRQUFRLENBQUMsYUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1NBQzVGLENBQUM7UUFDRixNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDM0QsTUFBTSxRQUFRLEdBQUcsZUFBZSxDQUFDLFdBQVcsRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDbEUsT0FBTztZQUNMLE1BQU0sRUFBRSxRQUFRLENBQUMsTUFBTTtZQUN2QixJQUFJO1lBQ0osT0FBTyxFQUFFLFNBQVMsQ0FBQyxRQUFRLENBQUMsYUFBYSxFQUFFLFFBQVEsQ0FBQyxNQUFNLENBQUM7WUFDM0QsR0FBRyxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLEVBQUUsV0FBVyxFQUFFLFFBQVEsQ0FBQyxhQUFhLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQzFFLEdBQUcsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQ3BELFdBQVc7WUFDWCxZQUFZLEVBQUUsa0JBQWtCLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBNEI7WUFDOUUsV0FBVyxFQUFFLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLFlBQVksWUFBWSxVQUFVLFFBQVEsS0FBSyxRQUFRLENBQUMsT0FBTyxDQUFDLElBQUksY0FBYyxRQUFRLENBQUMsUUFBUSxDQUFDLElBQUksSUFBSTtTQUN0SyxDQUFDO0lBQ0osQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUV6RiwwRkFBMEY7SUFDMUYsTUFBTSxPQUFPLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztJQUNsQyxNQUFNLE9BQU8sR0FBRyxDQUFDLEdBQUcsWUFBWSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRSxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUMsRUFBRSxJQUFJLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUNqSCxPQUFPLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUN0QixNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsR0FBRyxFQUFHLENBQUM7UUFDNUIsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxTQUFTO1lBQUUsU0FBUztRQUMvRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2xCLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxZQUFZLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQzFELENBQUM7SUFDRCxNQUFNLEtBQUssR0FBRyxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxHQUFHLE9BQU8sRUFBRSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ3RGLE9BQU87UUFDTCxPQUFPO1FBQ1AsS0FBSyxFQUFFLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDMUIsSUFBSTtZQUNKLEdBQUcsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQ3ZELEdBQUcsQ0FBQyxjQUFjLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxXQUFXLEVBQUUsY0FBYyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7U0FDakcsQ0FBQyxDQUFDO1FBQ0gsSUFBSTtLQUNMLENBQUM7QUFDSixDQUFDIn0=
@@ -0,0 +1,3 @@
1
+ export * from './interfaces.js';
2
+ export { describeTypedRequests } from './describe.js';
3
+ export { FlexTypedRequestError, catalogFromTypedRequests } from './catalog.js';
@@ -0,0 +1,4 @@
1
+ export * from './interfaces.js';
2
+ export { describeTypedRequests } from './describe.js';
3
+ export { FlexTypedRequestError, catalogFromTypedRequests } from './catalog.js';
4
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c190eXBlZHJlcXVlc3QvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxpQkFBaUIsQ0FBQztBQUNoQyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDdEQsT0FBTyxFQUFFLHFCQUFxQixFQUFFLHdCQUF3QixFQUFFLE1BQU0sY0FBYyxDQUFDIn0=
@@ -0,0 +1,107 @@
1
+ import type { IFlexCatalogRunContext, IFlexCatalogType, IFlexToolCatalog, TFlexCatalogCallDecision } from '@modelprofile.com/flexharness';
2
+ /** A JSON Schema of the surface, plain JSON; its `$ref`s name `#/$defs/<type>` of the surface's types. */
3
+ export type TFlexTypedRequestSchema = Record<string, unknown>;
4
+ /** Where to read a typedrequest API's contracts, and what of them the catalog takes. */
5
+ export interface IFlexTypedRequestSurfaceOptions {
6
+ /** The `tsconfig.json` of the project that holds the contracts. */
7
+ tsconfig: string;
8
+ /** The contract files, or a glob of several: `ts_interfaces/requests/*.ts`. */
9
+ contracts: string;
10
+ /** The names of the exports that are contracts (default `/^IReq_/`). */
11
+ contractNamePattern?: RegExp;
12
+ /**
13
+ * Request properties the binding fills from the run instead of the model, such as the caller's
14
+ * identity or a CSRF token: left out of every request's schema and declaration.
15
+ */
16
+ omit?: readonly string[];
17
+ /**
18
+ * The namespace of a contract's method. Default: the base name of the file that declares it
19
+ * (`vouchers` for `requests/vouchers.ts`, the folder's name for an `index.ts`).
20
+ */
21
+ area?(contract: {
22
+ name: string;
23
+ method: string;
24
+ file: string;
25
+ }): string;
26
+ }
27
+ /** One typed method of a surface: how the catalog shows it and validates its calls. */
28
+ export interface IFlexTypedRequestMethod {
29
+ /** The typed method the router answers: `getVoucher`. */
30
+ method: string;
31
+ /** The catalog path the model calls: `vouchers.getVoucher`. */
32
+ path: string;
33
+ /** The first sentence of the contract's JSDoc, or the method. */
34
+ summary: string;
35
+ /** The contract's JSDoc, its tags included. */
36
+ description?: string;
37
+ /** Set by a `@deprecated` tag on the contract. */
38
+ deprecated?: boolean;
39
+ /** The request's schema, without the omitted properties. */
40
+ inputSchema: TFlexTypedRequestSchema;
41
+ /** The response's schema. */
42
+ outputSchema: TFlexTypedRequestSchema;
43
+ /** `function getVoucher(params: …): Promise<…>;` as the contract writes it, after its JSDoc. */
44
+ declaration: string;
45
+ }
46
+ /**
47
+ * A typedrequest API as the catalog takes it, read from its TypeScript sources by
48
+ * `describeTypedRequests()`. Plain JSON, so a build step can write it and a server read it.
49
+ */
50
+ export interface IFlexTypedRequestSurface {
51
+ /** The typed methods, by path. */
52
+ methods: IFlexTypedRequestMethod[];
53
+ /**
54
+ * The named types, by name: each with its JSON Schema where requests and responses refer to it,
55
+ * its TypeScript declaration where declarations name it, or both. A generic is declared once
56
+ * (`IPage`) and its instances have schemas (`IPage_IVoucher`).
57
+ */
58
+ types: IFlexCatalogType[];
59
+ /** The request properties left out of every request, which the binding fills. */
60
+ omit: string[];
61
+ }
62
+ /**
63
+ * A router that answers typed requests in process, such as a `TypedRouter` of
64
+ * `@api.global/typedrequest`.
65
+ */
66
+ export interface IFlexTypedRequestRouter {
67
+ routeAndAddResponse(request: IFlexTypedRequestEnvelope, options?: {
68
+ trustedLocalData?: Record<string, unknown>;
69
+ trustedAbortSignal?: AbortSignal;
70
+ }): Promise<IFlexTypedRequestEnvelope | null>;
71
+ }
72
+ /** A typed request as the router takes it and answers it. */
73
+ export interface IFlexTypedRequestEnvelope {
74
+ method: string;
75
+ request: object;
76
+ response: object;
77
+ correlation?: {
78
+ id: string;
79
+ phase: 'request' | 'response';
80
+ };
81
+ error?: {
82
+ text: string;
83
+ data: any;
84
+ };
85
+ }
86
+ /** How a catalog answers a surface's methods. */
87
+ export interface IFlexTypedRequestCatalogOptions<TScope = unknown> {
88
+ /** The router that answers the methods, in process. */
89
+ router: IFlexTypedRequestRouter;
90
+ /**
91
+ * The request properties the surface leaves out, from the run: `{ identity }` of the user the
92
+ * session acts for. They are merged over the model's parameters.
93
+ */
94
+ request?(method: IFlexTypedRequestMethod, context: IFlexCatalogRunContext<TScope>): Record<string, unknown> | PromiseLike<Record<string, unknown>>;
95
+ /** Server-owned data the handlers read as `localData`, as a transport would hand it. */
96
+ localData?(method: IFlexTypedRequestMethod, context: IFlexCatalogRunContext<TScope>): Record<string, unknown> | PromiseLike<Record<string, unknown>>;
97
+ /** The methods the model may use. Default: every method that is not deprecated. */
98
+ include?(method: IFlexTypedRequestMethod): boolean;
99
+ /** Other names of methods, by typed method: `{ getVoucher: ['Beleg abrufen'] }`. */
100
+ aliases?: Readonly<Record<string, readonly string[]>>;
101
+ /** The words the API's users say for its own words; see `IFlexToolCatalog.vocabulary`. */
102
+ vocabulary?: IFlexToolCatalog['vocabulary'];
103
+ /** Decides each validated call before it runs; see `IFlexCatalogMethod.onCall`. */
104
+ onCall?(method: IFlexTypedRequestMethod, params: Record<string, unknown>, context: IFlexCatalogRunContext<TScope>): TFlexCatalogCallDecision | PromiseLike<TFlexCatalogCallDecision>;
105
+ /** Receives the calls `onCall` proposed; see `IFlexToolCatalog.onProposals`. */
106
+ onProposals?: IFlexToolCatalog<TScope>['onProposals'];
107
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX3R5cGVkcmVxdWVzdC9pbnRlcmZhY2VzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIifQ==
@@ -0,0 +1,7 @@
1
+ import * as crypto from 'node:crypto';
2
+ import * as path from 'node:path';
3
+ export { crypto, path };
4
+ import { FlexHarnessValidationError, FlexToolCatalog } from '@modelprofile.com/flexharness';
5
+ export { FlexHarnessValidationError, FlexToolCatalog };
6
+ /** Loads @api.global/typedopenapi, which reads the contracts from their TypeScript sources. */
7
+ export declare const importTypedOpenApi: () => Promise<typeof import("@api.global/typedopenapi")>;
@@ -0,0 +1,11 @@
1
+ // node native scope
2
+ import * as crypto from 'node:crypto';
3
+ import * as path from 'node:path';
4
+ export { crypto, path };
5
+ // @modelprofile.com scope
6
+ import { FlexHarnessValidationError, FlexToolCatalog } from '@modelprofile.com/flexharness';
7
+ export { FlexHarnessValidationError, FlexToolCatalog };
8
+ // @api.global scope: @api.global/typedopenapi is an optional peer, loaded only to describe a surface
9
+ /** Loads @api.global/typedopenapi, which reads the contracts from their TypeScript sources. */
10
+ export const importTypedOpenApi = async () => await import('@api.global/typedopenapi');
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX3R5cGVkcmVxdWVzdC9wbHVnaW5zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLG9CQUFvQjtBQUNwQixPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUN0QyxPQUFPLEtBQUssSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUVsQyxPQUFPLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDO0FBRXhCLDBCQUEwQjtBQUMxQixPQUFPLEVBQUUsMEJBQTBCLEVBQUUsZUFBZSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFFNUYsT0FBTyxFQUFFLDBCQUEwQixFQUFFLGVBQWUsRUFBRSxDQUFDO0FBRXZELHFHQUFxRztBQUVyRywrRkFBK0Y7QUFDL0YsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsS0FBSyxJQUFJLEVBQUUsQ0FBQyxNQUFNLE1BQU0sQ0FBQywwQkFBMEIsQ0FBQyxDQUFDIn0=
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "Task Venture Capital GmbH",
3
3
  "license": "MIT",
4
4
  "name": "@modelprofile.com/flexharness",
5
- "version": "10.16.0",
5
+ "version": "10.17.0",
6
6
  "type": "module",
7
7
  "description": "Managed agent sessions with tools, MCP, chat, OCR, documents and browser control through focused subpath imports.",
8
8
  "main": "./dist_ts/index.js",
@@ -71,12 +71,16 @@
71
71
  "./stores/nosqldb": {
72
72
  "types": "./dist_ts_stores_nosqldb/index.d.ts",
73
73
  "import": "./dist_ts_stores_nosqldb/index.js"
74
+ },
75
+ "./typedrequest": {
76
+ "types": "./dist_ts_typedrequest/index.d.ts",
77
+ "import": "./dist_ts_typedrequest/index.js"
74
78
  }
75
79
  },
76
80
  "dependencies": {
77
81
  "@cfworker/json-schema": "^4.1.1",
78
- "@modelprofile.com/flexharness-agent": "10.16.0",
79
- "@modelprofile.com/flexharness-models": "10.16.0",
82
+ "@modelprofile.com/flexharness-agent": "10.17.0",
83
+ "@modelprofile.com/flexharness-models": "10.17.0",
80
84
  "@push.rocks/smartrequest": "^5.5.1",
81
85
  "ai": "^7.0.111",
82
86
  "zod": "^4.6.5"
@@ -96,7 +100,8 @@
96
100
  "@design.estate/dees-catalog": "^17.0.0 || ^19.5.0 || ^20.0.0 || ^21.0.0",
97
101
  "quickjs-emscripten-core": "^0.32.0",
98
102
  "@jitl/quickjs-ng-wasmfile-release-sync": "^0.32.0",
99
- "amaro": "^1.2.1"
103
+ "amaro": "^1.2.1",
104
+ "@api.global/typedopenapi": "^0.8.0"
100
105
  },
101
106
  "peerDependenciesMeta": {
102
107
  "@modelcontextprotocol/sdk": {
@@ -143,6 +148,9 @@
143
148
  },
144
149
  "amaro": {
145
150
  "optional": true
151
+ },
152
+ "@api.global/typedopenapi": {
153
+ "optional": true
146
154
  }
147
155
  },
148
156
  "engines": {
@@ -184,6 +192,8 @@
184
192
  "dist_ts_chat_harness",
185
193
  "ts_stores_nosqldb",
186
194
  "dist_ts_stores_nosqldb",
195
+ "ts_typedrequest",
196
+ "dist_ts_typedrequest",
187
197
  "readme.md",
188
198
  "license.md"
189
199
  ]
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '10.16.0',
6
+ version: '10.17.0',
7
7
  description: 'The FlexHarness toolbox source repository and shared dependency catalog.'
8
8
  }
@@ -497,6 +497,22 @@ interface IRunReadCall {
497
497
 
498
498
  const notShared = Symbol('not shared');
499
499
 
500
+ /** Runs `operation` after the operations queued before it under `key`, whatever their outcome. */
501
+ function queueOperation<TResult>(
502
+ queues: Map<string, Promise<void>>,
503
+ key: string,
504
+ operation: () => Promise<TResult>,
505
+ ): Promise<TResult> {
506
+ const previous = queues.get(key) ?? Promise.resolve();
507
+ const result = previous.then(operation);
508
+ const barrier = result.then(() => undefined, () => undefined);
509
+ queues.set(key, barrier);
510
+ void barrier.then(() => {
511
+ if (queues.get(key) === barrier) queues.delete(key);
512
+ });
513
+ return result;
514
+ }
515
+
500
516
  /** The steers of a run: the input its generation takes in, and the prompts it accepted for it. */
501
517
  interface IRunSteering {
502
518
  readonly input: plugins.AgentPendingInputQueue;
@@ -928,6 +944,8 @@ export class FlexHarness<TScope = unknown> {
928
944
  private readonly orphanedTombstoneOwners = new Map<string, IOrphanedTombstoneOwner<TScope>>();
929
945
  private readonly pendingPromptAdmissionOwners = new Set<IPendingPromptAdmission>();
930
946
  private readonly projectManagementQueues = new Map<string, Promise<void>>();
947
+ /** A session's result writes, one at a time: they share the session's index of results. */
948
+ private readonly resultQueues = new Map<string, Promise<void>>();
931
949
  private readonly activeSlashCommandExecutions = new Map<string, IActiveSlashCommandExecution>();
932
950
  private readonly activeSlashCommandListings = new Set<IActiveSlashCommandListing>();
933
951
  private readonly stagedSessionImports = new Map<string, IStagedSessionImport>();
@@ -5318,17 +5336,7 @@ export class FlexHarness<TScope = unknown> {
5318
5336
  sessionId: string,
5319
5337
  operation: () => Promise<TResult>,
5320
5338
  ): Promise<TResult> {
5321
- const key = this.projectManagementKey(storageKey, sessionId);
5322
- const previous = this.projectManagementQueues.get(key) ?? Promise.resolve();
5323
- const result = previous.then(operation);
5324
- const barrier = result.then(() => undefined, () => undefined);
5325
- this.projectManagementQueues.set(key, barrier);
5326
- void barrier.then(() => {
5327
- if (this.projectManagementQueues.get(key) === barrier) {
5328
- this.projectManagementQueues.delete(key);
5329
- }
5330
- });
5331
- return result;
5339
+ return queueOperation(this.projectManagementQueues, this.projectManagementKey(storageKey, sessionId), operation);
5332
5340
  }
5333
5341
 
5334
5342
  private async awaitProjectManagementOperations(
@@ -6016,10 +6024,22 @@ export class FlexHarness<TScope = unknown> {
6016
6024
  };
6017
6025
  // A budget too small for even the handle keeps the truncated output.
6018
6026
  if (jsonBytes(result) > maxBytes) return normalizeJsonValue(output, this.toolOutputLimits);
6019
- await results.save(run.state.storageKey, run.sessionId, handle, stored);
6027
+ const { storageKey } = run.state;
6028
+ await queueOperation(this.resultQueues, this.resultQueueKey(storageKey, run.sessionId), async () => {
6029
+ await results.save(storageKey, run.sessionId, handle, stored);
6030
+ // The session keeps its latest results; older ones are deleted.
6031
+ const handles = await results.list(storageKey, run.sessionId);
6032
+ for (const old of handles.slice(0, Math.max(0, handles.length - FLEX_RESULT_LIMITS.maxResultsPerSession))) {
6033
+ await results.delete(storageKey, run.sessionId, old);
6034
+ }
6035
+ });
6020
6036
  return result as unknown as TJsonValue;
6021
6037
  }
6022
6038
 
6039
+ private resultQueueKey(storageKey: string, sessionId: string): string {
6040
+ return JSON.stringify([storageKey, sessionId]);
6041
+ }
6042
+
6023
6043
  /** The JSON bytes of one `results.get` page: the output budget, less room for the page's fields. */
6024
6044
  private resultPageBytes(): number {
6025
6045
  return Math.max(64, this.toolOutputLimits.maxBytes - 512);
@@ -6033,7 +6053,7 @@ export class FlexHarness<TScope = unknown> {
6033
6053
  const method = FLEX_RESULT_METHOD;
6034
6054
  const stored = await this.stores.results!.load(run.state.storageKey, run.sessionId, params.handle);
6035
6055
  if (stored === undefined) {
6036
- return { ok: false, error: { error: 'unknown_handle', method, handle: params.handle, hint: 'A handle reads a result this session stored.' } };
6056
+ return { ok: false, error: { error: 'unknown_handle', method, handle: params.handle, hint: `A handle reads one of the latest ${FLEX_RESULT_LIMITS.maxResultsPerSession} results this session stored.` } };
6037
6057
  }
6038
6058
  const path = params.path ?? '';
6039
6059
  const value = selectPath(stored, path);
@@ -11243,12 +11263,18 @@ export class FlexHarness<TScope = unknown> {
11243
11263
  tombstone,
11244
11264
  );
11245
11265
  };
11266
+ const resultStore = this.stores.results;
11267
+ const resultCleanup = async (): Promise<void> => {
11268
+ // A result being saved for the session is written before its results are deleted.
11269
+ await this.resultQueues.get(this.resultQueueKey(storageKey, sessionId));
11270
+ await resultStore!.deleteSession(storageKey, sessionId);
11271
+ };
11246
11272
  const results = await Promise.allSettled([
11247
11273
  this.stores.agentEvents.deleteSession(storageKey, sessionId),
11248
11274
  this.stores.jobs.deleteSession(storageKey, sessionId),
11249
11275
  this.stores.projections.deleteSession(storageKey, sessionId),
11250
11276
  this.stores.permissions.deleteSession(storageKey, sessionId),
11251
- ...(this.stores.results ? [this.stores.results.deleteSession(storageKey, sessionId)] : []),
11277
+ ...(resultStore ? [resultCleanup()] : []),
11252
11278
  // An imported session refuses every project-management write, so it has no state to fence.
11253
11279
  ...(tombstone.imported ? [] : [projectManagementCleanup()]),
11254
11280
  ]);
@@ -52,6 +52,7 @@ import {
52
52
  providerKey,
53
53
  validateExpectedRevision,
54
54
  validateIdentifier,
55
+ validateResultIndex,
55
56
  validateProjectManagementWriteContext,
56
57
  validateSnapshotSave,
57
58
  } from './utils.storerules.js';
@@ -355,6 +356,14 @@ class InMemoryResultStore implements IFlexResultStore {
355
356
  return value === undefined ? undefined : cloneSerializable(value);
356
357
  }
357
358
 
359
+ public async list(storageKey: string, sessionId: string): Promise<string[]> {
360
+ return [...this.sessions.get(providerKey(storageKey, sessionId))?.keys() ?? []];
361
+ }
362
+
363
+ public async delete(storageKey: string, sessionId: string, handle: string): Promise<void> {
364
+ this.sessions.get(providerKey(storageKey, sessionId))?.delete(handle);
365
+ }
366
+
358
367
  public async deleteSession(storageKey: string, sessionId: string): Promise<void> {
359
368
  this.sessions.delete(providerKey(storageKey, sessionId));
360
369
  }
@@ -464,6 +473,10 @@ class JsonFileRoot {
464
473
  return plugins.path.join(this.resultSessionPath(storageKey, sessionId), `${this.digest(handle)}.json`);
465
474
  }
466
475
 
476
+ public resultIndexPath(storageKey: string, sessionId: string): string {
477
+ return plugins.path.join(this.resultSessionPath(storageKey, sessionId), 'index.json');
478
+ }
479
+
467
480
  public resultSessionPath(storageKey: string, sessionId: string): string {
468
481
  validateIdentifier(storageKey, 'storageKey');
469
482
  validateIdentifier(sessionId, 'sessionId');
@@ -888,30 +901,60 @@ class JsonPermissionStore implements IFlexPermissionStore {
888
901
  }
889
902
  }
890
903
 
904
+ /**
905
+ * One file per result and an index of the session's handles, oldest first. Every operation of a
906
+ * session runs in the session's queue, so the index follows the files.
907
+ */
891
908
  class JsonResultStore implements IFlexResultStore {
892
909
  constructor(private readonly root: JsonFileRoot) {}
893
910
 
894
- public async save(storageKey: string, sessionId: string, handle: string, value: TJsonValue): Promise<void> {
911
+ private inSessionQueue<T>(storageKey: string, sessionId: string, operation: () => Promise<T>): Promise<T> {
912
+ return JsonFileRoot.inQueue(`results:${this.root.resultSessionPath(storageKey, sessionId)}`, operation);
913
+ }
914
+
915
+ private readIndex(storageKey: string, sessionId: string): Promise<string[]> {
916
+ return this.root.read(this.root.resultIndexPath(storageKey, sessionId), validateResultIndex)
917
+ .then((index) => index?.handles ?? []);
918
+ }
919
+
920
+ public save(storageKey: string, sessionId: string, handle: string, value: TJsonValue): Promise<void> {
895
921
  const filePath = this.root.resultPath(storageKey, sessionId, handle);
896
- await JsonFileRoot.inQueue(filePath, async () => {
922
+ return this.inSessionQueue(storageKey, sessionId, async () => {
897
923
  if (await this.root.read(filePath, (stored) => stored) !== undefined) {
898
924
  throw new FlexHarnessStoreConflictError(`${providerKey(storageKey, sessionId)}:${handle}`, 0, 1);
899
925
  }
900
926
  await this.root.write(filePath, value);
927
+ const handles = await this.readIndex(storageKey, sessionId);
928
+ await this.root.write(this.root.resultIndexPath(storageKey, sessionId), { handles: [...handles, handle] });
901
929
  });
902
930
  }
903
931
 
904
- public async load(storageKey: string, sessionId: string, handle: string): Promise<TJsonValue | undefined> {
932
+ public load(storageKey: string, sessionId: string, handle: string): Promise<TJsonValue | undefined> {
905
933
  const filePath = this.root.resultPath(storageKey, sessionId, handle);
906
- return JsonFileRoot.inQueue(filePath, () => this.root.read(filePath, (value) => {
934
+ return this.inSessionQueue(storageKey, sessionId, () => this.root.read(filePath, (value) => {
907
935
  assertJsonSerializable(value);
908
936
  return value as TJsonValue;
909
937
  }));
910
938
  }
911
939
 
912
- public async deleteSession(storageKey: string, sessionId: string): Promise<void> {
940
+ public list(storageKey: string, sessionId: string): Promise<string[]> {
941
+ return this.inSessionQueue(storageKey, sessionId, () => this.readIndex(storageKey, sessionId));
942
+ }
943
+
944
+ public delete(storageKey: string, sessionId: string, handle: string): Promise<void> {
945
+ return this.inSessionQueue(storageKey, sessionId, async () => {
946
+ const handles = await this.readIndex(storageKey, sessionId);
947
+ await this.root.write(
948
+ this.root.resultIndexPath(storageKey, sessionId),
949
+ { handles: handles.filter((entry) => entry !== handle) },
950
+ );
951
+ await this.root.deleteFile(this.root.resultPath(storageKey, sessionId, handle));
952
+ });
953
+ }
954
+
955
+ public deleteSession(storageKey: string, sessionId: string): Promise<void> {
913
956
  const directory = this.root.resultSessionPath(storageKey, sessionId);
914
- await JsonFileRoot.inQueue(`results:${directory}`, () => this.root.deleteDirectory(directory));
957
+ return this.inSessionQueue(storageKey, sessionId, () => this.root.deleteDirectory(directory));
915
958
  }
916
959
  }
917
960