@acid-cat/spec-ts 0.3.1 → 0.5.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.
package/README.md CHANGED
@@ -12,7 +12,8 @@ pnpm add @acid-cat/spec-ts
12
12
 
13
13
  | Export | Use |
14
14
  | --- | --- |
15
- | `ok`, `fail`, `callFailure`, `isCallFailure` | Make and test [outcomes](../../spec/invocation.md#outcomes) |
15
+ | `ok`, `fail`, `callFailure`, `isCallFailure` | Make and test [outcomes](../../spec/invocation.md#outcomes). `fail` takes the whole failure: `fail({ _tag: "Offline", since })` |
16
+ | `isJsonObject` | Tell if a value is a JSON object: not `null`, and not an array |
16
17
  | `CALL_FAILURE_KINDS`, `CallFailureError` | The kinds of call failure, and the JavaScript error for one |
17
18
  | `AVAILABLE`, `unavailable` | Make [availability](../../spec/invocation.md#availability) values |
18
19
  | `viewOf`, `bundle` | The [view](../../spec/catalog.md#views) of a target, and a schema with its shared types |
package/dist/index.d.ts CHANGED
@@ -25,10 +25,10 @@ export type Result<Value, Failure> = {
25
25
  /** The end of a call: a value, a failure or a call failure. */
26
26
  export type Outcome<Value, Failure> = Result<Value, Failure> | CallFailure;
27
27
  export declare const ok: <Value>(value: Value) => Result<Value, never>;
28
- /** A failure with its tag, as a result. */
29
- export declare const fail: <Tag extends string, Data extends object = {}>(tag: Tag, data?: Data) => Result<never, {
30
- readonly _tag: Tag;
31
- } & Data>;
28
+ /** A failure, with its tag in `_tag`, as a result: `fail({ _tag: "Offline" })`. */
29
+ export declare const fail: <const Failure extends {
30
+ readonly _tag: string;
31
+ }>(failure: Failure) => Result<never, Failure>;
32
32
  export declare const callFailure: (kind: CallFailureKind, message: string) => CallFailure;
33
33
  export declare const isCallFailure: (outcome: Outcome<unknown, unknown>) => outcome is CallFailure;
34
34
  /** The JavaScript error for a call failure (spec/bindings/typescript.md#handles). */
@@ -143,6 +143,8 @@ export interface CatalogDocument extends JsonObject {
143
143
  };
144
144
  }
145
145
  export declare const REFERENCE: RegExp;
146
+ /** Tell if a value is a JSON object: an object that is not `null` and not an array. */
147
+ export declare const isJsonObject: (value: unknown) => value is JsonObject;
146
148
  /** The schemas of a capability, each with the member that holds it. */
147
149
  export declare function schemasOf(capability: CatalogCapability): readonly (readonly [string, JsonObject])[];
148
150
  /** Every object in a JSON value, at all levels. */
@@ -151,6 +153,14 @@ export declare function visitObjects(value: unknown, onObject: (node: JsonObject
151
153
  export declare function typesUsedBy(schema: unknown, types: {
152
154
  readonly [name: string]: JsonObject;
153
155
  }): Map<string, JsonObject>;
156
+ /**
157
+ * An input schema that is a union of object schemas, with `"type": "object"`, so that it is an object schema
158
+ * (spec/catalog.md#capabilities). A union is `anyOf` or `oneOf`, directly or in the type that a `$ref` names. Each of
159
+ * its schemas is an object, or a `$ref` to a type that is an object. Other schemas do not change.
160
+ */
161
+ export declare function objectInput(schema: JsonObject, types: {
162
+ readonly [name: string]: JsonObject;
163
+ }): JsonObject;
154
164
  /** Bundle a schema (spec/catalog.md#bundling). */
155
165
  export declare function bundle(schema: JsonObject, types: {
156
166
  readonly [name: string]: JsonObject;
package/dist/index.js CHANGED
@@ -19,13 +19,10 @@ const ok = (value) => ({
19
19
  ok: true,
20
20
  value
21
21
  });
22
- /** A failure with its tag, as a result. */
23
- const fail = (tag, data) => ({
22
+ /** A failure, with its tag in `_tag`, as a result: `fail({ _tag: "Offline" })`. */
23
+ const fail = (failure) => ({
24
24
  ok: false,
25
- error: {
26
- _tag: tag,
27
- ...data ?? {}
28
- }
25
+ error: failure
29
26
  });
30
27
  const callFailure = (kind, message) => ({
31
28
  kind,
@@ -65,15 +62,16 @@ const cliExitCode = (outcome) => isCallFailure(outcome) ? CLI_EXIT_CODES[outcome
65
62
  * code point order of the names. `input` is the bundled input schema.
66
63
  */
67
64
  function cliFlags(input) {
68
- const properties = input.properties ?? {};
69
- const definitions = input.$defs ?? {};
70
- const required = new Set(input.required ?? []);
65
+ const properties = objectOrEmpty(input.properties);
66
+ const definitions = objectOrEmpty(input.$defs);
67
+ const required = new Set(stringsOf(input.required));
71
68
  const byName = (a, b) => a < b ? -1 : a > b ? 1 : 0;
72
69
  const names = Object.keys(properties).sort(byName);
73
70
  return [...names.filter((name) => required.has(name)), ...names.filter((name) => !required.has(name))].map((name) => {
74
- const reference = properties[name].$ref;
71
+ const declared = objectOrEmpty(properties[name]);
72
+ const reference = declared.$ref;
75
73
  const target = typeof reference === "string" ? REFERENCE.exec(reference)?.[1] : void 0;
76
- const property = (target === void 0 ? void 0 : definitions[target]) ?? properties[name];
74
+ const property = objectOrEmpty((target === void 0 ? void 0 : definitions[target]) ?? declared);
77
75
  const flag = property.type === "boolean" ? `--${name}` : `--${name} <${flagValue(property)}>`;
78
76
  return required.has(name) ? flag : `[${flag}]`;
79
77
  });
@@ -112,7 +110,7 @@ function screenRequest(request, rules) {
112
110
  const origin = request.headers.get("Origin");
113
111
  if (origin !== null && !rules.allowedOrigins.includes(origin)) return "origin";
114
112
  if (request.method === "GET" || request.method === "HEAD") return void 0;
115
- if ((request.headers.get("Content-Type") ?? "").split(";")[0].trim().toLowerCase() !== rules.mediaType) return "media-type";
113
+ if (((request.headers.get("Content-Type") ?? "").split(";")[0] ?? "").trim().toLowerCase() !== rules.mediaType) return "media-type";
116
114
  const declared = Number(request.headers.get("Content-Length") ?? "");
117
115
  if (Number.isFinite(declared) && declared > rules.maximumBytes) return "too-large";
118
116
  }
@@ -186,6 +184,12 @@ function mcpToolResult(outcome, hasOutputSchema) {
186
184
  } : { content: text(outcome.value) };
187
185
  }
188
186
  const REFERENCE = /^#\/\$defs\/([A-Za-z][A-Za-z0-9_]*)$/;
187
+ /** Tell if a value is a JSON object: an object that is not `null` and not an array. */
188
+ const isJsonObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
189
+ /** A value as a JSON object, or an empty object when it is not one. */
190
+ const objectOrEmpty = (value) => isJsonObject(value) ? value : {};
191
+ /** The strings in a value that is an array, or no strings when it is not an array. */
192
+ const stringsOf = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
189
193
  /** The schemas of a capability, each with the member that holds it. */
190
194
  function schemasOf(capability) {
191
195
  return [
@@ -198,7 +202,7 @@ function schemasOf(capability) {
198
202
  /** Every object in a JSON value, at all levels. */
199
203
  function visitObjects(value, onObject) {
200
204
  if (Array.isArray(value)) value.forEach((item) => visitObjects(item, onObject));
201
- else if (value !== null && typeof value === "object") {
205
+ else if (isJsonObject(value)) {
202
206
  onObject(value);
203
207
  Object.values(value).forEach((item) => visitObjects(item, onObject));
204
208
  }
@@ -217,6 +221,27 @@ function typesUsedBy(schema, types) {
217
221
  collect(schema);
218
222
  return used;
219
223
  }
224
+ /**
225
+ * An input schema that is a union of object schemas, with `"type": "object"`, so that it is an object schema
226
+ * (spec/catalog.md#capabilities). A union is `anyOf` or `oneOf`, directly or in the type that a `$ref` names. Each of
227
+ * its schemas is an object, or a `$ref` to a type that is an object. Other schemas do not change.
228
+ */
229
+ function objectInput(schema, types) {
230
+ const resolve = (node) => {
231
+ if (!isJsonObject(node)) return void 0;
232
+ const object = node;
233
+ const name = typeof object.$ref === "string" ? REFERENCE.exec(object.$ref)?.[1] : void 0;
234
+ return name === void 0 ? object : types[name];
235
+ };
236
+ const target = resolve(schema);
237
+ if (schema.type !== void 0 || target === void 0 || target.type !== void 0) return schema;
238
+ const union = target.anyOf ?? target.oneOf;
239
+ if (!Array.isArray(union) || union.length === 0) return schema;
240
+ return union.every((member) => resolve(member)?.type === "object") ? {
241
+ type: "object",
242
+ ...schema
243
+ } : schema;
244
+ }
220
245
  /** Bundle a schema (spec/catalog.md#bundling). */
221
246
  function bundle(schema, types) {
222
247
  const only = Object.keys(schema).length === 1 ? REFERENCE.exec(String(schema.$ref))?.[1] : void 0;
@@ -246,4 +271,4 @@ function viewOf(catalog, target) {
246
271
  };
247
272
  }
248
273
  //#endregion
249
- export { AVAILABLE, CALL_FAILURE_KINDS, CLI_EXIT_CODES, CallFailureError, HTTP_STATUS, ID_PATTERN, MCP_PROTOCOL_VERSION, MCP_REJECTIONS, MCP_UNKNOWN_TOOL, REFERENCE, bundle, callFailure, cliExitCode, cliFlags, cliUsage, commandPath, fail, httpStatus, isCallFailure, isId, mcpToolResult, ok, readBounded, schemasOf, screenRequest, typesUsedBy, unavailable, viewOf, visitObjects };
274
+ export { AVAILABLE, CALL_FAILURE_KINDS, CLI_EXIT_CODES, CallFailureError, HTTP_STATUS, ID_PATTERN, MCP_PROTOCOL_VERSION, MCP_REJECTIONS, MCP_UNKNOWN_TOOL, REFERENCE, bundle, callFailure, cliExitCode, cliFlags, cliUsage, commandPath, fail, httpStatus, isCallFailure, isId, isJsonObject, mcpToolResult, objectInput, ok, readBounded, schemasOf, screenRequest, typesUsedBy, unavailable, viewOf, visitObjects };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acid-cat/spec-ts",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "The fixed details of the AcidCat specification as TypeScript data and pure functions, with no dependencies. Part of the TypeScript reference implementation.",
5
5
  "keywords": [
6
6
  "acidcat",