@deepseek-ai/dsh-typert-loader 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.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, DeepSeek
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/typert/loader/README.md
5
+ README.md: ab9293de1630fdbe8c560bb9e6d00c272cc34161
6
+ README.zh.md: f73beec4fb09d5376d905a5e22e8bb1ccb710f90
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # @deepseek-ai/dsh-typert-loader
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ Node-only Loader integration for generated Typert artifacts. The plugin requires `ctx.loader` and `ctx.typert`; it does not provide the registry itself.
6
+
7
+ During activation it scans existing Loader entries. It then follows Cordis `internal/plugin` lifecycle notifications, resolves each entry package's `package.json`, imports `./typert` when exported, validates its `TYPERT` manifest, and registers the contribution until the entry or this plugin unmounts. An import that settles after either owner is gone is discarded.
8
+
9
+ `packages` lists additional package artifacts to register for plugins nested behind another Loader entry. Cordis fibers do not retain those nested plugins' npm specifiers, so this boundary is explicit; every configured package must resolve from the config tree and export `./typert`.
10
+
11
+ Packages without the export are skipped. Package resolution and imported manifests are cached for the process lifetime, so adding an export requires a restart. A malformed artifact fails activation when already mounted; a later failure is logged without preventing unrelated packages from registering.
12
+
13
+ ## Model Experience
14
+
15
+ None, as the loader only feeds [`ctx.typert`](../registry/README.md); consumers own any model-visible projection.
16
+
17
+ #### KV Cache effect
18
+
19
+ No direct effect.
20
+
21
+ ## Known Limitations and Deferred Work
22
+
23
+ - Discovery imports only the host face; client runtimes need a separate composition owner before equivalent discovery is added.
24
+ - Loader entries are discovered automatically. Nested or non-Loader plugins require an explicit `packages` entry or direct `ctx.typert.register()` ownership.
package/README.zh.md ADDED
@@ -0,0 +1,24 @@
1
+ # @deepseek-ai/dsh-typert-loader
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 生成的 Typert 产物所用的 Loader 集成,仅支持 Node。该插件需要 `ctx.loader` 和 `ctx.typert`;它本身不提供注册表。
6
+
7
+ 激活时,该插件会扫描现有的 Loader 配置项。随后它会监听 Cordis `internal/plugin` 生命周期通知,解析每个配置项所属包的 `package.json`,在其导出 `./typert` 时导入该子路径,校验其 `TYPERT` manifest(元数据清单),并注册该贡献项,直到配置项或本插件卸载。如果导入操作在配置项或本插件卸载后才结束,系统会丢弃其结果。
8
+
9
+ `packages` 用于列出需要为嵌套在另一 Loader 配置项下的插件额外注册的包产物。Cordis fiber 不会保留这些嵌套插件的 npm 包说明符,因此这里通过显式配置划定边界;配置中列出的每个包都必须能从配置树解析,并导出 `./typert`。
10
+
11
+ 未导出该子路径的包会被跳过。包解析结果和已导入的 manifest 会在整个进程生命周期内缓存,因此新增该导出后必须重启进程。如果已经挂载的产物格式错误,插件激活会失败;后续失败只会记录到日志,不会阻止无关包完成注册。
12
+
13
+ ## 模型体验
14
+
15
+ 无。loader 只向 [`ctx.typert`](../registry/README.md) 提供注册项;任何模型可见投影均由消费方负责。
16
+
17
+ #### KV Cache 影响
18
+
19
+ 无直接影响。
20
+
21
+ ## 已知限制与暂缓事项
22
+
23
+ - 发现机制只会导入宿主侧产物;若要为客户端运行时添加等价的发现机制,需要先有独立的组合所有者。
24
+ - Loader 配置项会自动发现。嵌套插件或非 Loader 插件需要显式加入 `packages`,或由组合所有者直接负责调用 `ctx.typert.register()`。
package/lib/index.js ADDED
@@ -0,0 +1,333 @@
1
+ import { createRequire } from "node:module";
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import z from "@deepseek-ai/schemastery";
6
+ //#region lib/types/index.js
7
+ /**
8
+ * Typert Loader integration: automatic registration for mounted plugin packages.
9
+ *
10
+ * When a loader entry mounts, this plugin resolves the entry's package.json; a
11
+ * package exporting `./typert` has its host face imported and its
12
+ * `TYPERT` manifest registered into `ctx.typert`, and the registration is
13
+ * withdrawn when the entry unmounts. Explicit `packages` cover plugins nested
14
+ * behind another Loader entry, whose Cordis fibers carry no resolvable package
15
+ * specifier. Packages without the export are skipped silently when discovered
16
+ * from Loader entries; an explicit package or declared artifact that is broken
17
+ * fails loud — aggregated into this plugin's activation throw for existing
18
+ * entries, contained to a logged error per package in steady state.
19
+ *
20
+ * Scanning is incremental per entry name, mirroring the client-modules node
21
+ * half: every cordis `internal/plugin` emission marks the fiber's entry name
22
+ * dirty and a microtask flush reconciles each dirty name against the live
23
+ * loader entries; the activation pass seeds the same dirty set with all
24
+ * current entries. Package verdicts and imported manifests are cached per
25
+ * package name and never expire — plugin-set changes take effect on restart.
26
+ *
27
+ * Manual `ctx.typert.register()` remains available for contributions
28
+ * that do not use a `./typert` artifact (hand-written wire schemas,
29
+ * tests, non-loader compositions).
30
+ *
31
+ * @module @deepseek-ai/dsh-typert-loader
32
+ */
33
+ var __rewriteRelativeImportExtension = function(path, preserveJsx) {
34
+ if (typeof path === "string" && /^\.\.?\//.test(path)) return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
35
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
36
+ });
37
+ return path;
38
+ };
39
+ /** The package.json exports key naming a package's host-face typert artifact. */
40
+ const TYPERT_HOST_EXPORT = "./typert";
41
+ /** Cordis plugin name. */
42
+ const name = "typert-loader";
43
+ /** Services required before registration: the registry this plugin feeds and the Loader it observes. */
44
+ const inject = ["typert", "loader"];
45
+ /** Validate explicit package names and default to Loader-entry discovery only. */
46
+ const Config = z.object({ packages: z.array(z.string().min(1)).default([]) });
47
+ const MEMBER_KINDS = new Set([
48
+ "property",
49
+ "method",
50
+ "getter",
51
+ "setter",
52
+ "call",
53
+ "construct",
54
+ "index"
55
+ ]);
56
+ /** Resolve the `./typert` export to a relative path, accepting the string and one-level conditional forms. */
57
+ function typertExportOf(pkgName, exportsField) {
58
+ if (typeof exportsField !== "object" || exportsField === null) return void 0;
59
+ const target = exportsField[TYPERT_HOST_EXPORT];
60
+ if (target === void 0) return void 0;
61
+ if (typeof target === "string") return target;
62
+ if (typeof target === "object" && target !== null) {
63
+ const fallback = target.default;
64
+ if (typeof fallback === "string") return fallback;
65
+ }
66
+ throw new Error(`typert-loader: ${pkgName} exports["${TYPERT_HOST_EXPORT}"] must be a string or an object with a string default`);
67
+ }
68
+ /**
69
+ * Narrow a dynamically imported typert module's `TYPERT` export to a
70
+ * contribution owned by `pkgName`. This is the module/file boundary: the
71
+ * manifest crosses from a build artifact into the typed registry, so every
72
+ * field is checked and every failure names the package and the defect.
73
+ * @param pkgName - the package whose typert face was imported.
74
+ * @param exported - the module's `TYPERT` export.
75
+ * @returns the validated contribution.
76
+ */
77
+ function validateTypertManifest(pkgName, exported) {
78
+ if (typeof exported !== "object" || exported === null) throw new Error(`typert-loader: ${pkgName} exports "${TYPERT_HOST_EXPORT}" but its module has no TYPERT manifest object`);
79
+ const manifest = exported;
80
+ if (manifest.package !== pkgName) throw new Error(`typert-loader: ${pkgName} TYPERT manifest names package ${JSON.stringify(manifest.package)} — the manifest must be owned by the package that exports it`);
81
+ if (manifest.face !== "host") throw new Error(`typert-loader: ${pkgName} exports "${TYPERT_HOST_EXPORT}" but TYPERT.face is not "host"`);
82
+ if (!Array.isArray(manifest.schemas)) throw new Error(`typert-loader: ${pkgName} TYPERT.schemas must be an array`);
83
+ for (const value of manifest.schemas) {
84
+ if (typeof value !== "object" || value === null) throw new Error(`typert-loader: ${pkgName} TYPERT.schemas contains a non-object schema`);
85
+ const schema = value;
86
+ requireString(pkgName, schema, "name", "schema");
87
+ if (typeof schema.schema !== "object" || schema.schema === null || !("_zod" in schema.schema)) throw new Error(`typert-loader: ${pkgName} TYPERT schema "${schema.name}" is not a zod v4 schema instance`);
88
+ }
89
+ const model = requireObject(pkgName, manifest.model, "TYPERT.model");
90
+ const services = requireArray(pkgName, model.services, "TYPERT.model.services");
91
+ const events = requireArray(pkgName, model.events, "TYPERT.model.events");
92
+ const objects = requireArray(pkgName, model.objects, "TYPERT.model.objects");
93
+ for (const value of services) {
94
+ const service = requireObject(pkgName, value, "service");
95
+ requireDocumentation(pkgName, service, "service");
96
+ requireString(pkgName, service, "key", "service");
97
+ requireString(pkgName, service, "exportName", "service");
98
+ requireMembers(pkgName, service.members, `service "${service.key}"`);
99
+ requireTypes(pkgName, service.types, `service "${service.key}"`);
100
+ }
101
+ for (const value of events) {
102
+ const event = requireObject(pkgName, value, "event");
103
+ requireDocumentation(pkgName, event, "event");
104
+ requireString(pkgName, event, "name", "event");
105
+ requireString(pkgName, event, "signature", `event "${event.name}"`);
106
+ if (event.mode !== void 0 && typeof event.mode !== "string") throw new Error(`typert-loader: ${pkgName} event "${event.name}" mode must be a string`);
107
+ }
108
+ for (const value of objects) {
109
+ const object = requireObject(pkgName, value, "object");
110
+ requireDocumentation(pkgName, object, "object");
111
+ requireString(pkgName, object, "name", "object");
112
+ requireString(pkgName, object, "exportName", "object");
113
+ requireMembers(pkgName, object.members, `object "${object.name}"`);
114
+ requireTypes(pkgName, object.types, `object "${object.name}"`);
115
+ }
116
+ for (const value of requireArray(pkgName, manifest.invocations, "TYPERT.invocations")) requireInvocation(pkgName, value);
117
+ return manifest;
118
+ }
119
+ function requireObject(pkgName, value, subject) {
120
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`typert-loader: ${pkgName} ${subject} must be an object`);
121
+ return value;
122
+ }
123
+ function requireArray(pkgName, value, subject) {
124
+ if (!Array.isArray(value)) throw new Error(`typert-loader: ${pkgName} ${subject} must be an array`);
125
+ return value;
126
+ }
127
+ function requireString(pkgName, value, key, subject) {
128
+ if (typeof value[key] !== "string" || value[key].length === 0) throw new Error(`typert-loader: ${pkgName} ${subject} has a missing or empty ${key}`);
129
+ }
130
+ function requireDocumentation(pkgName, value, subject) {
131
+ requireArray(pkgName, value.tags, `${subject}.tags`);
132
+ for (const key of [
133
+ "description",
134
+ "summary",
135
+ "jsDoc"
136
+ ]) if (value[key] !== void 0 && typeof value[key] !== "string") throw new Error(`typert-loader: ${pkgName} ${subject}.${key} must be a string`);
137
+ }
138
+ function requireMembers(pkgName, value, subject) {
139
+ for (const item of requireArray(pkgName, value, `${subject}.members`)) {
140
+ const member = requireObject(pkgName, item, `${subject} member`);
141
+ requireString(pkgName, member, "name", `${subject} member`);
142
+ requireString(pkgName, member, "signature", `${subject} member`);
143
+ if (typeof member.kind !== "string" || !MEMBER_KINDS.has(member.kind)) throw new Error(`typert-loader: ${pkgName} ${subject} member "${member.name}" has invalid kind`);
144
+ }
145
+ }
146
+ function requireTypes(pkgName, value, subject) {
147
+ for (const item of requireArray(pkgName, value, `${subject}.types`)) {
148
+ const type = requireObject(pkgName, item, `${subject} type`);
149
+ requireString(pkgName, type, "name", `${subject} type`);
150
+ requireString(pkgName, type, "declaration", `${subject} type`);
151
+ }
152
+ }
153
+ function requireInvocation(pkgName, value) {
154
+ const invocation = requireObject(pkgName, value, "invocation");
155
+ for (const key of [
156
+ "id",
157
+ "service",
158
+ "namespace",
159
+ "method"
160
+ ]) requireString(pkgName, invocation, key, "invocation");
161
+ const id = invocation.id;
162
+ const receiver = requireObject(pkgName, invocation.invocation, `invocation "${id}" receiver`);
163
+ if (receiver.kind === "context") {
164
+ requireString(pkgName, receiver, "context", `invocation "${id}" Context receiver`);
165
+ requireString(pkgName, receiver, "wire", `invocation "${id}" Context receiver`);
166
+ requireStrictCodec(pkgName, receiver.codec, `invocation "${id}" Context codec`);
167
+ } else if (receiver.kind !== "direct") throw new Error(`typert-loader: ${pkgName} invocation "${id}" receiver kind must be "direct" or "context"`);
168
+ const wires = /* @__PURE__ */ new Set();
169
+ const parameters = /* @__PURE__ */ new Map();
170
+ let lookupCount = 0;
171
+ for (const valueParameter of requireArray(pkgName, invocation.parameters, `invocation "${id}" parameters`)) {
172
+ const parameter = requireObject(pkgName, valueParameter, `invocation "${id}" parameter`);
173
+ requireString(pkgName, parameter, "name", `invocation "${id}" parameter`);
174
+ requireString(pkgName, parameter, "wire", `invocation "${id}" parameter`);
175
+ const wire = parameter.wire;
176
+ if (wires.has(wire)) throw new Error(`typert-loader: ${pkgName} invocation "${id}" repeats wire field "${wire}"`);
177
+ wires.add(wire);
178
+ if (parameter.source === "lookup") {
179
+ lookupCount += 1;
180
+ requireString(pkgName, parameter, "lookup", `invocation "${id}" lookup parameter`);
181
+ } else if (parameter.source === "json") {
182
+ if (parameter.lookup !== void 0) throw new Error(`typert-loader: ${pkgName} invocation "${id}" JSON parameter declares a lookup`);
183
+ } else throw new Error(`typert-loader: ${pkgName} invocation "${id}" parameter source must be "json" or "lookup"`);
184
+ parameters.set(wire, parameter);
185
+ requireStrictCodec(pkgName, parameter.codec, `invocation "${id}" parameter codec`);
186
+ }
187
+ if (invocation.cancellation !== void 0) {
188
+ if (requireObject(pkgName, invocation.cancellation, `invocation "${id}" cancellation`).parameter !== "signal") throw new Error(`typert-loader: ${pkgName} invocation "${id}" cancellation parameter must be "signal"`);
189
+ }
190
+ if (invocation.scope !== void 0) {
191
+ if (receiver.kind !== "direct") throw new Error(`typert-loader: ${pkgName} invocation "${id}" Context receiver cannot declare a direct scope projection`);
192
+ const scope = requireObject(pkgName, invocation.scope, `invocation "${id}" scope`);
193
+ requireString(pkgName, scope, "context", `invocation "${id}" scope`);
194
+ requireString(pkgName, scope, "wire", `invocation "${id}" scope`);
195
+ const parameter = parameters.get(scope.wire);
196
+ if (lookupCount !== 1 || parameter?.source !== "lookup" || parameter.lookup !== scope.context) throw new Error(`typert-loader: ${pkgName} invocation "${id}" scope wire "${scope.wire}" must select its only lookup parameter`);
197
+ }
198
+ if (receiver.kind === "context" && wires.has(receiver.wire)) throw new Error(`typert-loader: ${pkgName} invocation "${id}" repeats Context wire field "${receiver.wire}"`);
199
+ requireStrictCodec(pkgName, invocation.result, `invocation "${id}" result codec`);
200
+ if (invocation.sourceLocation !== void 0) {
201
+ const location = requireObject(pkgName, invocation.sourceLocation, `invocation "${id}" sourceLocation`);
202
+ requireString(pkgName, location, "file", `invocation "${id}" sourceLocation`);
203
+ for (const key of ["line", "column"]) if (!Number.isInteger(location[key]) || location[key] < 1) throw new Error(`typert-loader: ${pkgName} invocation "${id}" sourceLocation.${key} must be a positive integer`);
204
+ }
205
+ }
206
+ function requireStrictCodec(pkgName, value, subject) {
207
+ const codec = requireObject(pkgName, value, subject);
208
+ if (codec.mode !== "strict") throw new Error(`typert-loader: ${pkgName} ${subject} must use a strict codec`);
209
+ requireString(pkgName, codec, "typeSymbol", subject);
210
+ if (typeof codec.schema !== "object" || codec.schema === null || !("_zod" in codec.schema) || typeof codec.schema.parse !== "function") throw new Error(`typert-loader: ${pkgName} ${subject} is not backed by a zod v4 schema`);
211
+ }
212
+ /**
213
+ * Scan current Loader entries during activation, then follow entry mounts and
214
+ * unmounts for this plugin's lifetime.
215
+ * @param ctx - plugin context carrying `typert` and `loader`.
216
+ * @param config - explicit package artifacts in addition to Loader entries.
217
+ */
218
+ async function apply(ctx, config) {
219
+ if (ctx.baseUrl === void 0) throw new Error("typert-loader: ctx.baseUrl is unset — the loader needs the config-tree anchor to resolve plugin packages");
220
+ const require = createRequire(ctx.baseUrl);
221
+ const configured = new Set(config.packages);
222
+ const registered = /* @__PURE__ */ new Map();
223
+ const pending = /* @__PURE__ */ new Map();
224
+ const artifactPath = /* @__PURE__ */ new Map();
225
+ const manifests = /* @__PURE__ */ new Map();
226
+ const dirty = /* @__PURE__ */ new Set();
227
+ let flushQueued = false;
228
+ let active = true;
229
+ ctx.effect(function* () {
230
+ yield () => {
231
+ active = false;
232
+ dirty.clear();
233
+ };
234
+ }, "typert loader lifetime");
235
+ const resolveArtifact = (pkgName) => {
236
+ const cached = artifactPath.get(pkgName);
237
+ if (cached !== void 0) return cached;
238
+ let pkgPath;
239
+ try {
240
+ pkgPath = require.resolve(`${pkgName}/package.json`);
241
+ } catch (cause) {
242
+ if (configured.has(pkgName)) throw new Error(`typert-loader: configured package "${pkgName}" cannot be resolved from the config tree — add it to the composition package dependencies or remove it from packages`, { cause });
243
+ artifactPath.set(pkgName, null);
244
+ return null;
245
+ }
246
+ const rel = typertExportOf(pkgName, JSON.parse(readFileSync(pkgPath, "utf8")).exports);
247
+ if (rel === void 0 && configured.has(pkgName)) throw new Error(`typert-loader: configured package "${pkgName}" does not export "${TYPERT_HOST_EXPORT}"`);
248
+ const resolved = rel === void 0 ? null : join(dirname(pkgPath), rel);
249
+ artifactPath.set(pkgName, resolved);
250
+ return resolved;
251
+ };
252
+ const loadManifest = (pkgName, path) => {
253
+ let loading = manifests.get(pkgName);
254
+ if (loading === void 0) {
255
+ loading = import(__rewriteRelativeImportExtension(pathToFileURL(path).href)).then((mod) => validateTypertManifest(pkgName, mod.TYPERT), (cause) => {
256
+ throw new Error(`typert-loader: ${pkgName} exports "${TYPERT_HOST_EXPORT}" but importing ${path} failed: ${String(cause)}`);
257
+ });
258
+ manifests.set(pkgName, loading);
259
+ }
260
+ return loading;
261
+ };
262
+ const qualifies = (entryName) => {
263
+ if (configured.has(entryName)) return true;
264
+ for (const entry of ctx.loader.entries()) if (entry.options.name === entryName && entry.fiber !== void 0 && !entry.disabled) return true;
265
+ return false;
266
+ };
267
+ /** Reconcile one entry name against the live loader entries; a mount returns its async task. */
268
+ const processOne = (entryName) => {
269
+ if (!qualifies(entryName)) {
270
+ const dispose = registered.get(entryName);
271
+ if (dispose !== void 0) {
272
+ registered.delete(entryName);
273
+ return dispose();
274
+ }
275
+ return;
276
+ }
277
+ if (registered.has(entryName) || pending.has(entryName)) return void 0;
278
+ const path = resolveArtifact(entryName);
279
+ if (path === null) return void 0;
280
+ const task = loadManifest(entryName, path).then((manifest) => {
281
+ if (!active || !qualifies(entryName) || registered.has(entryName)) return;
282
+ registered.set(entryName, ctx.typert.register(manifest));
283
+ });
284
+ pending.set(entryName, task);
285
+ const settle = () => {
286
+ pending.delete(entryName);
287
+ };
288
+ task.then(settle, settle);
289
+ return task;
290
+ };
291
+ const flush = (onError) => {
292
+ const tasks = [];
293
+ for (const entryName of [...dirty]) {
294
+ dirty.delete(entryName);
295
+ try {
296
+ const task = processOne(entryName);
297
+ if (task !== void 0) tasks.push(task.catch((error) => {
298
+ onError(toError(error));
299
+ }));
300
+ } catch (error) {
301
+ onError(toError(error));
302
+ }
303
+ }
304
+ return tasks;
305
+ };
306
+ ctx.on("internal/plugin", (fiber) => {
307
+ const entryName = fiber.entry?.options.name;
308
+ if (entryName === void 0) return;
309
+ dirty.add(entryName);
310
+ if (flushQueued) return;
311
+ flushQueued = true;
312
+ queueMicrotask(() => {
313
+ flushQueued = false;
314
+ if (!active) return;
315
+ for (const task of flush((err) => {
316
+ ctx.logger.error(err);
317
+ }));
318
+ });
319
+ });
320
+ for (const packageName of configured) dirty.add(packageName);
321
+ for (const entry of ctx.loader.entries()) dirty.add(entry.options.name);
322
+ const failures = [];
323
+ await Promise.all(flush((err) => {
324
+ failures.push(err);
325
+ }));
326
+ if (failures.length > 0) throw new AggregateError(failures, `typert-loader: ${String(failures.length)} typert contributor(s) failed to register:\n${failures.map((e) => ` - ${e.message}`).join("\n")}`);
327
+ }
328
+ /** Normalize an arbitrary import or manifest failure to an Error. */
329
+ function toError(error) {
330
+ return error instanceof Error ? error : new Error(String(error));
331
+ }
332
+ //#endregion
333
+ export { Config, TYPERT_HOST_EXPORT, apply, inject, name, validateTypertManifest };
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@deepseek-ai/dsh-typert-loader`.
4
+ * @module @deepseek-ai/dsh-typert-loader/invariant
5
+ */
6
+ const PACKAGE_NAME = "@deepseek-ai/dsh-typert-loader";
7
+ /** Cordis companion plugin name. */
8
+ const name = "typert-loader-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: the Loader entry lifecycle directly owns each exact
13
+ * registry disposer, and integration tests observe registration and removal.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Typert Loader integration: automatic registration for mounted plugin packages.
3
+ *
4
+ * When a loader entry mounts, this plugin resolves the entry's package.json; a
5
+ * package exporting `./typert` has its host face imported and its
6
+ * `TYPERT` manifest registered into `ctx.typert`, and the registration is
7
+ * withdrawn when the entry unmounts. Explicit `packages` cover plugins nested
8
+ * behind another Loader entry, whose Cordis fibers carry no resolvable package
9
+ * specifier. Packages without the export are skipped silently when discovered
10
+ * from Loader entries; an explicit package or declared artifact that is broken
11
+ * fails loud — aggregated into this plugin's activation throw for existing
12
+ * entries, contained to a logged error per package in steady state.
13
+ *
14
+ * Scanning is incremental per entry name, mirroring the client-modules node
15
+ * half: every cordis `internal/plugin` emission marks the fiber's entry name
16
+ * dirty and a microtask flush reconciles each dirty name against the live
17
+ * loader entries; the activation pass seeds the same dirty set with all
18
+ * current entries. Package verdicts and imported manifests are cached per
19
+ * package name and never expire — plugin-set changes take effect on restart.
20
+ *
21
+ * Manual `ctx.typert.register()` remains available for contributions
22
+ * that do not use a `./typert` artifact (hand-written wire schemas,
23
+ * tests, non-loader compositions).
24
+ *
25
+ * @module @deepseek-ai/dsh-typert-loader
26
+ */
27
+ import type { Context } from '@deepseek-ai/cordis';
28
+ import z from '@deepseek-ai/schemastery';
29
+ import type { TypertContribution } from '@deepseek-ai/dsh-typert-registry/types';
30
+ /** The package.json exports key naming a package's host-face typert artifact. */
31
+ export declare const TYPERT_HOST_EXPORT = "./typert";
32
+ /** Cordis plugin name. */
33
+ export declare const name = "typert-loader";
34
+ /** Services required before registration: the registry this plugin feeds and the Loader it observes. */
35
+ export declare const inject: string[];
36
+ /** Additional package artifacts whose owning plugins are nested behind another Loader entry. */
37
+ export interface Config {
38
+ /** Exact npm package names that must resolve and export `./typert`. */
39
+ packages?: string[];
40
+ }
41
+ /** Validate explicit package names and default to Loader-entry discovery only. */
42
+ export declare const Config: z<Config>;
43
+ /**
44
+ * Narrow a dynamically imported typert module's `TYPERT` export to a
45
+ * contribution owned by `pkgName`. This is the module/file boundary: the
46
+ * manifest crosses from a build artifact into the typed registry, so every
47
+ * field is checked and every failure names the package and the defect.
48
+ * @param pkgName - the package whose typert face was imported.
49
+ * @param exported - the module's `TYPERT` export.
50
+ * @returns the validated contribution.
51
+ */
52
+ export declare function validateTypertManifest(pkgName: string, exported: unknown): TypertContribution;
53
+ /**
54
+ * Scan current Loader entries during activation, then follow entry mounts and
55
+ * unmounts for this plugin's lifetime.
56
+ * @param ctx - plugin context carrying `typert` and `loader`.
57
+ * @param config - explicit package artifacts in addition to Loader entries.
58
+ */
59
+ export declare function apply(ctx: Context, config: Config): Promise<void>;
60
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@deepseek-ai/dsh-typert-loader`.
3
+ * @module @deepseek-ai/dsh-typert-loader/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "typert-loader-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@deepseek-ai/dsh-typert-loader",
3
+ "description": "Loader integration for generated Typert package contributions",
4
+ "version": "0.0.1-rc.1",
5
+ "publishConfig": {
6
+ "access": "restricted"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/typert/loader"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "lib/index.js",
30
+ "lib/invariant.js",
31
+ "lib/types/**/*.d.ts"
32
+ ],
33
+ "license": "BSD-3-Clause",
34
+ "peerDependencies": {
35
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
36
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
37
+ "@deepseek-ai/dsh-typert-registry": "^0.0.1-rc.1",
38
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
39
+ },
40
+ "dependencies": {
41
+ "@deepseek-ai/schemastery": "^3.18.1-rc.1"
42
+ },
43
+ "devDependencies": {
44
+ "zod": "^4.4.3",
45
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
46
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
47
+ "@deepseek-ai/dsh-typert-registry": "^0.0.1-rc.1",
48
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
49
+ }
50
+ }