spyret 0.1.0 → 0.2.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.
@@ -0,0 +1,272 @@
1
+ import { Graph } from 'graphlib';
2
+
3
+ interface IAtom {
4
+ id: string;
5
+ type: string;
6
+ label: string;
7
+ /**
8
+ * Optional key-value labels associated with this atom.
9
+ * Used for language-specific metadata that should be displayed prominently on nodes
10
+ * (e.g., Skolems in Alloy, annotations in other languages).
11
+ * These labels are styled differently from regular attributes - typically in the node's color.
12
+ */
13
+ labels?: Record<string, string[]>;
14
+ }
15
+ /**
16
+ * One tuple in a relation.
17
+ *
18
+ * A tuple owns its own arity: `atoms.length` is the truth, and `types` is
19
+ * this tuple's signature, one entry per atom. A relation may hold tuples of
20
+ * different arity (see {@link IRelation}), so nothing may read one tuple's
21
+ * arity as the arity of the relation around it.
22
+ */
23
+ interface ITuple {
24
+ atoms: string[];
25
+ types: string[];
26
+ }
27
+ interface IType {
28
+ id: string;
29
+ types: string[];
30
+ atoms: IAtom[];
31
+ isBuiltin: boolean;
32
+ }
33
+ /**
34
+ * A named set of tuples.
35
+ *
36
+ * Relations are RAGGED-TOLERANT: the tuples need not all have the same arity.
37
+ * Host languages hand us this routinely — two unrelated Python classes can both
38
+ * have a `foo` field, one holding pairs and one holding triples, and both are
39
+ * the relation `foo`. Alloy reaches the same place from the other side: two
40
+ * sigs may each declare a field `foo`, and their ids differ (`A<:foo`,
41
+ * `B<:foo`) while the name does not.
42
+ *
43
+ * Records with distinct IDs stay distinct in storage. The name is what
44
+ * selectors see: a name denotes the set union of all matching records.
45
+ * IDs are preserved for host reconstruction and exact-ID mutation. Rendering, the
46
+ * evaluators and the constraint layer all work tuple by tuple, so a ragged
47
+ * relation draws and queries correctly.
48
+ */
49
+ interface IRelation {
50
+ id: string;
51
+ name: string;
52
+ /**
53
+ * A SUMMARY of the tuples' column types, one entry per column, positional.
54
+ *
55
+ * It is only meaningful when every tuple has the same arity. When the tuples
56
+ * disagree — a ragged relation — no positional list can describe them, and
57
+ * this is `[]`.
58
+ *
59
+ * So `types.length` is NOT the relation's arity, and must never be read as
60
+ * one: on a ragged relation it is 0 no matter how wide the tuples are. Arity
61
+ * lives on the tuple (`ITuple.atoms.length`).
62
+ */
63
+ types: string[];
64
+ tuples: ITuple[];
65
+ }
66
+ interface IDataInstance {
67
+ getAtomType(id: string): IType;
68
+ getTypes(): readonly IType[];
69
+ getAtoms(): readonly IAtom[];
70
+ getRelations(): readonly IRelation[];
71
+ generateGraph(hideDisconnected: boolean, hideDisconnectedBuiltIns: boolean): Graph;
72
+ }
73
+
74
+ /** Spyret's data view of a validated capture, implementing Core's public contract. */
75
+ declare class CapturedDataInstance implements IDataInstance {
76
+ private datum;
77
+ private atoms;
78
+ private types;
79
+ constructor(datum: {
80
+ atoms: IAtom[];
81
+ relations: IRelation[];
82
+ types: IType[];
83
+ });
84
+ getAtoms(): readonly IAtom[];
85
+ getRelations(): readonly IRelation[];
86
+ getTypes(): readonly IType[];
87
+ getAtomType(id: string): IType;
88
+ generateGraph(hideDisconnected?: boolean, hideDisconnectedBuiltIns?: boolean): Graph;
89
+ }
90
+
91
+ /** Internal lossless numbers. IDataInstance stores their literal in the atom label. */
92
+ type PyretNumberPayload = {
93
+ version: 1;
94
+ } & ({
95
+ kind: 'integer';
96
+ value: string;
97
+ } | {
98
+ kind: 'rational';
99
+ numerator: string;
100
+ denominator: string;
101
+ } | {
102
+ kind: 'roughnum';
103
+ value: string;
104
+ });
105
+
106
+ interface ConstructorInfo {
107
+ name: string;
108
+ arity: number;
109
+ fields: string[];
110
+ mutableFields?: number[];
111
+ }
112
+
113
+ /** The adapter returns observations, never serialized values or display output. */
114
+ type PyretObservation = {
115
+ kind: 'primitive';
116
+ value: string | boolean | {
117
+ $pyretNumber: PyretNumberPayload;
118
+ };
119
+ } | {
120
+ kind: 'nothing';
121
+ } | {
122
+ kind: 'constructor';
123
+ identity: object;
124
+ info: ConstructorInfo;
125
+ values: unknown[];
126
+ } | {
127
+ kind: 'object';
128
+ fields: Array<[string, unknown]>;
129
+ } | {
130
+ kind: 'raw-array' | 'tuple';
131
+ values: unknown[];
132
+ } | {
133
+ kind: 'reference';
134
+ value: unknown;
135
+ } | {
136
+ kind: 'string-dict';
137
+ mutable: boolean;
138
+ sealed: boolean;
139
+ entries: Array<[string, unknown]>;
140
+ } | {
141
+ kind: 'table';
142
+ headers: string[];
143
+ rows: unknown[][];
144
+ };
145
+ interface PyretRuntimeAdapter {
146
+ /** Throw for unsupported state; capture adds the selected root and path. */
147
+ observe(value: unknown): PyretObservation;
148
+ }
149
+ /** Predicates must come from the runtime that owns the value, not another realm. */
150
+ interface PyretCaptureRuntime {
151
+ Any: unknown;
152
+ isNumber(value: unknown): boolean;
153
+ isNothing(value: unknown): boolean;
154
+ isDataValue(value: unknown): boolean;
155
+ isTuple(value: unknown): boolean;
156
+ isRef(value: unknown): boolean;
157
+ isFunction(value: unknown): boolean;
158
+ isMethod(value: unknown): boolean;
159
+ isOpaque(value: unknown): boolean;
160
+ isObject(value: unknown): boolean;
161
+ }
162
+ /**
163
+ * Adapter for upstream Pyret's JS runtime. All runtime representation reads are
164
+ * isolated here (including library backing stores). No printers, annotations,
165
+ * user functions, field dereferencing, or evaluator operations are invoked.
166
+ */
167
+ declare function createPyretRuntimeAdapter(runtime: PyretCaptureRuntime): PyretRuntimeAdapter;
168
+
169
+ type PyretCaptureJson = null | boolean | number | string | PyretCaptureJson[] | {
170
+ [key: string]: PyretCaptureJson;
171
+ };
172
+ interface PyretCaptureSnapshot {
173
+ format: 'spytial-pyret-capture';
174
+ version: 1;
175
+ datum: {
176
+ atoms: IAtom[];
177
+ relations: IRelation[];
178
+ types: IType[];
179
+ };
180
+ roots: Array<{
181
+ name: string;
182
+ atomId: string;
183
+ observation?: PyretCaptureJson;
184
+ }>;
185
+ provenance?: PyretCaptureJson;
186
+ }
187
+
188
+ /** The Pyret-specific part of diagramming. Rendering is supplied by the host. */
189
+ declare function prepareDiagram(value: unknown, runtime: Parameters<typeof createPyretRuntimeAdapter>[0]): {
190
+ snapshot: PyretCaptureSnapshot;
191
+ instance: CapturedDataInstance;
192
+ sourcePreview: string;
193
+ };
194
+
195
+ /** The owning runtime, with its standard field, list and execution APIs. */
196
+ interface SpytialRuntime extends PyretCaptureRuntime {
197
+ hasField(value: unknown, name: string): boolean;
198
+ getColonField(value: unknown, name: string): unknown;
199
+ getField(value: unknown, name: string): unknown;
200
+ num_to_fixnum(value: unknown): number;
201
+ ffi: {
202
+ isLink(value: unknown): boolean;
203
+ isEmpty(value: unknown): boolean;
204
+ isSome(value: unknown): boolean;
205
+ isNone(value: unknown): boolean;
206
+ };
207
+ safeCall(thunk: () => unknown, after: (value: unknown) => unknown, frame: string): unknown;
208
+ runThunk(thunk: () => unknown, done: (result: {
209
+ result?: unknown;
210
+ exn?: unknown;
211
+ }) => void): void;
212
+ isSuccessResult(result: unknown): boolean;
213
+ }
214
+
215
+ type Layout = {
216
+ nodes: {
217
+ mostSpecificType: string;
218
+ }[];
219
+ };
220
+ /** Only the Core operations used by the Pyret integration. Core is supplied separately. */
221
+ interface BrowserCore {
222
+ parseLayoutSpec(yaml: string): any;
223
+ Evaluators: {
224
+ SGraphQueryEvaluator: new () => {
225
+ initialize(input: {
226
+ sourceData: IDataInstance;
227
+ }): unknown;
228
+ };
229
+ };
230
+ LayoutInstance: new (spec: any, evaluator: any, instance: number, align: boolean) => {
231
+ generateLayout(data: IDataInstance): {
232
+ layout: Layout;
233
+ };
234
+ };
235
+ }
236
+ interface DiagramRuntime extends SpytialRuntime {
237
+ ffi: SpytialRuntime['ffi'] & {
238
+ makeMessageException(message: string): unknown;
239
+ throwMessageException(message: string): never;
240
+ };
241
+ makeOpaque(value: unknown): unknown;
242
+ makeFunction(fn: (...args: any[]) => unknown, name?: string): unknown;
243
+ makeModuleReturn(values: Record<string, unknown>, types: Record<string, unknown>): unknown;
244
+ checkString(value: unknown): void;
245
+ pauseStack(callback: (restarter: {
246
+ resume(value: unknown): void;
247
+ error(error: unknown): void;
248
+ /** Standard Pyret PausePackage thread handlers; used to observe Stop. */
249
+ handlers?: {
250
+ break(): void;
251
+ };
252
+ }) => void): unknown;
253
+ }
254
+ interface BrowserHost {
255
+ core: () => BrowserCore | Promise<BrowserCore>;
256
+ document: Document;
257
+ registerOutput(runtime: DiagramRuntime, handle: object, render: () => HTMLElement): void;
258
+ }
259
+ /** Append sections in discovery order. Interpretation and conflicts belong to Core. */
260
+ declare function combineSpytialSpecs(specs: readonly string[]): string;
261
+ /** Construct a fresh view for every display of an opaque diagram value. */
262
+ declare function createDiagramView(prepared: ReturnType<typeof prepareDiagram>, layout: Layout, document: Document): HTMLElement;
263
+ /** All Pyret-facing behavior lives here; hosts supply Core and output registration. */
264
+ declare function createPyretModule(runtime: DiagramRuntime, host: BrowserHost): unknown;
265
+ /** Compatibility adapter for stock CPO. Private APIs are isolated to this function. */
266
+ declare function registerCpoOutput(runtime: DiagramRuntime, handle: object, render: () => HTMLElement, jquery: (node: HTMLElement) => unknown): void;
267
+ /** Load only when used. The IDE may already supply a compatible Core bundle. */
268
+ declare function loadBrowserCore(document: Document, url: string): Promise<BrowserCore>;
269
+ /** Used by the packaged native Pyret module (js-file or gdrive-js). */
270
+ declare function createCpoModule(runtime: DiagramRuntime, document: Document, coreUrl: string): unknown;
271
+
272
+ export { type BrowserCore, type BrowserHost, type DiagramRuntime, combineSpytialSpecs, createCpoModule, createDiagramView, createPyretModule, loadBrowserCore, registerCpoOutput };
@@ -0,0 +1,272 @@
1
+ import { Graph } from 'graphlib';
2
+
3
+ interface IAtom {
4
+ id: string;
5
+ type: string;
6
+ label: string;
7
+ /**
8
+ * Optional key-value labels associated with this atom.
9
+ * Used for language-specific metadata that should be displayed prominently on nodes
10
+ * (e.g., Skolems in Alloy, annotations in other languages).
11
+ * These labels are styled differently from regular attributes - typically in the node's color.
12
+ */
13
+ labels?: Record<string, string[]>;
14
+ }
15
+ /**
16
+ * One tuple in a relation.
17
+ *
18
+ * A tuple owns its own arity: `atoms.length` is the truth, and `types` is
19
+ * this tuple's signature, one entry per atom. A relation may hold tuples of
20
+ * different arity (see {@link IRelation}), so nothing may read one tuple's
21
+ * arity as the arity of the relation around it.
22
+ */
23
+ interface ITuple {
24
+ atoms: string[];
25
+ types: string[];
26
+ }
27
+ interface IType {
28
+ id: string;
29
+ types: string[];
30
+ atoms: IAtom[];
31
+ isBuiltin: boolean;
32
+ }
33
+ /**
34
+ * A named set of tuples.
35
+ *
36
+ * Relations are RAGGED-TOLERANT: the tuples need not all have the same arity.
37
+ * Host languages hand us this routinely — two unrelated Python classes can both
38
+ * have a `foo` field, one holding pairs and one holding triples, and both are
39
+ * the relation `foo`. Alloy reaches the same place from the other side: two
40
+ * sigs may each declare a field `foo`, and their ids differ (`A<:foo`,
41
+ * `B<:foo`) while the name does not.
42
+ *
43
+ * Records with distinct IDs stay distinct in storage. The name is what
44
+ * selectors see: a name denotes the set union of all matching records.
45
+ * IDs are preserved for host reconstruction and exact-ID mutation. Rendering, the
46
+ * evaluators and the constraint layer all work tuple by tuple, so a ragged
47
+ * relation draws and queries correctly.
48
+ */
49
+ interface IRelation {
50
+ id: string;
51
+ name: string;
52
+ /**
53
+ * A SUMMARY of the tuples' column types, one entry per column, positional.
54
+ *
55
+ * It is only meaningful when every tuple has the same arity. When the tuples
56
+ * disagree — a ragged relation — no positional list can describe them, and
57
+ * this is `[]`.
58
+ *
59
+ * So `types.length` is NOT the relation's arity, and must never be read as
60
+ * one: on a ragged relation it is 0 no matter how wide the tuples are. Arity
61
+ * lives on the tuple (`ITuple.atoms.length`).
62
+ */
63
+ types: string[];
64
+ tuples: ITuple[];
65
+ }
66
+ interface IDataInstance {
67
+ getAtomType(id: string): IType;
68
+ getTypes(): readonly IType[];
69
+ getAtoms(): readonly IAtom[];
70
+ getRelations(): readonly IRelation[];
71
+ generateGraph(hideDisconnected: boolean, hideDisconnectedBuiltIns: boolean): Graph;
72
+ }
73
+
74
+ /** Spyret's data view of a validated capture, implementing Core's public contract. */
75
+ declare class CapturedDataInstance implements IDataInstance {
76
+ private datum;
77
+ private atoms;
78
+ private types;
79
+ constructor(datum: {
80
+ atoms: IAtom[];
81
+ relations: IRelation[];
82
+ types: IType[];
83
+ });
84
+ getAtoms(): readonly IAtom[];
85
+ getRelations(): readonly IRelation[];
86
+ getTypes(): readonly IType[];
87
+ getAtomType(id: string): IType;
88
+ generateGraph(hideDisconnected?: boolean, hideDisconnectedBuiltIns?: boolean): Graph;
89
+ }
90
+
91
+ /** Internal lossless numbers. IDataInstance stores their literal in the atom label. */
92
+ type PyretNumberPayload = {
93
+ version: 1;
94
+ } & ({
95
+ kind: 'integer';
96
+ value: string;
97
+ } | {
98
+ kind: 'rational';
99
+ numerator: string;
100
+ denominator: string;
101
+ } | {
102
+ kind: 'roughnum';
103
+ value: string;
104
+ });
105
+
106
+ interface ConstructorInfo {
107
+ name: string;
108
+ arity: number;
109
+ fields: string[];
110
+ mutableFields?: number[];
111
+ }
112
+
113
+ /** The adapter returns observations, never serialized values or display output. */
114
+ type PyretObservation = {
115
+ kind: 'primitive';
116
+ value: string | boolean | {
117
+ $pyretNumber: PyretNumberPayload;
118
+ };
119
+ } | {
120
+ kind: 'nothing';
121
+ } | {
122
+ kind: 'constructor';
123
+ identity: object;
124
+ info: ConstructorInfo;
125
+ values: unknown[];
126
+ } | {
127
+ kind: 'object';
128
+ fields: Array<[string, unknown]>;
129
+ } | {
130
+ kind: 'raw-array' | 'tuple';
131
+ values: unknown[];
132
+ } | {
133
+ kind: 'reference';
134
+ value: unknown;
135
+ } | {
136
+ kind: 'string-dict';
137
+ mutable: boolean;
138
+ sealed: boolean;
139
+ entries: Array<[string, unknown]>;
140
+ } | {
141
+ kind: 'table';
142
+ headers: string[];
143
+ rows: unknown[][];
144
+ };
145
+ interface PyretRuntimeAdapter {
146
+ /** Throw for unsupported state; capture adds the selected root and path. */
147
+ observe(value: unknown): PyretObservation;
148
+ }
149
+ /** Predicates must come from the runtime that owns the value, not another realm. */
150
+ interface PyretCaptureRuntime {
151
+ Any: unknown;
152
+ isNumber(value: unknown): boolean;
153
+ isNothing(value: unknown): boolean;
154
+ isDataValue(value: unknown): boolean;
155
+ isTuple(value: unknown): boolean;
156
+ isRef(value: unknown): boolean;
157
+ isFunction(value: unknown): boolean;
158
+ isMethod(value: unknown): boolean;
159
+ isOpaque(value: unknown): boolean;
160
+ isObject(value: unknown): boolean;
161
+ }
162
+ /**
163
+ * Adapter for upstream Pyret's JS runtime. All runtime representation reads are
164
+ * isolated here (including library backing stores). No printers, annotations,
165
+ * user functions, field dereferencing, or evaluator operations are invoked.
166
+ */
167
+ declare function createPyretRuntimeAdapter(runtime: PyretCaptureRuntime): PyretRuntimeAdapter;
168
+
169
+ type PyretCaptureJson = null | boolean | number | string | PyretCaptureJson[] | {
170
+ [key: string]: PyretCaptureJson;
171
+ };
172
+ interface PyretCaptureSnapshot {
173
+ format: 'spytial-pyret-capture';
174
+ version: 1;
175
+ datum: {
176
+ atoms: IAtom[];
177
+ relations: IRelation[];
178
+ types: IType[];
179
+ };
180
+ roots: Array<{
181
+ name: string;
182
+ atomId: string;
183
+ observation?: PyretCaptureJson;
184
+ }>;
185
+ provenance?: PyretCaptureJson;
186
+ }
187
+
188
+ /** The Pyret-specific part of diagramming. Rendering is supplied by the host. */
189
+ declare function prepareDiagram(value: unknown, runtime: Parameters<typeof createPyretRuntimeAdapter>[0]): {
190
+ snapshot: PyretCaptureSnapshot;
191
+ instance: CapturedDataInstance;
192
+ sourcePreview: string;
193
+ };
194
+
195
+ /** The owning runtime, with its standard field, list and execution APIs. */
196
+ interface SpytialRuntime extends PyretCaptureRuntime {
197
+ hasField(value: unknown, name: string): boolean;
198
+ getColonField(value: unknown, name: string): unknown;
199
+ getField(value: unknown, name: string): unknown;
200
+ num_to_fixnum(value: unknown): number;
201
+ ffi: {
202
+ isLink(value: unknown): boolean;
203
+ isEmpty(value: unknown): boolean;
204
+ isSome(value: unknown): boolean;
205
+ isNone(value: unknown): boolean;
206
+ };
207
+ safeCall(thunk: () => unknown, after: (value: unknown) => unknown, frame: string): unknown;
208
+ runThunk(thunk: () => unknown, done: (result: {
209
+ result?: unknown;
210
+ exn?: unknown;
211
+ }) => void): void;
212
+ isSuccessResult(result: unknown): boolean;
213
+ }
214
+
215
+ type Layout = {
216
+ nodes: {
217
+ mostSpecificType: string;
218
+ }[];
219
+ };
220
+ /** Only the Core operations used by the Pyret integration. Core is supplied separately. */
221
+ interface BrowserCore {
222
+ parseLayoutSpec(yaml: string): any;
223
+ Evaluators: {
224
+ SGraphQueryEvaluator: new () => {
225
+ initialize(input: {
226
+ sourceData: IDataInstance;
227
+ }): unknown;
228
+ };
229
+ };
230
+ LayoutInstance: new (spec: any, evaluator: any, instance: number, align: boolean) => {
231
+ generateLayout(data: IDataInstance): {
232
+ layout: Layout;
233
+ };
234
+ };
235
+ }
236
+ interface DiagramRuntime extends SpytialRuntime {
237
+ ffi: SpytialRuntime['ffi'] & {
238
+ makeMessageException(message: string): unknown;
239
+ throwMessageException(message: string): never;
240
+ };
241
+ makeOpaque(value: unknown): unknown;
242
+ makeFunction(fn: (...args: any[]) => unknown, name?: string): unknown;
243
+ makeModuleReturn(values: Record<string, unknown>, types: Record<string, unknown>): unknown;
244
+ checkString(value: unknown): void;
245
+ pauseStack(callback: (restarter: {
246
+ resume(value: unknown): void;
247
+ error(error: unknown): void;
248
+ /** Standard Pyret PausePackage thread handlers; used to observe Stop. */
249
+ handlers?: {
250
+ break(): void;
251
+ };
252
+ }) => void): unknown;
253
+ }
254
+ interface BrowserHost {
255
+ core: () => BrowserCore | Promise<BrowserCore>;
256
+ document: Document;
257
+ registerOutput(runtime: DiagramRuntime, handle: object, render: () => HTMLElement): void;
258
+ }
259
+ /** Append sections in discovery order. Interpretation and conflicts belong to Core. */
260
+ declare function combineSpytialSpecs(specs: readonly string[]): string;
261
+ /** Construct a fresh view for every display of an opaque diagram value. */
262
+ declare function createDiagramView(prepared: ReturnType<typeof prepareDiagram>, layout: Layout, document: Document): HTMLElement;
263
+ /** All Pyret-facing behavior lives here; hosts supply Core and output registration. */
264
+ declare function createPyretModule(runtime: DiagramRuntime, host: BrowserHost): unknown;
265
+ /** Compatibility adapter for stock CPO. Private APIs are isolated to this function. */
266
+ declare function registerCpoOutput(runtime: DiagramRuntime, handle: object, render: () => HTMLElement, jquery: (node: HTMLElement) => unknown): void;
267
+ /** Load only when used. The IDE may already supply a compatible Core bundle. */
268
+ declare function loadBrowserCore(document: Document, url: string): Promise<BrowserCore>;
269
+ /** Used by the packaged native Pyret module (js-file or gdrive-js). */
270
+ declare function createCpoModule(runtime: DiagramRuntime, document: Document, coreUrl: string): unknown;
271
+
272
+ export { type BrowserCore, type BrowserHost, type DiagramRuntime, combineSpytialSpecs, createCpoModule, createDiagramView, createPyretModule, loadBrowserCore, registerCpoOutput };