@brika/type-system 0.1.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.
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Zod → TypeDescriptor conversion.
3
+ *
4
+ * Converts Zod schemas to TypeDescriptor, handling:
5
+ * - Standard Zod types (string, number, object, array, union, enum, etc.)
6
+ * - GenericRef, PassthroughRef, ResolvedRef marker objects from @brika/sdk
7
+ *
8
+ * This function is used at block registration time (SDK side) to produce
9
+ * TypeDescriptor metadata that ships over IPC.
10
+ */
11
+
12
+ import type { TypeDescriptor } from './descriptor';
13
+
14
+ /**
15
+ * Convert a Zod schema or SDK marker ref to a TypeDescriptor.
16
+ *
17
+ * @param schemaOrRef - A Zod schema, GenericRef, PassthroughRef, or ResolvedRef
18
+ * @returns TypeDescriptor
19
+ */
20
+ export function zodToDescriptor(schemaOrRef: unknown): TypeDescriptor {
21
+ // Handle SDK marker refs (GenericRef, PassthroughRef, ResolvedRef)
22
+ if (isMarkerRef(schemaOrRef)) {
23
+ return markerToDescriptor(schemaOrRef);
24
+ }
25
+
26
+ // Handle Zod schemas via JSON Schema intermediary
27
+ // This avoids depending on Zod internals
28
+ return fromZodViaJsonSchema(schemaOrRef);
29
+ }
30
+
31
+ // ─────────────────────────────────────────────────────────────────────────────
32
+ // Marker Ref Detection
33
+ // ─────────────────────────────────────────────────────────────────────────────
34
+
35
+ interface MarkerRef {
36
+ __type: 'generic' | 'passthrough' | 'resolved';
37
+ __generic?: string;
38
+ __passthrough?: string;
39
+ __source?: string;
40
+ __configField?: string;
41
+ }
42
+
43
+ function isMarkerRef(value: unknown): value is MarkerRef {
44
+ return (
45
+ typeof value === 'object' &&
46
+ value !== null &&
47
+ '__type' in value &&
48
+ typeof (value as MarkerRef).__type === 'string' &&
49
+ ['generic', 'passthrough', 'resolved'].includes((value as MarkerRef).__type)
50
+ );
51
+ }
52
+
53
+ function markerToDescriptor(ref: MarkerRef): TypeDescriptor {
54
+ switch (ref.__type) {
55
+ case 'generic':
56
+ return { kind: 'generic', typeVar: ref.__generic ?? 'T' };
57
+ case 'passthrough':
58
+ return { kind: 'passthrough', sourcePortId: ref.__passthrough ?? '' };
59
+ case 'resolved':
60
+ return { kind: 'resolved', source: ref.__source ?? '', configField: ref.__configField ?? '' };
61
+ }
62
+ }
63
+
64
+ // ─────────────────────────────────────────────────────────────────────────────
65
+ // Zod → TypeDescriptor (via JSON Schema)
66
+ // ─────────────────────────────────────────────────────────────────────────────
67
+
68
+ function fromZodViaJsonSchema(schema: unknown): TypeDescriptor {
69
+ try {
70
+ // Try using Zod's built-in toJSONSchema (Zod v4+)
71
+ const zod = getZodModule(schema);
72
+ if (zod?.toJSONSchema) {
73
+ const jsonSchema = zod.toJSONSchema(schema, { unrepresentable: 'any' }) as Record<
74
+ string,
75
+ unknown
76
+ >;
77
+ return fromJsonSchema(jsonSchema);
78
+ }
79
+ } catch {
80
+ // Fall through to constructor-based detection
81
+ }
82
+
83
+ // Fallback: detect type from Zod constructor name
84
+ return fromZodConstructor(schema);
85
+ }
86
+
87
+ /**
88
+ * Try to get the Zod module from a schema instance.
89
+ * Avoids a direct import of Zod so this package stays dependency-free.
90
+ */
91
+ function getZodModule(
92
+ schema: unknown
93
+ ): { toJSONSchema: (s: unknown, opts: Record<string, unknown>) => unknown } | null {
94
+ try {
95
+ // Zod v4 schemas have a registry reference
96
+ const s = schema as { constructor?: { name?: string }; '~standard'?: unknown };
97
+ if (s.constructor?.name?.startsWith('Zod') || s['~standard']) {
98
+ // Dynamic import would be async, so we use a direct approach:
99
+ // The caller should have Zod in scope when using this function
100
+ // We look for toJSONSchema on the Zod namespace
101
+ const zod = (globalThis as Record<string, unknown>).__brika_zod as
102
+ | { toJSONSchema: (s: unknown, opts: Record<string, unknown>) => unknown }
103
+ | undefined;
104
+ return zod ?? null;
105
+ }
106
+ } catch {
107
+ // ignore
108
+ }
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Convert a JSON Schema to TypeDescriptor.
114
+ * This is the primary conversion path and handles the full JSON Schema spec subset we use.
115
+ */
116
+ export function fromJsonSchema(schema: Record<string, unknown>): TypeDescriptor {
117
+ const type = schema.type as string | undefined;
118
+
119
+ // anyOf → union
120
+ if (schema.anyOf) {
121
+ const variants = (schema.anyOf as Record<string, unknown>[]).map(fromJsonSchema);
122
+ return variants.length === 1 && variants[0] ? variants[0] : { kind: 'union', variants };
123
+ }
124
+
125
+ // oneOf → union
126
+ if (schema.oneOf) {
127
+ const variants = (schema.oneOf as Record<string, unknown>[]).map(fromJsonSchema);
128
+ return variants.length === 1 && variants[0] ? variants[0] : { kind: 'union', variants };
129
+ }
130
+
131
+ // enum
132
+ if (schema.enum) {
133
+ const values = schema.enum as (string | number)[];
134
+ return { kind: 'enum', values };
135
+ }
136
+
137
+ // const → literal
138
+ if ('const' in schema) {
139
+ return { kind: 'literal', value: schema.const as string | number | boolean };
140
+ }
141
+
142
+ switch (type) {
143
+ case 'string':
144
+ return { kind: 'primitive', type: 'string' };
145
+ case 'number':
146
+ case 'integer':
147
+ return { kind: 'primitive', type: 'number' };
148
+ case 'boolean':
149
+ return { kind: 'primitive', type: 'boolean' };
150
+ case 'null':
151
+ return { kind: 'primitive', type: 'null' };
152
+
153
+ case 'array': {
154
+ if (schema.items) {
155
+ return { kind: 'array', element: fromJsonSchema(schema.items as Record<string, unknown>) };
156
+ }
157
+ if (schema.prefixItems) {
158
+ const elements = (schema.prefixItems as Record<string, unknown>[]).map(fromJsonSchema);
159
+ return { kind: 'tuple', elements };
160
+ }
161
+ return { kind: 'array', element: { kind: 'unknown' } };
162
+ }
163
+
164
+ case 'object': {
165
+ const properties = schema.properties as Record<string, Record<string, unknown>> | undefined;
166
+ if (!properties) {
167
+ // Bare object or Record type
168
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
169
+ return {
170
+ kind: 'record',
171
+ value: fromJsonSchema(schema.additionalProperties as Record<string, unknown>),
172
+ };
173
+ }
174
+ return { kind: 'record', value: { kind: 'unknown' } };
175
+ }
176
+
177
+ const required = new Set((schema.required as string[] | undefined) ?? []);
178
+ const fields: Record<string, { type: TypeDescriptor; optional: boolean }> = {};
179
+
180
+ for (const [key, propSchema] of Object.entries(properties)) {
181
+ fields[key] = {
182
+ type: fromJsonSchema(propSchema),
183
+ optional: !required.has(key),
184
+ };
185
+ }
186
+
187
+ return { kind: 'object', fields };
188
+ }
189
+
190
+ default:
191
+ // Unknown or mixed type
192
+ return { kind: 'unknown' };
193
+ }
194
+ }
195
+
196
+ // ─────────────────────────────────────────────────────────────────────────────
197
+ // Fallback: Constructor-based detection
198
+ // ─────────────────────────────────────────────────────────────────────────────
199
+
200
+ function fromZodConstructor(schema: unknown): TypeDescriptor {
201
+ const name = getZodTypeName(schema);
202
+
203
+ switch (name) {
204
+ case 'string':
205
+ return { kind: 'primitive', type: 'string' };
206
+ case 'number':
207
+ return { kind: 'primitive', type: 'number' };
208
+ case 'boolean':
209
+ return { kind: 'primitive', type: 'boolean' };
210
+ case 'null':
211
+ return { kind: 'primitive', type: 'null' };
212
+ case 'any':
213
+ return { kind: 'any' };
214
+ case 'unknown':
215
+ return { kind: 'unknown' };
216
+ case 'undefined':
217
+ return { kind: 'unknown' };
218
+
219
+ case 'array': {
220
+ const def = getDef(schema);
221
+ const element = def?.element ?? def?.type;
222
+ return {
223
+ kind: 'array',
224
+ element: element ? fromZodConstructor(element) : { kind: 'unknown' },
225
+ };
226
+ }
227
+
228
+ case 'object': {
229
+ const def = getDef(schema);
230
+ const shape = def?.shape;
231
+ if (!shape || typeof shape !== 'object') {
232
+ return { kind: 'record', value: { kind: 'unknown' } };
233
+ }
234
+
235
+ const fields: Record<string, { type: TypeDescriptor; optional: boolean }> = {};
236
+ for (const [key, fieldSchema] of Object.entries(shape as Record<string, unknown>)) {
237
+ const fieldName = getZodTypeName(fieldSchema);
238
+ fields[key] = {
239
+ type: fromZodConstructor(fieldName === 'optional' ? getInner(fieldSchema) : fieldSchema),
240
+ optional: fieldName === 'optional',
241
+ };
242
+ }
243
+ return { kind: 'object', fields };
244
+ }
245
+
246
+ case 'optional':
247
+ case 'nullable':
248
+ return fromZodConstructor(getInner(schema));
249
+
250
+ case 'union': {
251
+ const def = getDef(schema);
252
+ const options = def?.options as unknown[] | undefined;
253
+ if (options) {
254
+ return { kind: 'union', variants: options.map(fromZodConstructor) };
255
+ }
256
+ return { kind: 'unknown' };
257
+ }
258
+
259
+ case 'enum': {
260
+ const def = getDef(schema);
261
+ const values = def?.entries ?? def?.values;
262
+ if (Array.isArray(values)) {
263
+ return { kind: 'enum', values: values as (string | number)[] };
264
+ }
265
+ return { kind: 'unknown' };
266
+ }
267
+
268
+ case 'record':
269
+ return { kind: 'record', value: { kind: 'unknown' } };
270
+
271
+ default:
272
+ return { kind: 'unknown' };
273
+ }
274
+ }
275
+
276
+ function getZodTypeName(schema: unknown): string {
277
+ const s = schema as { constructor?: { name?: string } };
278
+ const name = s?.constructor?.name;
279
+ if (name?.startsWith('Zod')) {
280
+ return name.slice(3).toLowerCase();
281
+ }
282
+ return 'unknown';
283
+ }
284
+
285
+ function getDef(schema: unknown): Record<string, unknown> | null {
286
+ const s = schema as { _def?: Record<string, unknown> };
287
+ return s?._def ?? null;
288
+ }
289
+
290
+ function getInner(schema: unknown): unknown {
291
+ const def = getDef(schema);
292
+ return def?.innerType ?? def?.unwrapped ?? schema;
293
+ }
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @brika/type-system — unified type system for workflow port types.
3
+ *
4
+ * Single source of truth for port types across backend and frontend.
5
+ * Provides:
6
+ * - TypeDescriptor: serializable, structural type representation
7
+ * - isCompatible: structural type compatibility checking
8
+ * - inferTypes: graph-based type inference for generic/passthrough/resolved ports
9
+ * - getCompletions: autocomplete items from resolved types
10
+ * - displayType: human-readable type name strings
11
+ * - zodToDescriptor: Zod schema → TypeDescriptor conversion
12
+ * - fromJsonSchema: JSON Schema → TypeDescriptor conversion
13
+ * - toJsonSchema: TypeDescriptor → JSON Schema conversion
14
+ */
15
+
16
+ export type { PrimitiveType, TypeDescriptor } from './descriptor';
17
+ export { T, isConcrete, isWildcard, needsResolution, parseTypeName, parsePortType, inferType } from './descriptor';
18
+
19
+ export { displayType } from './display';
20
+
21
+ export { isCompatible } from './compatibility';
22
+
23
+ export type { CompletionItem } from './autocomplete';
24
+ export { getCompletions } from './autocomplete';
25
+
26
+ export type { GraphEdge, GraphNode, PortTypeMap, TypeResolver } from './inference';
27
+ export { inferTypes, portKey } from './inference';
28
+
29
+ export { fromJsonSchema, zodToDescriptor } from './from-zod';
30
+
31
+ export { toJsonSchema } from './to-json-schema';
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Graph Type Inference Engine
3
+ *
4
+ * Resolves generic, passthrough, and resolved port types
5
+ * based on the workflow graph structure and connections.
6
+ *
7
+ * Algorithm:
8
+ * 1. Resolve external types ($resolve markers via TypeResolver)
9
+ * 2. Forward propagation: concrete output → connected generic input
10
+ * 3. Passthrough resolution: passthrough(inputId) → copy input's resolved type
11
+ * 4. Backward propagation: concrete input → connected generic output
12
+ * 5. Iterate until stable (max 10 passes)
13
+ */
14
+
15
+ import { type TypeDescriptor, isConcrete, needsResolution } from './descriptor';
16
+
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+ // Graph Types
19
+ // ─────────────────────────────────────────────────────────────────────────────
20
+
21
+ export interface GraphNode {
22
+ id: string;
23
+ ports: Record<string, { direction: 'input' | 'output'; type: TypeDescriptor }>;
24
+ config?: Record<string, unknown>;
25
+ }
26
+
27
+ export interface GraphEdge {
28
+ sourceNode: string;
29
+ sourcePort: string;
30
+ targetNode: string;
31
+ targetPort: string;
32
+ }
33
+
34
+ /**
35
+ * External type resolver — looks up types from external sources.
36
+ * e.g., spark registry for $resolve:spark:sparkType
37
+ */
38
+ export interface TypeResolver {
39
+ resolve(source: string, key: string): TypeDescriptor | null;
40
+ }
41
+
42
+ /** Map of "nodeId:portId" → resolved TypeDescriptor */
43
+ export type PortTypeMap = Map<string, TypeDescriptor>;
44
+
45
+ // ─────────────────────────────────────────────────────────────────────────────
46
+ // Main Entry Point
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+
49
+ const MAX_ITERATIONS = 10;
50
+
51
+ /**
52
+ * Infer types for all ports in a workflow graph.
53
+ * Returns a map of resolved types for every port.
54
+ */
55
+ export function inferTypes(
56
+ nodes: GraphNode[],
57
+ edges: GraphEdge[],
58
+ resolver?: TypeResolver
59
+ ): PortTypeMap {
60
+ const result: PortTypeMap = new Map();
61
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
62
+
63
+ // Build edge lookup maps
64
+ const incoming = buildIncomingMap(edges);
65
+ const outgoing = buildOutgoingMap(edges);
66
+
67
+ // Seed with declared types (concrete types go directly into result)
68
+ for (const node of nodes) {
69
+ for (const [portId, port] of Object.entries(node.ports)) {
70
+ const key = portKey(node.id, portId);
71
+ if (isConcrete(port.type)) {
72
+ result.set(key, port.type);
73
+ }
74
+ }
75
+ }
76
+
77
+ // Phase 1: Resolve external types ($resolve markers)
78
+ if (resolver) {
79
+ resolveExternalTypes(nodes, resolver, result);
80
+ }
81
+
82
+ // Phase 2-4: Iterate until stable
83
+ for (let iter = 0; iter < MAX_ITERATIONS; iter++) {
84
+ let changed = false;
85
+
86
+ for (const node of nodes) {
87
+ // Forward propagation: concrete output → connected generic input
88
+ if (propagateForward(node, incoming, nodeMap, result)) changed = true;
89
+
90
+ // Passthrough resolution
91
+ if (resolvePassthrough(node, result)) changed = true;
92
+
93
+ // Backward propagation: concrete input → connected generic output
94
+ if (propagateBackward(node, outgoing, nodeMap, result)) changed = true;
95
+ }
96
+
97
+ if (!changed) break;
98
+ }
99
+
100
+ return result;
101
+ }
102
+
103
+ // ─────────────────────────────────────────────────────────────────────────────
104
+ // Edge Lookup Maps
105
+ // ─────────────────────────────────────────────────────────────────────────────
106
+
107
+ interface EdgeTarget {
108
+ node: string;
109
+ port: string;
110
+ }
111
+
112
+ /** Map: targetNodeId → (targetPortId → source) */
113
+ type IncomingMap = Map<string, Map<string, EdgeTarget>>;
114
+
115
+ /** Map: sourceNodeId → (sourcePortId → targets[]) */
116
+ type OutgoingMap = Map<string, Map<string, EdgeTarget[]>>;
117
+
118
+ function buildIncomingMap(edges: GraphEdge[]): IncomingMap {
119
+ const map: IncomingMap = new Map();
120
+ for (const e of edges) {
121
+ if (!map.has(e.targetNode)) map.set(e.targetNode, new Map());
122
+ map.get(e.targetNode)?.set(e.targetPort, { node: e.sourceNode, port: e.sourcePort });
123
+ }
124
+ return map;
125
+ }
126
+
127
+ function buildOutgoingMap(edges: GraphEdge[]): OutgoingMap {
128
+ const map: OutgoingMap = new Map();
129
+ for (const e of edges) {
130
+ if (!map.has(e.sourceNode)) map.set(e.sourceNode, new Map());
131
+ const portMap = map.get(e.sourceNode);
132
+ if (portMap) {
133
+ if (!portMap.has(e.sourcePort)) portMap.set(e.sourcePort, []);
134
+ portMap.get(e.sourcePort)?.push({ node: e.targetNode, port: e.targetPort });
135
+ }
136
+ }
137
+ return map;
138
+ }
139
+
140
+ // ─────────────────────────────────────────────────────────────────────────────
141
+ // Phase 1: External Type Resolution
142
+ // ─────────────────────────────────────────────────────────────────────────────
143
+
144
+ function resolveExternalTypes(
145
+ nodes: GraphNode[],
146
+ resolver: TypeResolver,
147
+ result: PortTypeMap
148
+ ): void {
149
+ for (const node of nodes) {
150
+ for (const [portId, port] of Object.entries(node.ports)) {
151
+ if (port.type.kind !== 'resolved') continue;
152
+
153
+ const configValue = node.config?.[port.type.configField] as string | undefined;
154
+ if (!configValue) continue;
155
+
156
+ const resolved = resolver.resolve(port.type.source, configValue);
157
+ if (resolved && isConcrete(resolved)) {
158
+ result.set(portKey(node.id, portId), resolved);
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ // ─────────────────────────────────────────────────────────────────────────────
165
+ // Phase 2: Forward Propagation
166
+ // ─────────────────────────────────────────────────────────────────────────────
167
+
168
+ function propagateForward(
169
+ node: GraphNode,
170
+ incoming: IncomingMap,
171
+ nodeMap: Map<string, GraphNode>,
172
+ result: PortTypeMap
173
+ ): boolean {
174
+ let changed = false;
175
+ const nodeIncoming = incoming.get(node.id);
176
+ if (!nodeIncoming) return false;
177
+
178
+ for (const [inputPortId, port] of Object.entries(node.ports)) {
179
+ if (port.direction !== 'input') continue;
180
+
181
+ const key = portKey(node.id, inputPortId);
182
+ if (result.has(key)) continue; // already resolved
183
+
184
+ const source = nodeIncoming.get(inputPortId);
185
+ if (!source) continue;
186
+
187
+ // Check if the source's declared type or its inferred type is concrete
188
+ const sourceKey = portKey(source.node, source.port);
189
+ const sourceType = result.get(sourceKey);
190
+
191
+ if (sourceType && isConcrete(sourceType) && needsResolution(port.type)) {
192
+ result.set(key, sourceType);
193
+ changed = true;
194
+ }
195
+ }
196
+
197
+ return changed;
198
+ }
199
+
200
+ // ─────────────────────────────────────────────────────────────────────────────
201
+ // Phase 3: Passthrough Resolution
202
+ // ─────────────────────────────────────────────────────────────────────────────
203
+
204
+ function resolvePassthrough(node: GraphNode, result: PortTypeMap): boolean {
205
+ let changed = false;
206
+
207
+ for (const [portId, port] of Object.entries(node.ports)) {
208
+ if (port.type.kind !== 'passthrough') continue;
209
+
210
+ const key = portKey(node.id, portId);
211
+ if (result.has(key)) continue;
212
+
213
+ // Look up the referenced input port on the same node
214
+ const sourceInputKey = portKey(node.id, port.type.sourcePortId);
215
+ const resolvedInput = result.get(sourceInputKey);
216
+
217
+ if (resolvedInput && isConcrete(resolvedInput)) {
218
+ result.set(key, resolvedInput);
219
+ changed = true;
220
+ }
221
+ }
222
+
223
+ return changed;
224
+ }
225
+
226
+ // ─────────────────────────────────────────────────────────────────────────────
227
+ // Phase 4: Backward Propagation
228
+ // ─────────────────────────────────────────────────────────────────────────────
229
+
230
+ function propagateBackward(
231
+ node: GraphNode,
232
+ outgoing: OutgoingMap,
233
+ nodeMap: Map<string, GraphNode>,
234
+ result: PortTypeMap
235
+ ): boolean {
236
+ let changed = false;
237
+ const nodeOutgoing = outgoing.get(node.id);
238
+ if (!nodeOutgoing) return false;
239
+
240
+ for (const [outputPortId, port] of Object.entries(node.ports)) {
241
+ if (port.direction !== 'output') continue;
242
+
243
+ const key = portKey(node.id, outputPortId);
244
+ if (result.has(key)) continue; // already resolved
245
+
246
+ if (!needsResolution(port.type)) continue;
247
+
248
+ const targets = nodeOutgoing.get(outputPortId);
249
+ if (!targets) continue;
250
+
251
+ // If any connected input has a concrete type, use it
252
+ for (const target of targets) {
253
+ const targetKey = portKey(target.node, target.port);
254
+ const targetType = result.get(targetKey);
255
+
256
+ if (targetType && isConcrete(targetType)) {
257
+ result.set(key, targetType);
258
+ changed = true;
259
+ break;
260
+ }
261
+ }
262
+ }
263
+
264
+ return changed;
265
+ }
266
+
267
+ // ─────────────────────────────────────────────────────────────────────────────
268
+ // Utility
269
+ // ─────────────────────────────────────────────────────────────────────────────
270
+
271
+ function portKey(nodeId: string, portId: string): string {
272
+ return `${nodeId}:${portId}`;
273
+ }
274
+
275
+ export { portKey };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * TypeDescriptor → JSON Schema conversion.
3
+ *
4
+ * Produces standard JSON Schema (draft 2020-12 compatible) for API consumers
5
+ * that need JSON Schema format (e.g., UI schema rendering, documentation).
6
+ */
7
+
8
+ import type { TypeDescriptor } from './descriptor';
9
+
10
+ /**
11
+ * Convert a TypeDescriptor to a JSON Schema object.
12
+ */
13
+ export function toJsonSchema(desc: TypeDescriptor): Record<string, unknown> {
14
+ switch (desc.kind) {
15
+ case 'primitive':
16
+ return { type: desc.type === 'null' ? 'null' : desc.type };
17
+
18
+ case 'literal':
19
+ return { const: desc.value };
20
+
21
+ case 'object': {
22
+ const properties: Record<string, Record<string, unknown>> = {};
23
+ const required: string[] = [];
24
+
25
+ for (const [key, field] of Object.entries(desc.fields)) {
26
+ properties[key] = toJsonSchema(field.type);
27
+ if (!field.optional) required.push(key);
28
+ }
29
+
30
+ const schema: Record<string, unknown> = { type: 'object', properties };
31
+ if (required.length > 0) schema.required = required;
32
+ return schema;
33
+ }
34
+
35
+ case 'array':
36
+ return { type: 'array', items: toJsonSchema(desc.element) };
37
+
38
+ case 'tuple':
39
+ return { type: 'array', prefixItems: desc.elements.map(toJsonSchema) };
40
+
41
+ case 'union':
42
+ return { anyOf: desc.variants.map(toJsonSchema) };
43
+
44
+ case 'record':
45
+ return { type: 'object', additionalProperties: toJsonSchema(desc.value) };
46
+
47
+ case 'enum':
48
+ return { enum: [...desc.values] };
49
+
50
+ case 'any':
51
+ return {};
52
+
53
+ case 'unknown':
54
+ return {};
55
+
56
+ case 'generic':
57
+ return { description: `generic<${desc.typeVar}>` };
58
+
59
+ case 'passthrough':
60
+ return { description: `passthrough(${desc.sourcePortId})` };
61
+
62
+ case 'resolved':
63
+ return { description: `$resolve:${desc.source}:${desc.configField}` };
64
+ }
65
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }