@deepseek-ai/dsh-typert-generator 0.2.1-alpha.2 → 0.2.1-alpha.3

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.
@@ -95,9 +95,10 @@ export interface InvocationParameterModel {
95
95
  /** Authored as an optional parameter, so consumers may omit the wire field. */
96
96
  readonly optional?: true;
97
97
  readonly boundary: RemoteBoundaryModel;
98
+ readonly description?: string;
98
99
  }
99
100
  /** One strictly analyzed Host method exported through Typert Gateway. */
100
- export interface InvocationModel {
101
+ export interface InvocationModel extends DocumentationModel {
101
102
  readonly id: string;
102
103
  readonly service: string;
103
104
  readonly namespace: string;
@@ -302,7 +303,7 @@ export interface TemplateSpanModel {
302
303
  readonly text: string;
303
304
  }
304
305
  /** Compiler-independent TypeScript type expression. */
305
- export type TypeNodeModel = {
306
+ export type TypeNodeModel = ({
306
307
  readonly id: TypeNodeId;
307
308
  readonly kind: 'keyword';
308
309
  readonly name: KeywordTypeName;
@@ -403,6 +404,9 @@ export type TypeNodeModel = {
403
404
  } | {
404
405
  readonly id: TypeNodeId;
405
406
  readonly kind: 'this';
407
+ }) & {
408
+ /** Canonical lossless JsonValue, represented without recursive tool-schema references. */
409
+ readonly json?: true;
406
410
  };
407
411
  /**
408
412
  * Return the direct type-expression edges owned by one node.
@@ -0,0 +1,20 @@
1
+ /** Lossless projection into the harness tool-schema subset. */
2
+ import type { TypeNodeId } from './model.ts';
3
+ import { TypeGraphRenderer } from './renderer.ts';
4
+ /** A resolved codec whose type the tool-schema subset cannot represent without weakening constraints. */
5
+ export declare class ToolSchemaError extends Error {
6
+ name: string;
7
+ }
8
+ /**
9
+ * Project one resolved type into the tool-schema subset that `@deepseek-ai/dsh-tools`
10
+ * enforces on tool parameters, tool output, and the generated PTC SDK. A type the
11
+ * subset cannot express without weakening constraints raises {@link ToolSchemaError},
12
+ * which callers report as an absent projection rather than a narrower schema.
13
+ * @param renderer - complete analyzed type graph.
14
+ * @param type - resolved wire type.
15
+ * @param closedObjects - close declared request objects with `additionalProperties: false`.
16
+ * @param optional - omit a top-level undefined alternative supplied by an optional argument.
17
+ * @returns an acyclic schema in the tool-schema subset.
18
+ */
19
+ export declare function projectToolSchema(renderer: TypeGraphRenderer, type: TypeNodeId, closedObjects?: boolean, optional?: boolean): Record<string, unknown>;
20
+ //# sourceMappingURL=tool-schema.d.ts.map
@@ -0,0 +1,156 @@
1
+ /** Lossless projection into the harness tool-schema subset. */
2
+ /** A resolved codec whose type the tool-schema subset cannot represent without weakening constraints. */
3
+ export class ToolSchemaError extends Error {
4
+ name = 'ToolSchemaError';
5
+ }
6
+ /**
7
+ * Project one resolved type into the tool-schema subset that `@deepseek-ai/dsh-tools`
8
+ * enforces on tool parameters, tool output, and the generated PTC SDK. A type the
9
+ * subset cannot express without weakening constraints raises {@link ToolSchemaError},
10
+ * which callers report as an absent projection rather than a narrower schema.
11
+ * @param renderer - complete analyzed type graph.
12
+ * @param type - resolved wire type.
13
+ * @param closedObjects - close declared request objects with `additionalProperties: false`.
14
+ * @param optional - omit a top-level undefined alternative supplied by an optional argument.
15
+ * @returns an acyclic schema in the tool-schema subset.
16
+ */
17
+ export function projectToolSchema(renderer, type, closedObjects = false, optional = false) {
18
+ const active = new Set();
19
+ const fail = (subject) => { throw new ToolSchemaError(`typert tool schema: ${subject}`); };
20
+ const project = (id, optional = false) => {
21
+ const node = renderer.node(id);
22
+ if (node.json)
23
+ return {};
24
+ if (active.has(id))
25
+ return fail('recursive selected type requires a supported JSON value declaration');
26
+ active.add(id);
27
+ try {
28
+ switch (node.kind) {
29
+ case 'keyword': {
30
+ if (['string', 'number', 'boolean'].includes(node.name))
31
+ return { type: node.name };
32
+ return fail(`unsupported ${node.name} value`);
33
+ }
34
+ case 'literal': {
35
+ if (typeof node.value === 'bigint')
36
+ return fail('bigint is not JSON');
37
+ return { type: node.value === null ? 'null' : typeof node.value, const: node.value };
38
+ }
39
+ case 'parenthesized': return project(node.type, optional);
40
+ case 'array': return { type: 'array', items: project(node.element) };
41
+ case 'object': return object(node.members);
42
+ case 'reference': {
43
+ if (node.target.kind !== 'declaration' || node.arguments.length !== 0) {
44
+ return fail(`unsupported selected reference ${node.name}`);
45
+ }
46
+ const declaration = renderer.declaration(node.target.symbol);
47
+ const schema = declaration.kind === 'alias' && declaration.type !== undefined
48
+ ? project(declaration.type, optional)
49
+ : object(declaration.members);
50
+ if (declaration.extends.length > 0)
51
+ return fail('unresolved inherited selected type');
52
+ return declaration.description === undefined ? schema : { ...schema, description: declaration.description };
53
+ }
54
+ case 'union': {
55
+ const branches = node.types.filter((item) => {
56
+ const branch = renderer.node(item);
57
+ return !(optional && branch.kind === 'keyword' && branch.name === 'undefined');
58
+ }).map(item => project(item));
59
+ return union(branches);
60
+ }
61
+ default: return fail(`unsupported selected ${node.kind} type`);
62
+ }
63
+ }
64
+ finally {
65
+ active.delete(id);
66
+ }
67
+ };
68
+ const object = (members) => {
69
+ const properties = {};
70
+ const required = [];
71
+ let indexed = false;
72
+ for (const member of members) {
73
+ if (member.static || member.visibility !== 'public' || member.computed === 'symbol')
74
+ continue;
75
+ if (member.kind === 'index') {
76
+ const key = member.signature.parameters[0];
77
+ if (key === undefined || project(key.type).type !== 'string'
78
+ || Object.keys(project(member.signature.returns)).length !== 0) {
79
+ return fail('selected dictionaries require string keys and unconstrained JSON values');
80
+ }
81
+ indexed = true;
82
+ continue;
83
+ }
84
+ if (member.kind !== 'property' || member.computed === 'dynamic')
85
+ return fail(`unsupported member ${member.name}`);
86
+ const name = member.jsonName ?? member.name;
87
+ const schema = project(member.type, member.optional);
88
+ properties[name] = member.description === undefined ? schema : { ...schema, description: member.description };
89
+ if (!member.optional)
90
+ required.push(name);
91
+ }
92
+ return { type: 'object', properties, required, additionalProperties: indexed || !closedObjects };
93
+ };
94
+ const union = (input) => {
95
+ if (input.some(branch => Object.keys(branch).length === 0))
96
+ return {};
97
+ const branches = [];
98
+ for (const branch of input) {
99
+ const scalar = branch.type !== undefined && !['object', 'array'].includes(branch.type);
100
+ const previous = scalar ? branches.find(candidate => candidate.type === branch.type) : undefined;
101
+ if (previous === undefined) {
102
+ branches.push(branch);
103
+ continue;
104
+ }
105
+ const values = literals(previous);
106
+ const next = literals(branch);
107
+ if (values === undefined || next === undefined) {
108
+ delete previous.const;
109
+ delete previous.enum;
110
+ }
111
+ else {
112
+ delete previous.const;
113
+ previous.enum = [...new Set([...values, ...next])];
114
+ }
115
+ }
116
+ if (branches.length === 1)
117
+ return branches[0];
118
+ if (branches.length === 0)
119
+ return fail('empty selected union');
120
+ for (const [index, left] of branches.entries()) {
121
+ for (const right of branches.slice(index + 1)) {
122
+ if (!disjoint(left, right))
123
+ return fail('selected union branches overlap or have no required discriminator');
124
+ }
125
+ }
126
+ return { oneOf: branches };
127
+ };
128
+ return project(type, optional);
129
+ }
130
+ function literals(schema) {
131
+ if (schema.const !== undefined)
132
+ return [schema.const];
133
+ return schema.enum;
134
+ }
135
+ function disjoint(left, right) {
136
+ if (left.oneOf !== undefined)
137
+ return left.oneOf.every(branch => disjoint(branch, right));
138
+ if (right.oneOf !== undefined)
139
+ return right.oneOf.every(branch => disjoint(left, branch));
140
+ if (left.type !== right.type)
141
+ return left.type !== undefined && right.type !== undefined;
142
+ if (left.type !== 'object' || right.type !== 'object')
143
+ return false;
144
+ return (left.required ?? []).some((name) => {
145
+ if (!right.required?.includes(name))
146
+ return false;
147
+ const a = left.properties?.[name];
148
+ const b = right.properties?.[name];
149
+ if (a === undefined || b === undefined)
150
+ return false;
151
+ const first = literals(a);
152
+ const second = literals(b);
153
+ return first !== undefined && second !== undefined && first.every(value => !second.includes(value));
154
+ });
155
+ }
156
+ //# sourceMappingURL=tool-schema.js.map
@@ -2,20 +2,31 @@
2
2
  * Optional tsdown (rolldown) plugin face of the typert generator. It lowers
3
3
  * standard decorators in TypeScript dependencies before bundling, then emits
4
4
  * model-driven face artifacts at the package output root. Packages without a
5
- * Typert or Remote export are skipped.
5
+ * Typert or Remote export are skipped. Workspace mode generates in a Worker
6
+ * Thread started by the pass's first `buildStart`, so bundling continues on the
7
+ * main thread while Typert analyzes; the pass's first `writeBundle` awaits the
8
+ * result and writes every artifact once.
6
9
  * @module @deepseek-ai/dsh-typert-generator/tsdown
7
10
  */
8
11
  import type { TypertFace } from './model.ts';
9
12
  /** The subset of the rolldown plugin contract used here (structural; avoids a rolldown type dependency). */
10
13
  interface TypertPlugin {
11
14
  name: string;
12
- transform: (code: string, id: string) => {
13
- code: string;
14
- map: string | undefined;
15
- } | undefined;
15
+ buildStart: (options: {
16
+ cwd: string;
17
+ }) => void;
18
+ transform: {
19
+ filter: {
20
+ id: RegExp;
21
+ };
22
+ handler: (code: string, id: string) => {
23
+ code: string;
24
+ map: string | undefined;
25
+ } | undefined;
26
+ };
16
27
  writeBundle: (options: {
17
28
  dir?: string;
18
- }) => void;
29
+ }) => Promise<void>;
19
30
  }
20
31
  /** Generation scope selected by a tsdown build phase. */
21
32
  export interface TypertPluginOptions {
@@ -24,6 +35,10 @@ export interface TypertPluginOptions {
24
35
  /** Independent TypeScript program faces included in this phase. */
25
36
  readonly faces?: readonly TypertFace[];
26
37
  }
38
+ /** A workspace-mode generation failure reported by, or observed on, the Typert Worker Thread. */
39
+ export declare class TypertWorkerError extends Error {
40
+ name: string;
41
+ }
27
42
  /**
28
43
  * Create the decorator-lowering and typert-generation plugin for the root tsdown config.
29
44
  * @param pluginOptions - package/workspace emission mode and independent program faces.
@@ -2,18 +2,29 @@
2
2
  * Optional tsdown (rolldown) plugin face of the typert generator. It lowers
3
3
  * standard decorators in TypeScript dependencies before bundling, then emits
4
4
  * model-driven face artifacts at the package output root. Packages without a
5
- * Typert or Remote export are skipped.
5
+ * Typert or Remote export are skipped. Workspace mode generates in a Worker
6
+ * Thread started by the pass's first `buildStart`, so bundling continues on the
7
+ * main thread while Typert analyzes; the pass's first `writeBundle` awaits the
8
+ * result and writes every artifact once.
6
9
  * @module @deepseek-ai/dsh-typert-generator/tsdown
7
10
  */
8
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
11
+ import { existsSync, readFileSync } from 'node:fs';
9
12
  import { dirname, join, resolve } from 'node:path';
13
+ import { Worker } from 'node:worker_threads';
10
14
  import ts from 'typescript';
11
- import { WorkspaceTypertGenerator } from "./workspace.js";
15
+ import { writePackageArtifacts, writeWorkspaceArtifacts } from "./artifact-files.js";
16
+ import { findWorkspaceRoot, hasTypertExport, WorkspaceTypertGenerator } from "./workspace.js";
17
+ /** A module id whose path (before any `?query`) names a TypeScript source. */
18
+ const TYPESCRIPT_MODULE = /^[^?]*\.[cm]?tsx?(?:\?|$)/;
12
19
  const DECORATOR_SYNTAX = /^\s*@[A-Za-z_$][\w$]*/m;
13
20
  // Full builds and typecheck own TypeScript diagnostics. Benchmark-only emission
14
21
  // defers them to the required full-build CI jobs; Typert generates reflection
15
22
  // without repeating those diagnostics.
16
23
  const TSC_OWNED_DIAGNOSTICS = { checkDiagnostics: false };
24
+ /** A workspace-mode generation failure reported by, or observed on, the Typert Worker Thread. */
25
+ export class TypertWorkerError extends Error {
26
+ name = 'TypertWorkerError';
27
+ }
17
28
  /**
18
29
  * Create the decorator-lowering and typert-generation plugin for the root tsdown config.
19
30
  * @param pluginOptions - package/workspace emission mode and independent program faces.
@@ -21,39 +32,68 @@ const TSC_OWNED_DIAGNOSTICS = { checkDiagnostics: false };
21
32
  */
22
33
  export function typertPlugin(pluginOptions = {}) {
23
34
  const artifactsByRoot = new Map();
35
+ const generations = new Map();
24
36
  const emittedWorkspaces = new Set();
37
+ const workspaceGeneration = (root) => {
38
+ let generation = generations.get(root);
39
+ if (generation === undefined) {
40
+ generation = generateInWorker({
41
+ root,
42
+ ...(pluginOptions.faces === undefined ? {} : { faces: pluginOptions.faces }),
43
+ options: TSC_OWNED_DIAGNOSTICS,
44
+ });
45
+ generations.set(root, generation);
46
+ }
47
+ return generation;
48
+ };
25
49
  return {
26
50
  name: 'dsh-typert-generator',
27
- transform(code, id) {
28
- const file = id.split('?', 1)[0] ?? id;
29
- if (!/\.[cm]?tsx?$/.test(file) || !DECORATOR_SYNTAX.test(code))
30
- return;
31
- const result = ts.transpileModule(code, {
32
- fileName: file,
33
- compilerOptions: {
34
- target: ts.ScriptTarget.ES2024,
35
- module: ts.ModuleKind.ESNext,
36
- ...(file.endsWith('x') ? { jsx: ts.JsxEmit.ReactJSX } : {}),
37
- sourceMap: true,
38
- },
39
- });
40
- return {
41
- code: result.outputText.replace(/\n?\/\/# sourceMappingURL=.*$/u, '\n'),
42
- map: result.sourceMapText,
43
- };
51
+ buildStart(inputOptions) {
52
+ // tsdown builds every workspace package concurrently through this one
53
+ // plugin instance; the first package build to start launches generation.
54
+ if (pluginOptions.mode === 'workspace')
55
+ void workspaceGeneration(findWorkspaceRoot(inputOptions.cwd));
44
56
  },
45
- writeBundle(bundleOptions) {
57
+ transform: {
58
+ // Rolldown evaluates the id filter natively, so non-TypeScript modules
59
+ // never cross into this JavaScript hook.
60
+ filter: { id: TYPESCRIPT_MODULE },
61
+ handler(code, id) {
62
+ if (!TYPESCRIPT_MODULE.test(id) || !DECORATOR_SYNTAX.test(code))
63
+ return;
64
+ const file = id.replace(/\?.*$/su, '');
65
+ const result = ts.transpileModule(code, {
66
+ fileName: file,
67
+ compilerOptions: {
68
+ target: ts.ScriptTarget.ES2024,
69
+ module: ts.ModuleKind.ESNext,
70
+ ...(file.endsWith('x') ? { jsx: ts.JsxEmit.ReactJSX } : {}),
71
+ sourceMap: true,
72
+ },
73
+ });
74
+ return {
75
+ code: result.outputText.replace(/\n?\/\/# sourceMappingURL=.*$/u, '\n'),
76
+ map: result.sourceMapText,
77
+ };
78
+ },
79
+ },
80
+ async writeBundle(bundleOptions) {
46
81
  // options.dir is the package's absolute outDir (<package>/lib); its
47
82
  // nearest package.json owns the bundle even when a custom config writes
48
83
  // a nested output such as <package>/lib/dev.
49
84
  if (bundleOptions.dir === undefined)
50
85
  return;
51
- const root = workspaceRoot(bundleOptions.dir);
86
+ const root = findWorkspaceRoot(bundleOptions.dir);
52
87
  if (emittedWorkspaces.has(root))
53
88
  return;
54
89
  if (pluginOptions.mode === 'workspace') {
55
- emitWorkspace(root, pluginOptions.faces);
90
+ // The first bundle to finish claims emission and reports any failure;
91
+ // later bundles of the pass return without waiting.
56
92
  emittedWorkspaces.add(root);
93
+ const outcome = await workspaceGeneration(root);
94
+ if (!outcome.ok)
95
+ throw outcome.error;
96
+ writeWorkspaceArtifacts(root, outcome.artifacts);
57
97
  return;
58
98
  }
59
99
  const packageDir = packageRoot(bundleOptions.dir, root);
@@ -70,53 +110,122 @@ export function typertPlugin(pluginOptions = {}) {
70
110
  : generator.generate(undefined, pluginOptions.faces);
71
111
  artifactsByRoot.set(root, artifacts);
72
112
  }
73
- emitArtifacts(packageDir, artifacts.filter(candidate => candidate.package === manifest.name));
113
+ writePackageArtifacts(packageDir, artifacts.filter(candidate => candidate.package === manifest.name));
74
114
  },
75
115
  };
76
- function emitWorkspace(root, faces) {
77
- const generator = new WorkspaceTypertGenerator(root, TSC_OWNED_DIAGNOSTICS);
78
- const packages = generator.discover(faces)
79
- .filter(candidate => hasTypertExport(readManifest(join(root, candidate.root)).exports))
80
- .map(candidate => candidate.package);
81
- if (packages.length === 0)
82
- return;
83
- for (const artifact of generator.generate(packages, faces)) {
84
- emitArtifacts(join(root, artifact.packageRoot), [artifact]);
85
- }
116
+ }
117
+ /**
118
+ * Run workspace generation in a Worker Thread. The outcome never rejects, and
119
+ * every terminal path terminates the worker before the outcome settles.
120
+ */
121
+ function generateInWorker(request) {
122
+ const worker = spawnWorker(request);
123
+ return new Promise((settle) => {
124
+ let settled = false;
125
+ const finish = (outcome) => {
126
+ if (settled)
127
+ return;
128
+ settled = true;
129
+ worker.terminate().then(() => { settle(outcome); }, (error) => { settle({ ok: false, error: workerError(request, 'could not terminate its worker', error) }); });
130
+ };
131
+ const fail = (problem, cause) => {
132
+ finish({ ok: false, error: workerError(request, problem, cause) });
133
+ };
134
+ worker.once('message', (value) => {
135
+ const response = parseWorkerResponse(value);
136
+ if (response === undefined) {
137
+ fail('received a malformed worker response');
138
+ }
139
+ else if (response.kind === 'failure') {
140
+ const cause = new Error(response.message);
141
+ cause.name = response.name;
142
+ if (response.stack !== undefined)
143
+ cause.stack = response.stack;
144
+ fail(`failed: ${response.name}: ${response.message}`, cause);
145
+ }
146
+ else {
147
+ finish({ ok: true, artifacts: response.artifacts });
148
+ }
149
+ });
150
+ worker.once('messageerror', (error) => { fail('received a worker response that could not be deserialized', error); });
151
+ worker.once('error', (error) => { fail(`crashed: ${error.message}`, error); });
152
+ worker.once('exit', (code) => { fail(`stopped: its worker exited with code ${String(code)} before reporting artifacts`); });
153
+ });
154
+ }
155
+ function workerError(request, problem, cause) {
156
+ return new TypertWorkerError(`typert-generator: workspace generation for ${request.root} ${problem}`, cause === undefined ? undefined : { cause });
157
+ }
158
+ function spawnWorker(request) {
159
+ /* v8 ignore next 3 -- the tsdown host build runs the built worker entry. */
160
+ if (!import.meta.url.endsWith('.ts')) {
161
+ return new Worker(new URL('./worker.js', import.meta.url), { workerData: request });
86
162
  }
163
+ // Source runs load the TypeScript worker entry through tsx.
164
+ const bootstrap = [
165
+ `import { register } from ${JSON.stringify(import.meta.resolve('tsx/esm/api'))}`,
166
+ 'register()',
167
+ `await import(${JSON.stringify(new URL('./worker.ts', import.meta.url).href)})`,
168
+ ].join('\n');
169
+ return new Worker(new URL(`data:text/javascript,${encodeURIComponent(bootstrap)}`), { workerData: request, execArgv: [] });
87
170
  }
88
- function emitArtifacts(packageDir, artifacts) {
89
- const output = join(packageDir, 'lib');
90
- mkdirSync(output, { recursive: true });
91
- let emittedRemote = false;
92
- for (const artifact of artifacts) {
93
- writeFileSync(join(output, `typert.${artifact.face}.js`), artifact.js);
94
- writeFileSync(join(output, `typert.${artifact.face}.d.ts`), artifact.dts);
95
- if (artifact.remote !== undefined) {
96
- emittedRemote = true;
97
- writeFileSync(join(output, 'typert.remote-client.js'), artifact.remote.js);
98
- writeFileSync(join(output, 'typert.remote-client.d.ts'), artifact.remote.dts);
99
- writeFileSync(join(output, 'typert.remote-client.d.ts.map'), artifact.remote.dtsMap);
100
- }
171
+ function parseWorkerResponse(value) {
172
+ if (!isRecord(value))
173
+ return undefined;
174
+ if (value.kind === 'failure') {
175
+ if (typeof value.name !== 'string' || typeof value.message !== 'string'
176
+ || (value.stack !== undefined && typeof value.stack !== 'string'))
177
+ return undefined;
178
+ return {
179
+ kind: 'failure',
180
+ name: value.name,
181
+ message: value.message,
182
+ ...(value.stack === undefined ? {} : { stack: value.stack }),
183
+ };
101
184
  }
102
- if (!emittedRemote && artifacts.some(artifact => artifact.face === 'host')) {
103
- for (const file of [
104
- 'typert.remote-client.js',
105
- 'typert.remote-client.d.ts',
106
- 'typert.remote-client.d.ts.map',
107
- ])
108
- rmSync(join(output, file), { force: true });
185
+ if (value.kind !== 'artifacts' || !Array.isArray(value.artifacts))
186
+ return undefined;
187
+ const artifacts = [];
188
+ for (const artifact of value.artifacts) {
189
+ const parsed = parseArtifact(artifact);
190
+ if (parsed === undefined)
191
+ return undefined;
192
+ artifacts.push(parsed);
109
193
  }
194
+ return { kind: 'artifacts', artifacts };
110
195
  }
111
- function readManifest(packageDir) {
112
- return JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'));
196
+ function parseArtifact(value) {
197
+ if (!isRecord(value)
198
+ || typeof value.package !== 'string'
199
+ || typeof value.packageRoot !== 'string'
200
+ || (value.face !== 'host' && value.face !== 'client')
201
+ || !Array.isArray(value.exports)
202
+ || !value.exports.every((entry) => typeof entry === 'string')
203
+ || typeof value.js !== 'string'
204
+ || typeof value.dts !== 'string')
205
+ return undefined;
206
+ const remote = parseRemoteArtifact(value.remote);
207
+ if (value.remote !== undefined && remote === undefined)
208
+ return undefined;
209
+ return {
210
+ package: value.package,
211
+ packageRoot: value.packageRoot,
212
+ face: value.face,
213
+ exports: value.exports,
214
+ js: value.js,
215
+ dts: value.dts,
216
+ ...(remote === undefined ? {} : { remote }),
217
+ };
218
+ }
219
+ function parseRemoteArtifact(value) {
220
+ if (!isRecord(value)
221
+ || typeof value.js !== 'string'
222
+ || typeof value.dts !== 'string'
223
+ || typeof value.dtsMap !== 'string')
224
+ return undefined;
225
+ return { js: value.js, dts: value.dts, dtsMap: value.dtsMap };
113
226
  }
114
- function hasTypertExport(exportsField) {
115
- if (exportsField === null || typeof exportsField !== 'object' || Array.isArray(exportsField))
116
- return false;
117
- return Object.hasOwn(exportsField, './typert')
118
- || Object.hasOwn(exportsField, './client/typert')
119
- || Object.hasOwn(exportsField, './remote');
227
+ function isRecord(value) {
228
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
120
229
  }
121
230
  function packageRoot(start, workspace) {
122
231
  let current = resolve(start);
@@ -127,14 +236,4 @@ function packageRoot(start, workspace) {
127
236
  }
128
237
  return undefined;
129
238
  }
130
- function workspaceRoot(start) {
131
- let current = resolve(start);
132
- while (!existsSync(join(current, 'tsconfig.host.json'))) {
133
- const parent = dirname(current);
134
- if (parent === current)
135
- throw new Error(`typert-generator: cannot find workspace root above ${start}`);
136
- current = parent;
137
- }
138
- return current;
139
- }
140
239
  //# sourceMappingURL=tsdown-plugin.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Worker Thread entry for workspace-mode Typert generation. It generates every
3
+ * explicit contributor of the requested workspace, posts exactly one
4
+ * {@link WorkspaceWorkerResponse}, and exits.
5
+ * @module @deepseek-ai/dsh-typert-generator/worker
6
+ */
7
+ import type { WorkspaceEmitResult, WorkspaceTypertGeneratorOptions } from './workspace.ts';
8
+ import type { TypertFace } from './model.ts';
9
+ /** Generation inputs passed as `workerData`. */
10
+ export interface WorkspaceWorkerRequest {
11
+ /** Workspace root containing the face aggregate tsconfigs. */
12
+ readonly root: string;
13
+ /** Faces to generate; every face when absent. */
14
+ readonly faces?: readonly TypertFace[];
15
+ /** Generator switches applied to the pass. */
16
+ readonly options: WorkspaceTypertGeneratorOptions;
17
+ }
18
+ /** The single message the worker posts: every generated artifact, or the failure that stopped generation. */
19
+ export type WorkspaceWorkerResponse = {
20
+ readonly kind: 'artifacts';
21
+ readonly artifacts: readonly WorkspaceEmitResult[];
22
+ } | {
23
+ readonly kind: 'failure';
24
+ readonly name: string;
25
+ readonly message: string;
26
+ readonly stack?: string;
27
+ };
28
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Worker Thread entry for workspace-mode Typert generation. It generates every
3
+ * explicit contributor of the requested workspace, posts exactly one
4
+ * {@link WorkspaceWorkerResponse}, and exits.
5
+ * @module @deepseek-ai/dsh-typert-generator/worker
6
+ */
7
+ import { parentPort, workerData } from 'node:worker_threads';
8
+ import { WorkspaceTypertGenerator } from "./workspace.js";
9
+ function parseRequest(value) {
10
+ const request = typeof value === 'object' && value !== null
11
+ ? value
12
+ : {};
13
+ const { root, faces, options } = request;
14
+ const checkDiagnostics = typeof options === 'object' && options !== null
15
+ ? options.checkDiagnostics
16
+ : null;
17
+ if (typeof root !== 'string'
18
+ || (faces !== undefined
19
+ && (!Array.isArray(faces) || !faces.every((face) => face === 'host' || face === 'client')))
20
+ || (checkDiagnostics !== undefined && typeof checkDiagnostics !== 'boolean')) {
21
+ throw new TypeError('typert-generator: malformed workspace worker request');
22
+ }
23
+ return {
24
+ root,
25
+ ...(faces === undefined ? {} : { faces }),
26
+ options: checkDiagnostics === undefined ? {} : { checkDiagnostics },
27
+ };
28
+ }
29
+ function respond() {
30
+ try {
31
+ const request = parseRequest(workerData);
32
+ const generator = new WorkspaceTypertGenerator(request.root, request.options);
33
+ return { kind: 'artifacts', artifacts: generator.generateContributors(request.faces) };
34
+ }
35
+ catch (error) {
36
+ if (!(error instanceof Error))
37
+ return { kind: 'failure', name: 'Error', message: String(error) };
38
+ return {
39
+ kind: 'failure',
40
+ name: error.name,
41
+ message: error.message,
42
+ ...(error.stack === undefined ? {} : { stack: error.stack }),
43
+ };
44
+ }
45
+ }
46
+ if (parentPort === null)
47
+ throw new Error('typert-generator: the workspace worker entry must run in a Worker Thread');
48
+ parentPort.postMessage(respond());
49
+ //# sourceMappingURL=worker.js.map