@openmbee/opensysml 0.9.2 → 0.10.1

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 (67) hide show
  1. package/README.md +355 -33
  2. package/dist/browser/index.d.ts +9 -0
  3. package/dist/browser/index.js +16 -0
  4. package/dist/browser/wasm-worker.d.ts +1 -0
  5. package/dist/browser/wasm-worker.js +2 -0
  6. package/dist/browser/wasm.d.ts +17 -0
  7. package/dist/browser/wasm.js +97 -0
  8. package/dist/core/arguments.d.ts +4 -0
  9. package/dist/core/arguments.js +19 -0
  10. package/dist/core/capabilities.d.ts +90 -9
  11. package/dist/core/capabilities.js +111 -10
  12. package/dist/core/connection.d.ts +203 -2
  13. package/dist/core/connection.js +810 -17
  14. package/dist/core/conversion.d.ts +153 -0
  15. package/dist/core/conversion.js +191 -0
  16. package/dist/core/document.d.ts +188 -0
  17. package/dist/core/document.js +457 -0
  18. package/dist/core/edit.d.ts +446 -0
  19. package/dist/core/edit.js +1994 -0
  20. package/dist/core/engines.d.ts +64 -0
  21. package/dist/core/engines.js +74 -0
  22. package/dist/core/errors.d.ts +164 -2
  23. package/dist/core/errors.js +209 -3
  24. package/dist/core/exploration.d.ts +71 -0
  25. package/dist/core/exploration.js +116 -0
  26. package/dist/core/graphs.d.ts +15 -0
  27. package/dist/core/graphs.js +3 -0
  28. package/dist/core/index.d.ts +30 -9
  29. package/dist/core/index.js +14 -5
  30. package/dist/core/model.d.ts +168 -8
  31. package/dist/core/model.js +258 -31
  32. package/dist/core/package.d.ts +2 -0
  33. package/dist/core/package.js +2 -0
  34. package/dist/core/query.d.ts +50 -0
  35. package/dist/core/query.js +197 -0
  36. package/dist/core/render-view.d.ts +97 -0
  37. package/dist/core/render-view.js +120 -0
  38. package/dist/core/sources.d.ts +34 -0
  39. package/dist/core/sources.js +134 -0
  40. package/dist/core/status.d.ts +9 -2
  41. package/dist/core/status.js +24 -8
  42. package/dist/core/typed.d.ts +71 -0
  43. package/dist/core/typed.js +245 -0
  44. package/dist/core/values.d.ts +127 -8
  45. package/dist/core/values.js +538 -30
  46. package/dist/core/verdict.d.ts +271 -0
  47. package/dist/core/verdict.js +567 -0
  48. package/dist/core/wasm.d.ts +114 -0
  49. package/dist/core/wasm.js +514 -0
  50. package/dist/generated/sysml_pb.d.ts +1062 -15
  51. package/dist/generated/sysml_pb.js +200 -100
  52. package/dist/node/generate-cli.d.ts +2 -0
  53. package/dist/node/generate-cli.js +10 -0
  54. package/dist/node/generate.d.ts +99 -0
  55. package/dist/node/generate.js +757 -0
  56. package/dist/node/index.d.ts +29 -2
  57. package/dist/node/index.js +211 -10
  58. package/dist/node/service.d.ts +5 -3
  59. package/dist/node/service.js +32 -26
  60. package/dist/node/wasm-source.d.ts +18 -0
  61. package/dist/node/wasm-source.js +103 -0
  62. package/dist/node/wasm-worker.d.ts +1 -0
  63. package/dist/node/wasm-worker.js +10 -0
  64. package/dist/node/wasm.d.ts +17 -0
  65. package/dist/node/wasm.js +63 -0
  66. package/package.json +19 -7
  67. package/release-digests.json +117 -0
@@ -0,0 +1,197 @@
1
+ // Querying a model the way the SysML v2 API & Services standard queries one:
2
+ // scope / select / where, where `where` is a primitive or composite constraint.
3
+ // Accepts the standard's JSON verbatim, or the same thing as options.
4
+ import { create } from "@bufbuild/protobuf";
5
+ import { CompositeOperator, ConstraintSchema, PrimitiveOperator, QuerySchema, } from "../generated/sysml_pb.js";
6
+ import { byCodeUnit } from "./capabilities.js";
7
+ import { QueryError } from "./errors.js";
8
+ const TYPE_KEY = "@type";
9
+ /** `@type` of the standard's query resource. */
10
+ export const TYPE_QUERY = "Query";
11
+ /** `@type` of a single-property constraint. */
12
+ export const TYPE_PRIMITIVE_CONSTRAINT = "PrimitiveConstraint";
13
+ /** `@type` of a constraint over nested constraints. */
14
+ export const TYPE_COMPOSITE_CONSTRAINT = "CompositeConstraint";
15
+ const PRIMITIVE_OPERATORS = new Map([
16
+ ["=", PrimitiveOperator.EQUAL],
17
+ [">", PrimitiveOperator.GREATER],
18
+ ["<", PrimitiveOperator.LESS],
19
+ ]);
20
+ const COMPOSITE_OPERATORS = new Map([
21
+ ["and", CompositeOperator.AND],
22
+ ["or", CompositeOperator.OR],
23
+ ]);
24
+ /** An element a query selected. */
25
+ export class QueryElement {
26
+ /** Qualified name of the element, which is what a scope names it by. */
27
+ id;
28
+ /** The element's metamodel type, e.g. `PartUsage`. */
29
+ type;
30
+ /** The selected properties it has; one it does not have is absent. */
31
+ properties;
32
+ constructor(init) {
33
+ this.id = init.id;
34
+ this.type = init.type;
35
+ this.properties = { ...init.properties };
36
+ }
37
+ /** Value of a selected property, or `fallback` if the element has none. */
38
+ get(propertyName, fallback) {
39
+ return this.properties[propertyName] ?? fallback;
40
+ }
41
+ /** The element as the standard's JSON names it: `@id`, `@type`, properties. */
42
+ asDict() {
43
+ return { "@id": this.id, "@type": this.type, ...this.properties };
44
+ }
45
+ toString() {
46
+ return `${this.id} (${this.type})`;
47
+ }
48
+ }
49
+ function isRecord(value) {
50
+ return typeof value === "object" && value !== null && !Array.isArray(value);
51
+ }
52
+ /** Translate a query, given as the standard's JSON or as keywords, to protobuf. */
53
+ export function buildQuery(options = {}) {
54
+ const { payload } = options;
55
+ let { scope, select, where } = options;
56
+ if (payload !== undefined) {
57
+ if (scope !== undefined || select !== undefined || where !== undefined) {
58
+ throw new QueryError("pass a query payload or scope/select/where keywords, not both");
59
+ }
60
+ if (!isRecord(payload)) {
61
+ throw new QueryError("a query is an object, not a " + typeof payload);
62
+ }
63
+ const declared = payload[TYPE_KEY] ?? TYPE_QUERY;
64
+ if (declared !== TYPE_QUERY) {
65
+ throw new QueryError(`expected a ${JSON.stringify(TYPE_QUERY)} payload, got ${JSON.stringify(declared)}`);
66
+ }
67
+ const unknown = Object.keys(payload).filter((key) => ![TYPE_KEY, "@id", "owningProject", "scope", "select", "where"].includes(key));
68
+ if (unknown.length > 0) {
69
+ unknown.sort(byCodeUnit);
70
+ throw new QueryError(`a query has no ${unknown.join(", ")}; the standard's query is scope, select and where`);
71
+ }
72
+ scope = payload.scope;
73
+ select = payload.select;
74
+ where = payload.where;
75
+ }
76
+ const query = create(QuerySchema, {
77
+ scope: sequence("scope", scope).map(scopeId),
78
+ select: sequence("select", select).map(propertyName),
79
+ });
80
+ if (where !== undefined) {
81
+ query.where = constraint(where);
82
+ }
83
+ return query;
84
+ }
85
+ function scopeId(entry) {
86
+ if (typeof entry === "string") {
87
+ return entry;
88
+ }
89
+ if (isRecord(entry) && typeof entry["@id"] === "string") {
90
+ return entry["@id"];
91
+ }
92
+ throw new QueryError(`a scope entry is an element's qualified name or a {'@id': ...} reference, not ${JSON.stringify(entry)}`);
93
+ }
94
+ function propertyName(entry) {
95
+ if (typeof entry !== "string") {
96
+ throw new QueryError(`a selected property is a name, not ${JSON.stringify(entry)}`);
97
+ }
98
+ return entry;
99
+ }
100
+ function sequence(field, value) {
101
+ if (value === undefined || value === null) {
102
+ return [];
103
+ }
104
+ if (typeof value === "string" || isRecord(value)) {
105
+ return [value];
106
+ }
107
+ if (!Array.isArray(value)) {
108
+ throw new QueryError(`${field} is a list, not ${typeof value}`);
109
+ }
110
+ return value;
111
+ }
112
+ function constraint(payload) {
113
+ if (!isRecord(payload)) {
114
+ throw new QueryError(`a constraint is an object, not ${typeof payload}`);
115
+ }
116
+ let declared = payload[TYPE_KEY];
117
+ if (declared === undefined) {
118
+ declared = "constraint" in payload ? TYPE_COMPOSITE_CONSTRAINT : TYPE_PRIMITIVE_CONSTRAINT;
119
+ }
120
+ if (declared === TYPE_PRIMITIVE_CONSTRAINT) {
121
+ return create(ConstraintSchema, {
122
+ constraint: { case: "primitive", value: primitive(payload) },
123
+ });
124
+ }
125
+ if (declared === TYPE_COMPOSITE_CONSTRAINT) {
126
+ return create(ConstraintSchema, {
127
+ constraint: { case: "composite", value: composite(payload) },
128
+ });
129
+ }
130
+ throw new QueryError(`unknown constraint type ${JSON.stringify(declared)}; the standard's constraints are ` +
131
+ `${TYPE_PRIMITIVE_CONSTRAINT} and ${TYPE_COMPOSITE_CONSTRAINT}`);
132
+ }
133
+ function rejectUnknown(payload, known, what) {
134
+ const unknown = Object.keys(payload).filter((key) => !known.includes(key));
135
+ if (unknown.length > 0) {
136
+ unknown.sort(byCodeUnit);
137
+ throw new QueryError(`a ${what} has no ${unknown.join(", ")}`);
138
+ }
139
+ }
140
+ function primitive(payload) {
141
+ rejectUnknown(payload, [TYPE_KEY, "@id", "inverse", "property", "operator", "value"], TYPE_PRIMITIVE_CONSTRAINT);
142
+ const operator = payload.operator;
143
+ const mapped = typeof operator === "string" ? PRIMITIVE_OPERATORS.get(operator) : undefined;
144
+ if (mapped === undefined) {
145
+ throw new QueryError(`unknown primitive operator ${JSON.stringify(operator)}; expected one of ` +
146
+ [...PRIMITIVE_OPERATORS.keys()].sort(byCodeUnit).join(", "));
147
+ }
148
+ const name = payload.property;
149
+ if (typeof name !== "string" || name === "") {
150
+ throw new QueryError(`a primitive constraint names one property, not ${JSON.stringify(name)}`);
151
+ }
152
+ return {
153
+ inverse: payload.inverse === true,
154
+ property: name,
155
+ operator: mapped,
156
+ value: values(payload.value),
157
+ };
158
+ }
159
+ function composite(payload) {
160
+ rejectUnknown(payload, [TYPE_KEY, "@id", "constraint", "operator"], TYPE_COMPOSITE_CONSTRAINT);
161
+ const operator = payload.operator;
162
+ const mapped = typeof operator === "string" ? COMPOSITE_OPERATORS.get(operator) : undefined;
163
+ if (mapped === undefined) {
164
+ throw new QueryError(`unknown composite operator ${JSON.stringify(operator)}; expected one of ` +
165
+ [...COMPOSITE_OPERATORS.keys()].sort(byCodeUnit).join(", "));
166
+ }
167
+ const nested = payload.constraint;
168
+ if (!Array.isArray(nested) || nested.length === 0) {
169
+ throw new QueryError(`a composite constraint combines a non-empty list of constraints, not ${JSON.stringify(nested)}`);
170
+ }
171
+ return {
172
+ operator: mapped,
173
+ constraint: nested.map(constraint),
174
+ };
175
+ }
176
+ function values(value) {
177
+ if (value === undefined || value === null) {
178
+ return [];
179
+ }
180
+ if (Array.isArray(value)) {
181
+ return value.map(compared);
182
+ }
183
+ return [compared(value)];
184
+ }
185
+ function compared(value) {
186
+ if (typeof value === "boolean") {
187
+ return value ? "true" : "false";
188
+ }
189
+ if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
190
+ return String(value);
191
+ }
192
+ throw new QueryError(`cannot compare against ${JSON.stringify(value)}`);
193
+ }
194
+ /** The elements a `QueryResponse` reports, as `QueryElement` records. */
195
+ export function elementsOf(response) {
196
+ return response.elements.map((element) => new QueryElement({ id: element.id, type: element.type, properties: element.properties }));
197
+ }
@@ -0,0 +1,97 @@
1
+ import type { RenderViewResponse } from "../generated/sysml_pb.js";
2
+ export interface RenderSpan {
3
+ file: string;
4
+ startLine: number;
5
+ startCol: number;
6
+ endLine: number;
7
+ endCol: number;
8
+ }
9
+ export interface RenderPort {
10
+ id: string;
11
+ name: string;
12
+ type: string;
13
+ direction: string;
14
+ }
15
+ export interface RenderGeometry {
16
+ x: number;
17
+ y: number;
18
+ width: number;
19
+ height: number;
20
+ hasSize: boolean;
21
+ collapsed: boolean;
22
+ }
23
+ export interface RenderStyle {
24
+ fill: string;
25
+ line: string;
26
+ text: string;
27
+ font: string;
28
+ fontSize: number;
29
+ bold: boolean;
30
+ italic: boolean;
31
+ }
32
+ export interface RenderPoint {
33
+ x: number;
34
+ y: number;
35
+ }
36
+ export interface RenderNode {
37
+ id: string;
38
+ kind: string;
39
+ name: string;
40
+ nameSynthesized: boolean;
41
+ type: string;
42
+ detail: string;
43
+ text: string;
44
+ standIn: boolean;
45
+ parent: string;
46
+ ports: readonly RenderPort[];
47
+ origin?: RenderSpan;
48
+ geometry?: RenderGeometry;
49
+ style?: RenderStyle;
50
+ }
51
+ export interface RenderEdge {
52
+ from: string;
53
+ to: string;
54
+ fromPort: string;
55
+ toPort: string;
56
+ label: string;
57
+ name: string;
58
+ kind: string;
59
+ origin?: RenderSpan;
60
+ route: readonly RenderPoint[];
61
+ style?: RenderStyle;
62
+ }
63
+ export interface RenderCanvas {
64
+ unit: string;
65
+ width: number;
66
+ height: number;
67
+ hasSize: boolean;
68
+ }
69
+ export interface RenderRow {
70
+ cells: readonly string[];
71
+ origin?: RenderSpan;
72
+ }
73
+ export interface RenderNote {
74
+ text: string;
75
+ anchor: string;
76
+ edgeFrom: string;
77
+ edgeTo: string;
78
+ x: number;
79
+ y: number;
80
+ width: number;
81
+ height: number;
82
+ hasSize: boolean;
83
+ origin?: RenderSpan;
84
+ }
85
+ export interface RenderedView {
86
+ view: string;
87
+ kind: string;
88
+ stated: string;
89
+ nodes: readonly RenderNode[];
90
+ edges: readonly RenderEdge[];
91
+ columns: readonly string[];
92
+ rows: readonly RenderRow[];
93
+ canvas?: RenderCanvas;
94
+ notes: readonly RenderNote[];
95
+ notices: readonly string[];
96
+ }
97
+ export declare function renderedViewOf(response: RenderViewResponse): RenderedView;
@@ -0,0 +1,120 @@
1
+ function spanOf(span) {
2
+ return span === undefined
3
+ ? undefined
4
+ : {
5
+ file: span.file,
6
+ startLine: span.startLine,
7
+ startCol: span.startCol,
8
+ endLine: span.endLine,
9
+ endCol: span.endCol,
10
+ };
11
+ }
12
+ function geometryOf(geometry) {
13
+ return geometry === undefined
14
+ ? undefined
15
+ : {
16
+ x: geometry.x,
17
+ y: geometry.y,
18
+ width: geometry.width,
19
+ height: geometry.height,
20
+ hasSize: geometry.hasSize,
21
+ collapsed: geometry.collapsed,
22
+ };
23
+ }
24
+ function styleOf(style) {
25
+ return style === undefined
26
+ ? undefined
27
+ : {
28
+ fill: style.fill,
29
+ line: style.line,
30
+ text: style.text,
31
+ font: style.font,
32
+ fontSize: style.fontSize,
33
+ bold: style.bold,
34
+ italic: style.italic,
35
+ };
36
+ }
37
+ function portOf(port) {
38
+ return { id: port.id, name: port.name, type: port.type, direction: port.direction };
39
+ }
40
+ function pointOf(point) {
41
+ return { x: point.x, y: point.y };
42
+ }
43
+ function nodeOf(node) {
44
+ const origin = spanOf(node.origin);
45
+ const geometry = geometryOf(node.geometry);
46
+ const style = styleOf(node.style);
47
+ return {
48
+ id: node.id,
49
+ kind: node.kind,
50
+ name: node.name,
51
+ nameSynthesized: node.nameSynthesized,
52
+ type: node.type,
53
+ detail: node.detail,
54
+ text: node.text,
55
+ standIn: node.standIn,
56
+ parent: node.parent,
57
+ ports: node.ports.map(portOf),
58
+ ...(origin === undefined ? {} : { origin }),
59
+ ...(geometry === undefined ? {} : { geometry }),
60
+ ...(style === undefined ? {} : { style }),
61
+ };
62
+ }
63
+ function edgeOf(edge) {
64
+ const origin = spanOf(edge.origin);
65
+ const style = styleOf(edge.style);
66
+ return {
67
+ from: edge.from,
68
+ to: edge.to,
69
+ fromPort: edge.fromPort,
70
+ toPort: edge.toPort,
71
+ label: edge.label,
72
+ name: edge.name,
73
+ kind: edge.kind,
74
+ route: edge.route.map(pointOf),
75
+ ...(origin === undefined ? {} : { origin }),
76
+ ...(style === undefined ? {} : { style }),
77
+ };
78
+ }
79
+ function rowOf(row) {
80
+ const origin = spanOf(row.origin);
81
+ return {
82
+ cells: [...row.cells],
83
+ ...(origin === undefined ? {} : { origin }),
84
+ };
85
+ }
86
+ function noteOf(note) {
87
+ const origin = spanOf(note.origin);
88
+ return {
89
+ text: note.text,
90
+ anchor: note.anchor,
91
+ edgeFrom: note.edgeFrom,
92
+ edgeTo: note.edgeTo,
93
+ x: note.x,
94
+ y: note.y,
95
+ width: note.width,
96
+ height: note.height,
97
+ hasSize: note.hasSize,
98
+ ...(origin === undefined ? {} : { origin }),
99
+ };
100
+ }
101
+ function canvasOf(canvas) {
102
+ return canvas === undefined
103
+ ? undefined
104
+ : { unit: canvas.unit, width: canvas.width, height: canvas.height, hasSize: canvas.hasSize };
105
+ }
106
+ export function renderedViewOf(response) {
107
+ const canvas = canvasOf(response.canvas);
108
+ return {
109
+ view: response.view,
110
+ kind: response.kind,
111
+ stated: response.stated,
112
+ nodes: response.nodes.map(nodeOf),
113
+ edges: response.edges.map(edgeOf),
114
+ columns: [...response.columns],
115
+ rows: response.rows.map(rowOf),
116
+ notes: response.notes.map(noteOf),
117
+ notices: [...response.notices],
118
+ ...(canvas === undefined ? {} : { canvas }),
119
+ };
120
+ }
@@ -0,0 +1,34 @@
1
+ import type { SourceDocument as PbSourceDocument } from "../generated/sysml_pb.js";
2
+ /** Languages inline content may be declared in. */
3
+ export declare const LANGUAGES: readonly ["sysml", "kerml"];
4
+ /** Refuses a `language` option that names no language, before any call. */
5
+ export declare function requireLanguage(language: string): void;
6
+ /** One document of a model parsed from several. Build with `file` or `inline`. */
7
+ export declare class SourceDocument {
8
+ /** File the service reads; its extension says which notation it is. */
9
+ readonly path: string | undefined;
10
+ /** Inline model source, parsed under `name`. */
11
+ readonly content: string | undefined;
12
+ /** What diagnostics call inline content; empty for a file, named by its path. */
13
+ readonly name: string;
14
+ /** Notation of inline content; `sysml` when unset. */
15
+ readonly language: string | undefined;
16
+ private constructor();
17
+ /** A file the service reads, named by its path. */
18
+ static file(path: string): SourceDocument;
19
+ /** Inline content, reported and indexed under `name`. */
20
+ static inline(name: string, content: string, options?: {
21
+ language?: string;
22
+ }): SourceDocument;
23
+ /** The name diagnostics report this document under: the path or name. */
24
+ get documentName(): string;
25
+ /** The `sysml.SourceDocument` message that sends this document. */
26
+ toPb(): PbSourceDocument;
27
+ }
28
+ /** What `parseSources` accepts for one document. */
29
+ export type Source = string | readonly [name: string, content: string] | SourceDocument;
30
+ /**
31
+ * The `SourceDocument` for each of `documents`, validated as Python's
32
+ * `source_documents` does: a sequence of at least one, no two named alike.
33
+ */
34
+ export declare function sourceDocuments(documents: readonly Source[]): SourceDocument[];
@@ -0,0 +1,134 @@
1
+ // The documents of a model parsed from several sources at once. Each is a file
2
+ // the service reads, or inline content under a name; both forms also have a
3
+ // SourceDocument the `inline`/`file` factories build.
4
+ import { create } from "@bufbuild/protobuf";
5
+ import { requireSourceText, requireString } from "./arguments.js";
6
+ import { SourceDocumentSchema } from "../generated/sysml_pb.js";
7
+ /** Languages inline content may be declared in. */
8
+ export const LANGUAGES = ["sysml", "kerml"];
9
+ /** Refuses a `language` option that names no language, before any call. */
10
+ export function requireLanguage(language) {
11
+ if (!LANGUAGES.includes(language)) {
12
+ throw new RangeError(`language must be "sysml" or "kerml", got "${language}"`);
13
+ }
14
+ }
15
+ /** One document of a model parsed from several. Build with `file` or `inline`. */
16
+ export class SourceDocument {
17
+ /** File the service reads; its extension says which notation it is. */
18
+ path;
19
+ /** Inline model source, parsed under `name`. */
20
+ content;
21
+ /** What diagnostics call inline content; empty for a file, named by its path. */
22
+ name;
23
+ /** Notation of inline content; `sysml` when unset. */
24
+ language;
25
+ constructor(init) {
26
+ if (init.name !== undefined) {
27
+ requireString("name", init.name);
28
+ }
29
+ for (const [field, value] of [
30
+ ["path", init.path],
31
+ ["content", init.content],
32
+ ["language", init.language],
33
+ ]) {
34
+ if (value !== undefined) {
35
+ requireString(field, value);
36
+ }
37
+ }
38
+ if (init.content !== undefined) {
39
+ requireSourceText("content", init.content);
40
+ }
41
+ if ((init.path === undefined) === (init.content === undefined)) {
42
+ throw new RangeError("a SourceDocument is either a file path or inline content, not both");
43
+ }
44
+ if (init.path !== undefined) {
45
+ if (init.path === "") {
46
+ throw new RangeError("a file document needs a path");
47
+ }
48
+ if (init.name !== undefined && init.name !== "") {
49
+ throw new RangeError("a file document is named by its path; name applies to inline content");
50
+ }
51
+ if (init.language !== undefined) {
52
+ throw new RangeError("a file's extension says which language it is; language applies to inline content");
53
+ }
54
+ }
55
+ else {
56
+ if (init.name === undefined || init.name === "") {
57
+ throw new RangeError("inline content needs a name; diagnostics report it under that name");
58
+ }
59
+ if (init.language !== undefined) {
60
+ requireLanguage(init.language);
61
+ }
62
+ }
63
+ this.path = init.path;
64
+ this.content = init.content;
65
+ this.name = init.name ?? "";
66
+ this.language = init.language;
67
+ }
68
+ /** A file the service reads, named by its path. */
69
+ static file(path) {
70
+ return new SourceDocument({ path });
71
+ }
72
+ /** Inline content, reported and indexed under `name`. */
73
+ static inline(name, content, options = {}) {
74
+ return new SourceDocument({ content, name, ...(options.language === undefined ? {} : { language: options.language }) });
75
+ }
76
+ /** The name diagnostics report this document under: the path or name. */
77
+ get documentName() {
78
+ return this.path ?? this.name;
79
+ }
80
+ /** The `sysml.SourceDocument` message that sends this document. */
81
+ toPb() {
82
+ if (this.path !== undefined) {
83
+ return create(SourceDocumentSchema, { source: { case: "filePath", value: this.path } });
84
+ }
85
+ return create(SourceDocumentSchema, {
86
+ source: { case: "content", value: this.content ?? "" },
87
+ name: this.name,
88
+ language: this.language ?? "",
89
+ });
90
+ }
91
+ }
92
+ function isNamedContent(document) {
93
+ return (Array.isArray(document) &&
94
+ document.length === 2 &&
95
+ typeof document[0] === "string" &&
96
+ typeof document[1] === "string");
97
+ }
98
+ /**
99
+ * The `SourceDocument` for each of `documents`, validated as Python's
100
+ * `source_documents` does: a sequence of at least one, no two named alike.
101
+ */
102
+ export function sourceDocuments(documents) {
103
+ if (!Array.isArray(documents)) {
104
+ throw new TypeError("parseSources takes a sequence of documents; write one document as [document]");
105
+ }
106
+ const result = [];
107
+ for (const [position, document] of documents.entries()) {
108
+ if (document instanceof SourceDocument) {
109
+ result.push(document);
110
+ }
111
+ else if (typeof document === "string") {
112
+ result.push(SourceDocument.file(document));
113
+ }
114
+ else if (isNamedContent(document)) {
115
+ result.push(SourceDocument.inline(document[0], document[1]));
116
+ }
117
+ else {
118
+ throw new TypeError(`document ${position} is of an unexpected form; expected a path, a ` +
119
+ `(name, content) pair or a SourceDocument`);
120
+ }
121
+ }
122
+ if (result.length === 0) {
123
+ throw new RangeError("parseSources needs at least one document");
124
+ }
125
+ const seen = new Set();
126
+ for (const document of result) {
127
+ const name = document.documentName;
128
+ if (seen.has(name)) {
129
+ throw new RangeError(`two documents are named ${JSON.stringify(name)}`);
130
+ }
131
+ seen.add(name);
132
+ }
133
+ return result;
134
+ }
@@ -7,9 +7,16 @@ export declare function statusName(code: Code): string;
7
7
  * knows what it asked about: a path, or a model held under a hash.
8
8
  */
9
9
  export type NotFoundSubject = "model" | "file";
10
+ /**
11
+ * What to make of an UNIMPLEMENTED the service answered with anyway, such as a
12
+ * capability-gated call refused by a service that checked its own list: the
13
+ * capabilities the call required, mapped to the MissingCapabilityError the
14
+ * preflight check meant to raise.
15
+ */
16
+ export type UnimplementedMapping = (details: string) => OpenSysMLError;
10
17
  /** Translates a failed call into the error for its status. */
11
- export declare function fromRpcError(error: unknown, notFound?: NotFoundSubject): OpenSysMLError;
18
+ export declare function fromRpcError(error: unknown, notFound?: NotFoundSubject, unimplemented?: UnimplementedMapping): OpenSysMLError;
12
19
  /** Translates a failed handshake, which says which service would not answer. */
13
20
  export declare function fromHandshakeError(error: unknown, origin: string): ServiceError;
14
21
  /** Awaits a call, translating whatever status it fails with. */
15
- export declare function callRpc<T>(call: Promise<T>, notFound?: NotFoundSubject): Promise<T>;
22
+ export declare function callRpc<T>(call: Promise<T>, notFound?: NotFoundSubject, unimplemented?: UnimplementedMapping): Promise<T>;
@@ -1,7 +1,7 @@
1
1
  // The client boundary for the service's statuses: a failed call is translated
2
2
  // into this client's error hierarchy, keeping the ConnectError as its cause.
3
3
  import { Code, ConnectError } from "@connectrpc/connect";
4
- import { InvalidRequestError, ModelFileNotFoundError, ModelNotFoundError, OpenSysMLError, ServiceError, ServiceTimeoutError, SymbolNotFoundError, UnsupportedOperationError, } from "./errors.js";
4
+ import { InvalidRequestError, ModelFileNotFoundError, ModelNotFoundError, OpenSysMLError, ServiceError, ServiceTimeoutError, ServiceUnavailableError, SymbolNotFoundError, UnsupportedOperationError, } from "./errors.js";
5
5
  /** A status code as the service's conformance scenarios spell it. */
6
6
  const STATUS_NAMES = new Map([
7
7
  [Code.Canceled, "CANCELLED"],
@@ -33,10 +33,11 @@ const CODE_ERRORS = new Map([
33
33
  [Code.OutOfRange, InvalidRequestError],
34
34
  [Code.DeadlineExceeded, ServiceTimeoutError],
35
35
  [Code.Canceled, ServiceTimeoutError],
36
+ [Code.Unavailable, ServiceUnavailableError],
36
37
  [Code.Unimplemented, UnsupportedOperationError],
37
38
  ]);
38
39
  /** Translates a failed call into the error for its status. */
39
- export function fromRpcError(error, notFound = "model") {
40
+ export function fromRpcError(error, notFound = "model", unimplemented) {
40
41
  // A capability refusal or a closed connection is already this client's own.
41
42
  if (error instanceof OpenSysMLError) {
42
43
  return error;
@@ -49,26 +50,41 @@ export function fromRpcError(error, notFound = "model") {
49
50
  if (connectError.code === Code.NotFound) {
50
51
  return notFoundError(message, status, notFound, connectError);
51
52
  }
52
- const cls = CODE_ERRORS.get(connectError.code) ?? ServiceError;
53
- const described = connectError.code === Code.Unavailable ? `sysml-grpc service unavailable: ${message}` : message;
53
+ if (connectError.code === Code.Unimplemented && unimplemented !== undefined) {
54
+ const refusal = unimplemented(connectError.rawMessage);
55
+ refusal.cause = connectError;
56
+ return refusal;
57
+ }
58
+ const unreachable = fetchRefused(connectError);
59
+ const cls = CODE_ERRORS.get(connectError.code) ??
60
+ (unreachable ? ServiceUnavailableError : ServiceError);
61
+ const described = connectError.code === Code.Unavailable || unreachable
62
+ ? `sysml-grpc service unavailable: ${message}`
63
+ : message;
54
64
  return new cls(described, { cause: connectError, code: status });
55
65
  }
56
66
  /** Translates a failed handshake, which says which service would not answer. */
57
67
  export function fromHandshakeError(error, origin) {
58
68
  const connectError = ConnectError.from(error);
59
- const cls = CODE_ERRORS.get(connectError.code) ?? ServiceError;
69
+ const cls = CODE_ERRORS.get(connectError.code) ??
70
+ (fetchRefused(connectError) ? ServiceUnavailableError : ServiceError);
60
71
  return new cls(`the sysml-grpc service at ${origin} did not answer: ${connectError.message}`, {
61
72
  cause: connectError,
62
73
  code: statusName(connectError.code),
63
74
  });
64
75
  }
76
+ // A browser fetch that never reached the service rejects with a TypeError,
77
+ // which ConnectError.from reports as UNKNOWN with that TypeError as its cause.
78
+ function fetchRefused(connectError) {
79
+ return (connectError.code === Code.Unknown && connectError.cause instanceof TypeError);
80
+ }
65
81
  /** Awaits a call, translating whatever status it fails with. */
66
- export async function callRpc(call, notFound = "model") {
82
+ export async function callRpc(call, notFound = "model", unimplemented) {
67
83
  try {
68
84
  return await call;
69
85
  }
70
86
  catch (error) {
71
- throw fromRpcError(error, notFound);
87
+ throw fromRpcError(error, notFound, unimplemented);
72
88
  }
73
89
  }
74
90
  // The service answers NOT_FOUND for a source file it cannot read, a model hash
@@ -82,7 +98,7 @@ function notFoundError(message, status, notFound, cause) {
82
98
  return new ModelNotFoundError(message, { cause, code: status });
83
99
  }
84
100
  if (lowered.includes("symbol not found")) {
85
- return new SymbolNotFoundError(symbolNameIn(message));
101
+ return new SymbolNotFoundError(symbolNameIn(message), [], { cause });
86
102
  }
87
103
  const cls = notFound === "file" ? ModelFileNotFoundError : ModelNotFoundError;
88
104
  return new cls(message, { cause, code: status });