@deepseek-ai/dsh-typert-generator 0.0.1-rc.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,745 @@
1
+ /**
2
+ * Model-driven Typert artifact emitter. It consumes only FaceModel and
3
+ * TypeGraph data; TypeScript compiler nodes are not part of this boundary.
4
+ * @module @deepseek-ai/dsh-typert-generator/emitter
5
+ */
6
+ import { Buffer } from 'node:buffer';
7
+ import { posix } from 'node:path';
8
+ import { GenMapping, addMapping, toEncodedMap } from '@jridgewell/gen-mapping';
9
+ import { TypeGraphRenderer } from "./renderer.js";
10
+ /** Failure to project a modeled construct into an emitted artifact. */
11
+ export class TypertEmitError extends Error {
12
+ name = 'TypertEmitError';
13
+ }
14
+ /** Emit generated runtime and type artifacts from one independently analyzed face. */
15
+ export class FaceModelEmitter {
16
+ face;
17
+ renderer;
18
+ /**
19
+ * Create an emitter for one face graph.
20
+ * @param face - independently analyzed face.
21
+ */
22
+ constructor(face) {
23
+ this.face = face;
24
+ this.renderer = new TypeGraphRenderer(face.graph);
25
+ }
26
+ /**
27
+ * Emit one modeled package.
28
+ * @param packageName - exact package name in the face model.
29
+ * @returns executable JavaScript and its precise declaration file.
30
+ */
31
+ emit(packageName) {
32
+ const packageModel = this.face.packages.find(candidate => candidate.name === packageName);
33
+ if (packageModel === undefined) {
34
+ throw new TypertEmitError(`typert emitter(${this.face.face}): package ${packageName} is not modeled on this face`);
35
+ }
36
+ const schemas = new SchemaEmitter(this.renderer, packageModel.schemas, invocationBoundaryRoots(packageModel.invocations));
37
+ const schemaArtifact = schemas.emit();
38
+ const runtimeModel = this.runtimeModel(packageModel);
39
+ const js = this.renderJs(packageModel, schemaArtifact, runtimeModel);
40
+ const dts = this.renderDts(packageModel, schemaArtifact);
41
+ return {
42
+ package: packageName,
43
+ face: this.face.face,
44
+ exports: packageModel.schemas.map(schema => schema.export.name),
45
+ js,
46
+ dts,
47
+ ...(this.face.face === 'host' && packageModel.invocations.length > 0
48
+ ? { remote: this.emitRemote(packageModel) }
49
+ : {}),
50
+ };
51
+ }
52
+ runtimeModel(packageModel) {
53
+ const services = packageModel.services.map((service) => {
54
+ const members = service.members.map(id => this.runtimeMember(this.renderer.member(id)));
55
+ return {
56
+ ...documentationLiteral(service),
57
+ key: service.key,
58
+ exportName: service.export.name,
59
+ members,
60
+ types: this.runtimeTypes(this.renderer.declarationClosureForMembers(service.members), service.symbol),
61
+ };
62
+ });
63
+ const events = packageModel.events.map((event) => {
64
+ const node = this.renderer.node(event.signature);
65
+ if (node.kind !== 'function') {
66
+ throw new TypertEmitError(`typert emitter(${this.face.face}): event ${event.name} is not a function type`);
67
+ }
68
+ return {
69
+ ...documentationLiteral(event),
70
+ name: event.name,
71
+ ...(event.mode === undefined ? {} : { mode: event.mode }),
72
+ signature: `${quote(event.name)}${this.renderer.renderSignature(node.signature)}`,
73
+ };
74
+ });
75
+ const objects = packageModel.objects.map((object) => {
76
+ const declaration = this.renderer.declaration(object.symbol);
77
+ return {
78
+ ...documentationLiteral(object),
79
+ name: declaration.name,
80
+ exportName: object.export.name,
81
+ members: declaration.members.map(member => this.runtimeMember(member)),
82
+ types: this.runtimeTypes(this.renderer.declarationClosureForMembers(declaration.members.map(member => member.id)), declaration.id),
83
+ };
84
+ });
85
+ return { services, events, objects };
86
+ }
87
+ runtimeMember(member) {
88
+ return {
89
+ kind: member.kind,
90
+ name: member.name,
91
+ signature: this.renderer.renderMember(member, true),
92
+ ...(member.summary === undefined ? {} : { summary: member.summary }),
93
+ ...(member.jsDoc === undefined ? {} : { jsDoc: member.jsDoc }),
94
+ };
95
+ }
96
+ runtimeTypes(declarations, root) {
97
+ return declarations
98
+ .filter(declaration => declaration.id !== root)
99
+ .map(declaration => ({
100
+ name: declaration.name,
101
+ declaration: this.renderer.renderDeclaration(declaration.id),
102
+ }))
103
+ .sort((left, right) => left.name.localeCompare(right.name));
104
+ }
105
+ renderJs(packageModel, schemas, runtimeModel) {
106
+ const lines = [
107
+ '/* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */',
108
+ ];
109
+ if (schemas.definitions.length > 0)
110
+ lines.push('import { z } from \'zod\'', '');
111
+ lines.push(...schemas.definitions);
112
+ if (schemas.definitions.length > 0)
113
+ lines.push('');
114
+ for (const schema of schemas.exports)
115
+ lines.push(`export const ${schema.exportName} = ${schema.internalName}`);
116
+ if (schemas.exports.length > 0)
117
+ lines.push('');
118
+ const model = JSON.stringify(runtimeModel, null, 2);
119
+ lines.push('export const TYPERT = {');
120
+ lines.push(` package: ${quote(packageModel.name)},`);
121
+ lines.push(` face: ${quote(this.face.face)},`);
122
+ lines.push(' schemas: [');
123
+ for (const schema of schemas.exports) {
124
+ lines.push(` { name: ${quote(schema.exportName)}, schema: ${schema.exportName} },`);
125
+ }
126
+ lines.push(' ],');
127
+ lines.push(' invocations: [');
128
+ for (const invocation of packageModel.invocations) {
129
+ lines.push(`${indent(this.invocationLiteral(invocation, schemas), 4)},`);
130
+ }
131
+ lines.push(' ],');
132
+ lines.push(` model: ${indent(model, 2).trimStart()},`);
133
+ lines.push('}');
134
+ return `${lines.join('\n')}\n`;
135
+ }
136
+ renderDts(packageModel, schemas) {
137
+ const imports = new Map();
138
+ for (const schema of schemas.exports) {
139
+ const specifier = packageExportSpecifier(packageModel.name, schema.model.export.subpath);
140
+ const names = imports.get(specifier) ?? [];
141
+ names.push(`${schema.model.export.name} as ${schema.exportName}$source`);
142
+ imports.set(specifier, names);
143
+ }
144
+ const lines = [
145
+ '/* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */',
146
+ ];
147
+ if (schemas.exports.length > 0)
148
+ lines.splice(1, 0, 'import type { z } from \'zod\'');
149
+ for (const [specifier, names] of [...imports].sort(([left], [right]) => left.localeCompare(right))) {
150
+ lines.push(`import type { ${names.sort().join(', ')} } from ${quote(specifier)}`);
151
+ }
152
+ lines.push('');
153
+ for (const schema of schemas.exports) {
154
+ lines.push(`export declare const ${schema.exportName}: z.ZodType<${schema.exportName}$source>`);
155
+ }
156
+ if (schemas.exports.length > 0)
157
+ lines.push('');
158
+ // The Loader validates and narrows this generated module boundary before
159
+ // registration. Keeping the public declaration unknown prevents every
160
+ // contributing business package from depending on the runtime registry.
161
+ lines.push('export declare const TYPERT: unknown');
162
+ return `${lines.join('\n')}\n`;
163
+ }
164
+ emitRemote(packageModel) {
165
+ const schemas = new SchemaEmitter(this.renderer, [], invocationBoundaryRoots(packageModel.invocations)).emit();
166
+ const lines = [
167
+ '/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */',
168
+ ];
169
+ if (schemas.definitions.length > 0)
170
+ lines.push('import { z } from \'zod\'', '');
171
+ lines.push(...schemas.definitions);
172
+ if (schemas.definitions.length > 0)
173
+ lines.push('');
174
+ lines.push('export const TYPERT_REMOTE = {');
175
+ lines.push(` package: ${quote(packageModel.name)},`);
176
+ lines.push(' descriptors: [');
177
+ for (const invocation of packageModel.invocations) {
178
+ lines.push(`${indent(this.invocationLiteral(invocation, schemas), 4)},`);
179
+ }
180
+ lines.push(' ],');
181
+ lines.push('}');
182
+ lines.push('');
183
+ lines.push('export default TYPERT_REMOTE');
184
+ const declaration = this.renderRemoteDts(packageModel);
185
+ return {
186
+ js: `${lines.join('\n')}\n`,
187
+ ...declaration,
188
+ };
189
+ }
190
+ invocationLiteral(invocation, schemas) {
191
+ const lines = [
192
+ '{',
193
+ ` id: ${quote(invocation.id)},`,
194
+ ` service: ${quote(invocation.service)},`,
195
+ ` namespace: ${quote(invocation.namespace)},`,
196
+ ` method: ${quote(invocation.method)},`,
197
+ ];
198
+ if (invocation.implementation !== undefined) {
199
+ lines.push(` implementation: ${quote(invocation.implementation)},`);
200
+ }
201
+ if (invocation.invocation.kind === 'direct') {
202
+ lines.push(' invocation: { kind: \'direct\' },');
203
+ }
204
+ else {
205
+ lines.push(' invocation: {');
206
+ lines.push(' kind: \'context\',');
207
+ lines.push(` context: ${quote(invocation.invocation.context)},`);
208
+ lines.push(` wire: ${quote(invocation.invocation.wire)},`);
209
+ lines.push(` codec: ${indent(strictCodec(invocation.invocation.boundary, schemas.boundary(contextBoundaryKey(invocation))), 4).trimStart()},`);
210
+ lines.push(' },');
211
+ }
212
+ if (invocation.scope !== undefined) {
213
+ lines.push(' scope: {');
214
+ lines.push(` context: ${quote(invocation.scope.context)},`);
215
+ lines.push(` wire: ${quote(invocation.scope.wire)},`);
216
+ lines.push(' },');
217
+ }
218
+ lines.push(' parameters: [');
219
+ invocation.parameters.forEach((parameter, index) => {
220
+ lines.push(' {');
221
+ lines.push(` name: ${quote(parameter.name)},`);
222
+ lines.push(` wire: ${quote(parameter.wire)},`);
223
+ lines.push(` source: ${quote(parameter.source)},`);
224
+ if (parameter.lookup !== undefined)
225
+ lines.push(` lookup: ${quote(parameter.lookup)},`);
226
+ lines.push(` codec: ${indent(strictCodec(parameter.boundary, schemas.boundary(parameterBoundaryKey(invocation, index))), 6).trimStart()},`);
227
+ lines.push(' },');
228
+ });
229
+ lines.push(' ],');
230
+ if (invocation.cancellation !== undefined) {
231
+ lines.push(" cancellation: { parameter: 'signal' },");
232
+ }
233
+ lines.push(` result: ${indent(strictCodec(invocation.result, schemas.boundary(resultBoundaryKey(invocation))), 2).trimStart()},`);
234
+ lines.push(` sourceLocation: ${JSON.stringify(invocation.location)},`);
235
+ lines.push('}');
236
+ return lines.join('\n');
237
+ }
238
+ renderRemoteDts(packageModel) {
239
+ const imports = remoteImports(packageModel.invocations);
240
+ const referenceNames = allocateRemoteImportNames(imports);
241
+ const grouped = new Map();
242
+ for (const imported of imports) {
243
+ const values = grouped.get(imported.specifier) ?? [];
244
+ values.push({
245
+ name: imported.name,
246
+ local: referenceNames.get(imported.symbol),
247
+ });
248
+ grouped.set(imported.specifier, values);
249
+ }
250
+ const lines = [
251
+ '/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */',
252
+ 'import type {',
253
+ ' TypeRTRemoteContribution,',
254
+ '} from \'@deepseek-ai/dsh-type-meta\'',
255
+ ];
256
+ const sourceMap = new GenMapping({ file: 'typert.remote-client.d.ts' });
257
+ for (const [specifier, values] of [...grouped].sort(([left], [right]) => left.localeCompare(right))) {
258
+ const names = values.sort((left, right) => left.local.localeCompare(right.local)).map(value => value.name === value.local ? value.name : `${value.name} as ${value.local}`);
259
+ lines.push(`import type { ${names.join(', ')} } from ${quote(specifier)}`);
260
+ }
261
+ lines.push('');
262
+ lines.push('declare module \'@deepseek-ai/dsh-type-meta\' {');
263
+ const direct = packageModel.invocations.filter(invocation => invocation.invocation.kind === 'direct');
264
+ const scoped = packageModel.invocations.filter(invocation => invocation.invocation.kind === 'context' || invocation.scope !== undefined);
265
+ if (direct.length > 0) {
266
+ for (const namespace of uniqueNamespaces(direct)) {
267
+ lines.push(` interface ${remoteNamespaceInterface(namespace)} {`);
268
+ for (const invocation of direct.filter(candidate => candidate.namespace === namespace)) {
269
+ this.pushRemoteNamespaceSignature(lines, sourceMap, packageModel, invocation, referenceNames);
270
+ }
271
+ lines.push(' }');
272
+ }
273
+ lines.push(' interface TypeRTRemoteMap {');
274
+ for (const invocation of direct) {
275
+ this.pushRemoteSignature(lines, sourceMap, packageModel, invocation, referenceNames, false);
276
+ }
277
+ lines.push(' }');
278
+ lines.push(' interface TypeRTRemoteNamespaceMap {');
279
+ for (const namespace of uniqueNamespaces(direct)) {
280
+ lines.push(` ${quote(namespace)}: ${remoteNamespaceInterface(namespace)}`);
281
+ }
282
+ lines.push(' }');
283
+ }
284
+ if (scoped.length > 0) {
285
+ lines.push(' interface TypeRTRemoteScopeMap {');
286
+ for (const invocation of scoped) {
287
+ this.pushRemoteSignature(lines, sourceMap, packageModel, invocation, referenceNames, true);
288
+ }
289
+ lines.push(' }');
290
+ }
291
+ lines.push('}');
292
+ lines.push('');
293
+ lines.push('export declare const TYPERT_REMOTE: TypeRTRemoteContribution');
294
+ lines.push('export default TYPERT_REMOTE');
295
+ lines.push('//# sourceMappingURL=typert.remote-client.d.ts.map');
296
+ return {
297
+ dts: `${lines.join('\n')}\n`,
298
+ dtsMap: `${JSON.stringify(toEncodedMap(sourceMap))}\n`,
299
+ };
300
+ }
301
+ pushRemoteSignature(lines, sourceMap, packageModel, invocation, referenceNames, scoped) {
302
+ const signature = this.remoteSignature(invocation, referenceNames, scoped);
303
+ const keyLength = signature.indexOf(': (');
304
+ if (keyLength < 0)
305
+ throw new TypertEmitError(`Remote signature ${invocation.id} has no property delimiter`);
306
+ this.pushMappedRemoteSignature(lines, sourceMap, packageModel, invocation, signature, keyLength);
307
+ }
308
+ pushRemoteNamespaceSignature(lines, sourceMap, packageModel, invocation, referenceNames) {
309
+ const key = renderRemotePropertyName(invocation.method);
310
+ const signature = `${key}: ${this.remoteFunctionType(invocation, referenceNames, false)}`;
311
+ this.pushMappedRemoteSignature(lines, sourceMap, packageModel, invocation, signature, key.length);
312
+ }
313
+ pushMappedRemoteSignature(lines, sourceMap, packageModel, invocation, signature, keyLength) {
314
+ lines.push(` ${signature}`);
315
+ const generatedLine = lines.length;
316
+ const source = remoteDeclarationSource(packageModel, invocation);
317
+ addMapping(sourceMap, {
318
+ generated: { line: generatedLine, column: 4 },
319
+ source,
320
+ original: { line: invocation.location.line, column: invocation.location.column - 1 },
321
+ name: invocation.method,
322
+ });
323
+ addMapping(sourceMap, {
324
+ generated: { line: generatedLine, column: 4 + keyLength },
325
+ });
326
+ }
327
+ remoteSignature(invocation, referenceNames, scoped) {
328
+ const context = invocation.invocation.kind === 'context'
329
+ ? invocation.invocation.context
330
+ : invocation.scope?.context;
331
+ const key = scoped
332
+ ? `${context}:${invocation.namespace}/${invocation.method}`
333
+ : `${invocation.namespace}/${invocation.method}`;
334
+ return `${quote(key)}: ${this.remoteFunctionType(invocation, referenceNames, scoped)}`;
335
+ }
336
+ remoteFunctionType(invocation, referenceNames, scoped) {
337
+ const parameters = invocation.parameters.filter(parameter => !scoped || invocation.invocation.kind === 'context' || parameter.wire !== invocation.scope?.wire).map(parameter => `${safeIdentifier(parameter.wire)}: ${this.renderer.renderType(parameter.boundary.type, referenceNames)}`);
338
+ if (invocation.cancellation !== undefined)
339
+ parameters.push('signal?: AbortSignal');
340
+ const result = this.renderer.renderType(invocation.result.type, referenceNames);
341
+ return `(${parameters.join(', ')}) => Promise<${result}>`;
342
+ }
343
+ }
344
+ function remoteDeclarationSource(packageModel, invocation) {
345
+ const relativeSource = posix.relative(packageModel.root, invocation.location.file);
346
+ if (relativeSource === '' || relativeSource === '..' || relativeSource.startsWith('../') || posix.isAbsolute(relativeSource)) {
347
+ throw new TypertEmitError(`Remote declaration ${invocation.id} is outside its package root ${packageModel.root}`);
348
+ }
349
+ return posix.join('..', relativeSource);
350
+ }
351
+ function uniqueNamespaces(invocations) {
352
+ return [...new Set(invocations.map(invocation => invocation.namespace))].sort();
353
+ }
354
+ function remoteNamespaceInterface(namespace) {
355
+ return `TypeRTRemoteNamespace$${Buffer.from(namespace, 'utf8').toString('hex')}`;
356
+ }
357
+ class SchemaEmitter {
358
+ renderer;
359
+ schemas;
360
+ boundaries;
361
+ names = new Map();
362
+ boundaryNames = new Map();
363
+ declarations;
364
+ constructor(renderer, schemas, boundaries) {
365
+ this.renderer = renderer;
366
+ this.schemas = schemas;
367
+ this.boundaries = boundaries;
368
+ const declarations = new Map();
369
+ for (const schema of schemas) {
370
+ for (const declaration of renderer.declarationClosureForTypes([schema.type])) {
371
+ declarations.set(declaration.id, declaration);
372
+ }
373
+ }
374
+ for (const boundary of boundaries) {
375
+ for (const declaration of renderer.declarationClosureForTypes([boundary.type])) {
376
+ declarations.set(declaration.id, declaration);
377
+ }
378
+ }
379
+ this.declarations = renderer.graph.declarations.filter(declaration => declarations.has(declaration.id));
380
+ const identifiers = new Set();
381
+ for (const declaration of this.declarations) {
382
+ const base = `${safeIdentifier(declaration.name)}$schema`;
383
+ let name = base;
384
+ let suffix = 2;
385
+ while (identifiers.has(name))
386
+ name = `${base}${String(suffix++)}`;
387
+ identifiers.add(name);
388
+ this.names.set(declaration.id, name);
389
+ }
390
+ for (const boundary of boundaries) {
391
+ const base = `${safeIdentifier(boundary.key)}$schema`;
392
+ let name = base;
393
+ let suffix = 2;
394
+ while (identifiers.has(name))
395
+ name = `${base}${String(suffix++)}`;
396
+ identifiers.add(name);
397
+ this.boundaryNames.set(boundary.key, name);
398
+ }
399
+ }
400
+ emit() {
401
+ const definitions = this.declarations.map(declaration => this.declarationDefinition(declaration));
402
+ for (const boundary of this.boundaries) {
403
+ definitions.push(`const ${this.boundaryName(boundary.key)} = ${this.typeSchema(boundary.type)}`);
404
+ }
405
+ const exports = this.schemas.map((model) => ({
406
+ model,
407
+ exportName: safeIdentifier(model.export.name),
408
+ internalName: this.exportSchemaName(model),
409
+ }));
410
+ return {
411
+ definitions,
412
+ exports,
413
+ boundary: key => this.boundaryName(key),
414
+ };
415
+ }
416
+ declarationDefinition(declaration) {
417
+ const name = this.schemaName(declaration.id);
418
+ if (declaration.typeParameters.length === 0) {
419
+ return `const ${name} = ${this.declarationSchema(declaration, new Map())}`;
420
+ }
421
+ const parameters = declaration.typeParameters.map((parameter, index) => [`type${String(index)}$schema`, parameter.id]);
422
+ const substitutions = new Map(parameters.map(([schema, id]) => [id, schema]));
423
+ return `const ${name} = (${parameters.map(([schema]) => schema).join(', ')}) => ${this.declarationSchema(declaration, substitutions)}`;
424
+ }
425
+ declarationSchema(declaration, substitutions) {
426
+ if (declaration.kind === 'enum') {
427
+ this.fail(declaration.name, 'enum declarations have no Zod projection');
428
+ }
429
+ if (declaration.kind === 'alias') {
430
+ if (declaration.type === undefined)
431
+ this.fail(declaration.name, 'alias has no modeled type');
432
+ return this.describe(this.typeSchema(declaration.type, substitutions), declaration);
433
+ }
434
+ const own = this.objectSchema(declaration.members, declaration.name, substitutions);
435
+ let result = own;
436
+ for (const heritage of declaration.extends) {
437
+ result = `z.intersection(${this.typeSchema(heritage, substitutions)}, ${result})`;
438
+ }
439
+ return this.describe(result, declaration);
440
+ }
441
+ typeSchema(id, substitutions = new Map()) {
442
+ const node = this.renderer.node(id);
443
+ switch (node.kind) {
444
+ case 'keyword': return this.keywordSchema(node.name);
445
+ case 'literal': return `z.literal(${node.text})`;
446
+ case 'parenthesized': return this.typeSchema(node.type, substitutions);
447
+ case 'reference': return this.referenceSchema(node, substitutions);
448
+ case 'union': {
449
+ if (node.types.length === 0)
450
+ return 'z.never()';
451
+ if (node.types.length === 1)
452
+ return this.typeSchema(node.types[0], substitutions);
453
+ return `z.union([${node.types.map(type => this.typeSchema(type, substitutions)).join(', ')}])`;
454
+ }
455
+ case 'intersection': {
456
+ const [head, ...tail] = node.types;
457
+ if (head === undefined)
458
+ return 'z.unknown()';
459
+ return tail.reduce((left, right) => `z.intersection(${left}, ${this.typeSchema(right, substitutions)})`, this.typeSchema(head, substitutions));
460
+ }
461
+ case 'array': return `z.array(${this.typeSchema(node.element, substitutions)})`;
462
+ case 'tuple': {
463
+ const fixed = node.elements.filter(element => !element.rest);
464
+ const rest = node.elements.find(element => element.rest);
465
+ let schema = `z.tuple([${fixed.map(element => this.optional(this.typeSchema(element.type, substitutions), element.optional)).join(', ')}])`;
466
+ if (rest !== undefined)
467
+ schema += `.rest(${this.tupleRestSchema(rest.type, substitutions)})`;
468
+ return schema;
469
+ }
470
+ case 'object': return this.objectSchema(node.members, id, substitutions);
471
+ case 'operator':
472
+ case 'indexed-access':
473
+ case 'conditional':
474
+ case 'infer':
475
+ case 'mapped':
476
+ case 'template-literal':
477
+ case 'type-query':
478
+ case 'import-type':
479
+ case 'predicate':
480
+ case 'function':
481
+ case 'constructor':
482
+ case 'this': return this.unsupported(node);
483
+ }
484
+ }
485
+ referenceSchema(node, substitutions) {
486
+ if (node.target.kind === 'declaration') {
487
+ const name = this.schemaName(node.target.symbol);
488
+ const declaration = this.renderer.declaration(node.target.symbol);
489
+ if (declaration.typeParameters.length === 0) {
490
+ if (node.arguments.length > 0) {
491
+ this.fail(node.name, `non-generic declaration received ${String(node.arguments.length)} type arguments`);
492
+ }
493
+ return `z.lazy(() => ${name})`;
494
+ }
495
+ const arguments_ = this.declarationArguments(node, declaration, substitutions);
496
+ return `z.lazy(() => ${name}(${arguments_.join(', ')}))`;
497
+ }
498
+ if (node.target.kind === 'type-parameter') {
499
+ if (node.arguments.length > 0)
500
+ this.fail(node.name, 'type parameter reference cannot receive type arguments');
501
+ const schema = substitutions.get(node.target.parameter);
502
+ if (schema === undefined)
503
+ this.fail(node.name, 'type parameter has no schema substitution');
504
+ return schema;
505
+ }
506
+ if (node.target.kind === 'standard') {
507
+ switch (node.target.name) {
508
+ case 'Array':
509
+ case 'ReadonlyArray': {
510
+ const element = node.arguments[0];
511
+ if (element === undefined)
512
+ this.fail(node.name, 'array reference has no element type');
513
+ return this.readonly(`z.array(${this.typeSchema(element, substitutions)})`, node.target.name === 'ReadonlyArray');
514
+ }
515
+ case 'Record': {
516
+ const key = node.arguments[0];
517
+ const value = node.arguments[1];
518
+ if (key === undefined || value === undefined)
519
+ this.fail(node.name, 'Record requires key and value types');
520
+ return `z.record(${this.typeSchema(key, substitutions)}, ${this.typeSchema(value, substitutions)})`;
521
+ }
522
+ case 'Date': return 'z.date()';
523
+ default: this.fail(node.name, `standard type ${node.target.name} has no Zod projection`);
524
+ }
525
+ }
526
+ this.fail(node.name, `${node.target.kind} reference has no Zod projection`);
527
+ }
528
+ declarationArguments(node, declaration, substitutions) {
529
+ if (node.arguments.length > declaration.typeParameters.length) {
530
+ this.fail(node.name, `generic declaration accepts ${String(declaration.typeParameters.length)} type arguments but received ${String(node.arguments.length)}`);
531
+ }
532
+ const resolved = new Map(substitutions);
533
+ const arguments_ = [];
534
+ for (const [index, parameter] of declaration.typeParameters.entries()) {
535
+ const argument = node.arguments[index];
536
+ const schema = argument === undefined
537
+ ? parameter.default === undefined
538
+ ? this.fail(node.name, `missing type argument ${parameter.name}`)
539
+ : this.typeSchema(parameter.default, resolved)
540
+ : this.typeSchema(argument, substitutions);
541
+ arguments_.push(schema);
542
+ resolved.set(parameter.id, schema);
543
+ }
544
+ return arguments_;
545
+ }
546
+ tupleRestSchema(id, substitutions) {
547
+ const node = this.renderer.node(id);
548
+ if (node.kind === 'array')
549
+ return this.typeSchema(node.element, substitutions);
550
+ if (node.kind === 'reference'
551
+ && node.target.kind === 'standard'
552
+ && (node.target.name === 'Array' || node.target.name === 'ReadonlyArray')) {
553
+ const element = node.arguments[0];
554
+ if (element === undefined)
555
+ this.fail(node.name, 'tuple rest array has no element type');
556
+ return this.typeSchema(element, substitutions);
557
+ }
558
+ this.fail(id, 'tuple rest element must retain an array type');
559
+ }
560
+ objectSchema(members, subject, substitutions) {
561
+ const properties = [];
562
+ const indices = [];
563
+ let symbolMembers = 0;
564
+ for (const member of members) {
565
+ if (member.static || member.visibility !== 'public')
566
+ continue;
567
+ if (member.computed === 'symbol') {
568
+ symbolMembers++;
569
+ continue;
570
+ }
571
+ if (member.computed === 'dynamic') {
572
+ this.fail(subject, `computed member ${member.name} has no fixed JSON property name`);
573
+ }
574
+ if (member.kind === 'index') {
575
+ const parameter = member.signature.parameters[0];
576
+ if (member.signature.parameters.length !== 1 || parameter === undefined) {
577
+ this.fail(subject, 'index signature must have exactly one key parameter');
578
+ }
579
+ indices.push(this.readonly(`z.record(${this.typeSchema(parameter.type, substitutions)}, ${this.typeSchema(member.signature.returns, substitutions)})`, member.readonly));
580
+ continue;
581
+ }
582
+ if (member.kind !== 'property')
583
+ this.fail(subject, `${member.kind} member ${member.name} is not data-schema projectable`);
584
+ const property = this.describe(this.optional(this.readonly(this.typeSchema(member.type, substitutions), member.readonly), member.optional), member);
585
+ properties.push(`${quote(member.jsonName ?? member.name)}: ${property}`);
586
+ }
587
+ if (indices.length > 1)
588
+ this.fail(subject, 'object type has more than one JSON index signature');
589
+ // A unique-symbol-only object is a compile-time marker and imposes no JSON shape.
590
+ if (properties.length === 0 && indices.length === 0 && symbolMembers > 0)
591
+ return 'z.unknown()';
592
+ const object = `z.object({${properties.length === 0 ? '' : `\n${properties.map(property => ` ${property},`).join('\n')}\n`}})`;
593
+ const index = indices[0];
594
+ if (index === undefined)
595
+ return object;
596
+ if (properties.length === 0)
597
+ return index;
598
+ return `z.intersection(${object}, ${index})`;
599
+ }
600
+ exportSchemaName(model) {
601
+ const name = this.schemaName(model.symbol);
602
+ const declaration = this.renderer.declaration(model.symbol);
603
+ if (declaration.typeParameters.length > 0) {
604
+ this.fail(model.export.name, 'generic schema exports require a concrete declaration');
605
+ }
606
+ return name;
607
+ }
608
+ keywordSchema(name) {
609
+ switch (name) {
610
+ case 'any': return 'z.any()';
611
+ case 'unknown': return 'z.unknown()';
612
+ case 'never': return 'z.never()';
613
+ case 'string': return 'z.string()';
614
+ case 'number': return 'z.number()';
615
+ case 'bigint': return 'z.bigint()';
616
+ case 'boolean': return 'z.boolean()';
617
+ case 'symbol': return 'z.symbol()';
618
+ case 'undefined': return 'z.undefined()';
619
+ case 'void': return 'z.void()';
620
+ case 'object': return "z.custom((value) => (typeof value === 'object' && value !== null) || typeof value === 'function')";
621
+ default: this.fail(name, `keyword ${name} has no Zod projection`);
622
+ }
623
+ }
624
+ schemaName(symbol) {
625
+ const name = this.names.get(symbol);
626
+ if (name === undefined)
627
+ this.fail(symbol, 'referenced declaration is outside the selected schema closure');
628
+ return name;
629
+ }
630
+ boundaryName(key) {
631
+ const name = this.boundaryNames.get(key);
632
+ if (name === undefined)
633
+ this.fail(key, 'invocation boundary is outside the selected schema roots');
634
+ return name;
635
+ }
636
+ describe(schema, documentation) {
637
+ return documentation.description === undefined ? schema : `${schema}.describe(${quote(documentation.description)})`;
638
+ }
639
+ optional(schema, optional) {
640
+ return optional ? `${schema}.optional()` : schema;
641
+ }
642
+ readonly(schema, readonly) {
643
+ return readonly ? `${schema}.readonly()` : schema;
644
+ }
645
+ unsupported(node) {
646
+ this.fail(node.id, `type node ${node.kind} has no Zod projection`);
647
+ }
648
+ fail(subject, message) {
649
+ throw new TypertEmitError(`typert Zod emitter: ${subject}: ${message}`);
650
+ }
651
+ }
652
+ function documentationLiteral(documentation) {
653
+ return {
654
+ ...(documentation.description === undefined ? {} : { description: documentation.description }),
655
+ ...(documentation.summary === undefined ? {} : { summary: documentation.summary }),
656
+ tags: documentation.tags,
657
+ ...(documentation.jsDoc === undefined ? {} : { jsDoc: documentation.jsDoc }),
658
+ };
659
+ }
660
+ function invocationBoundaryRoots(invocations) {
661
+ const result = [];
662
+ for (const invocation of invocations) {
663
+ if (invocation.invocation.kind === 'context') {
664
+ result.push({ key: contextBoundaryKey(invocation), type: invocation.invocation.boundary.codecType });
665
+ }
666
+ invocation.parameters.forEach((parameter, index) => {
667
+ result.push({ key: parameterBoundaryKey(invocation, index), type: parameter.boundary.codecType });
668
+ });
669
+ result.push({ key: resultBoundaryKey(invocation), type: invocation.result.codecType });
670
+ }
671
+ return result;
672
+ }
673
+ function contextBoundaryKey(invocation) {
674
+ return `${invocation.id}:context`;
675
+ }
676
+ function parameterBoundaryKey(invocation, index) {
677
+ return `${invocation.id}:parameter:${String(index)}`;
678
+ }
679
+ function resultBoundaryKey(invocation) {
680
+ return `${invocation.id}:result`;
681
+ }
682
+ function strictCodec(boundary, schema) {
683
+ return [
684
+ '{',
685
+ ' mode: \'strict\',',
686
+ ` typeSymbol: ${quote(boundary.typeSymbol)},`,
687
+ ` schema: ${schema},`,
688
+ '}',
689
+ ].join('\n');
690
+ }
691
+ function remoteImports(invocations) {
692
+ const imports = new Map();
693
+ const add = (boundary) => {
694
+ for (const imported of boundary.imports) {
695
+ const current = imports.get(imported.symbol);
696
+ if (current !== undefined
697
+ && (current.specifier !== imported.specifier || current.name !== imported.name)) {
698
+ throw new TypertEmitError(`typert Remote emitter: symbol ${imported.symbol} has inconsistent public imports`);
699
+ }
700
+ imports.set(imported.symbol, imported);
701
+ }
702
+ };
703
+ for (const invocation of invocations) {
704
+ if (invocation.invocation.kind === 'context')
705
+ add(invocation.invocation.boundary);
706
+ for (const parameter of invocation.parameters)
707
+ add(parameter.boundary);
708
+ add(invocation.result);
709
+ }
710
+ return [...imports.values()].sort((left, right) => left.specifier.localeCompare(right.specifier) || left.name.localeCompare(right.name));
711
+ }
712
+ function allocateRemoteImportNames(imports) {
713
+ const used = new Set(['TypeRTRemoteContribution', 'TYPERT_REMOTE']);
714
+ const names = new Map();
715
+ for (const imported of imports) {
716
+ const base = safeIdentifier(imported.name);
717
+ let name = base;
718
+ let suffix = 2;
719
+ while (used.has(name))
720
+ name = `${base}$remote${String(suffix++)}`;
721
+ used.add(name);
722
+ names.set(imported.symbol, name);
723
+ }
724
+ return names;
725
+ }
726
+ function packageExportSpecifier(packageName, subpath) {
727
+ return subpath === '.' ? packageName : `${packageName}${subpath.slice(1)}`;
728
+ }
729
+ function safeIdentifier(name) {
730
+ const normalized = name.replace(/[^$\w]/gu, '_');
731
+ if (/^[$A-Z_a-z]/u.test(normalized))
732
+ return normalized;
733
+ return `_${normalized}`;
734
+ }
735
+ function renderRemotePropertyName(name) {
736
+ return /^[$A-Z_a-z][$\w]*$/u.test(name) ? name : quote(name);
737
+ }
738
+ function quote(value) {
739
+ return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'").replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'`;
740
+ }
741
+ function indent(value, spaces) {
742
+ const prefix = ' '.repeat(spaces);
743
+ return value.split('\n').map(line => `${prefix}${line}`).join('\n');
744
+ }
745
+ //# sourceMappingURL=emitter.js.map