@objectstack/types 17.2.0 → 17.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +941 -0
- package/dist/index.d.mts +533 -9
- package/dist/index.d.ts +533 -9
- package/dist/index.js +309 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +293 -3
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +12 -3
- package/dist/node.d.ts +12 -3
- package/dist/node.js +198 -2
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +200 -4
- package/dist/node.mjs.map +1 -1
- package/package.json +3 -3
package/dist/node.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/node.ts
|
|
2
|
-
import { readFileSync } from "fs";
|
|
2
|
+
import { existsSync, readFileSync, realpathSync } from "fs";
|
|
3
3
|
import { createRequire } from "module";
|
|
4
|
-
import { join } from "path";
|
|
4
|
+
import { dirname, join, resolve, sep } from "path";
|
|
5
5
|
import { pathToFileURL } from "url";
|
|
6
6
|
|
|
7
7
|
// src/module-not-found.ts
|
|
@@ -56,7 +56,7 @@ function isDeclaredByHost(specifier, hostRoot) {
|
|
|
56
56
|
var HOST_IMPORT_FAILURE_KIND = "objectstackHostImportFailureKind";
|
|
57
57
|
function hostImportFailureKind(err) {
|
|
58
58
|
const kind = err?.[HOST_IMPORT_FAILURE_KIND];
|
|
59
|
-
return kind === "undeclared" || kind === "declared-unresolvable" ? kind : void 0;
|
|
59
|
+
return kind === "undeclared" || kind === "declared-unresolvable" || kind === "declared-no-loadable-entry" ? kind : void 0;
|
|
60
60
|
}
|
|
61
61
|
function hostImportError(kind, message, cause) {
|
|
62
62
|
const err = new Error(message);
|
|
@@ -98,6 +98,185 @@ function unresolvableMessage(declaration, cause) {
|
|
|
98
98
|
\u2022 it IS installed but its "main"/"exports" points at a dist that was never built
|
|
99
99
|
(resolver: ${detail})`;
|
|
100
100
|
}
|
|
101
|
+
var ESM_IMPORT_CONDITIONS = /* @__PURE__ */ new Set([
|
|
102
|
+
"node-addons",
|
|
103
|
+
"node",
|
|
104
|
+
"import",
|
|
105
|
+
"default"
|
|
106
|
+
]);
|
|
107
|
+
var CJS_REQUIRE_CONDITIONS = /* @__PURE__ */ new Set([
|
|
108
|
+
"node-addons",
|
|
109
|
+
"node",
|
|
110
|
+
"require",
|
|
111
|
+
"default"
|
|
112
|
+
]);
|
|
113
|
+
function selectConditionTarget(node, conditions) {
|
|
114
|
+
if (typeof node === "string") return node;
|
|
115
|
+
if (Array.isArray(node)) {
|
|
116
|
+
for (const alternative of node) {
|
|
117
|
+
const hit = selectConditionTarget(alternative, conditions);
|
|
118
|
+
if (hit !== void 0) return hit;
|
|
119
|
+
}
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
122
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
123
|
+
for (const entry of Object.entries(node)) {
|
|
124
|
+
if (!conditions.has(entry[0])) continue;
|
|
125
|
+
const hit = selectConditionTarget(entry[1], conditions);
|
|
126
|
+
if (hit !== void 0) return hit;
|
|
127
|
+
}
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
function resolveExportsSubpath(exportsField, subpath, conditions = ESM_IMPORT_CONDITIONS) {
|
|
131
|
+
if (exportsField === void 0) return void 0;
|
|
132
|
+
const keys = typeof exportsField === "object" && exportsField !== null && !Array.isArray(exportsField) ? Object.keys(exportsField) : void 0;
|
|
133
|
+
const isSubpathMap = keys !== void 0 && keys.length > 0 && keys.every((key) => key === "." || key.indexOf("./") === 0);
|
|
134
|
+
if (!isSubpathMap) {
|
|
135
|
+
return subpath === "." ? selectConditionTarget(exportsField, conditions) : void 0;
|
|
136
|
+
}
|
|
137
|
+
const map = exportsField;
|
|
138
|
+
if (Object.prototype.hasOwnProperty.call(map, subpath)) {
|
|
139
|
+
return selectConditionTarget(map[subpath], conditions);
|
|
140
|
+
}
|
|
141
|
+
let best;
|
|
142
|
+
for (const entry of Object.entries(map)) {
|
|
143
|
+
const star = entry[0].indexOf("*");
|
|
144
|
+
if (star < 0 || entry[0].indexOf("*", star + 1) >= 0) continue;
|
|
145
|
+
const prefix = entry[0].slice(0, star);
|
|
146
|
+
const suffix = entry[0].slice(star + 1);
|
|
147
|
+
if (subpath.indexOf(prefix) !== 0) continue;
|
|
148
|
+
if (suffix !== "" && subpath.slice(subpath.length - suffix.length) !== suffix) continue;
|
|
149
|
+
if (subpath.length < prefix.length + suffix.length) continue;
|
|
150
|
+
if (best !== void 0 && (best.prefix.length > prefix.length || best.prefix.length === prefix.length && best.suffix.length >= suffix.length)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
best = { prefix, suffix, target: entry[1] };
|
|
154
|
+
}
|
|
155
|
+
if (best === void 0) return void 0;
|
|
156
|
+
const matched = subpath.slice(best.prefix.length, subpath.length - best.suffix.length);
|
|
157
|
+
const target = selectConditionTarget(best.target, conditions);
|
|
158
|
+
return target === void 0 ? void 0 : target.split("*").join(matched);
|
|
159
|
+
}
|
|
160
|
+
function exportsSubpathOf(specifier, packageName) {
|
|
161
|
+
return specifier === packageName ? "." : `.${specifier.slice(packageName.length)}`;
|
|
162
|
+
}
|
|
163
|
+
function packageRootOf(resolvedFile, packageName) {
|
|
164
|
+
let dir = dirname(resolvedFile);
|
|
165
|
+
for (let hop = 0; hop < 64; hop += 1) {
|
|
166
|
+
try {
|
|
167
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
168
|
+
if (manifest.name === packageName) return dir;
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
const parent = dirname(dir);
|
|
172
|
+
if (parent === dir) return void 0;
|
|
173
|
+
dir = parent;
|
|
174
|
+
}
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
function esmEntryForDeclared(specifier, packageName, cjsResolved) {
|
|
178
|
+
const root = packageRootOf(cjsResolved, packageName);
|
|
179
|
+
if (root === void 0) return void 0;
|
|
180
|
+
let exportsField;
|
|
181
|
+
try {
|
|
182
|
+
exportsField = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).exports;
|
|
183
|
+
} catch {
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
if (exportsField === void 0 || exportsField === null) return void 0;
|
|
187
|
+
const subpath = exportsSubpathOf(specifier, packageName);
|
|
188
|
+
const target = resolveExportsSubpath(exportsField, subpath);
|
|
189
|
+
if (typeof target !== "string" || target.indexOf("./") !== 0) return void 0;
|
|
190
|
+
const entry = resolve(root, target);
|
|
191
|
+
if (entry.indexOf(root + sep) !== 0) return void 0;
|
|
192
|
+
return existsSync(entry) ? entry : void 0;
|
|
193
|
+
}
|
|
194
|
+
function hasInvalidExportsSubpathSegments(subpath) {
|
|
195
|
+
if (subpath === ".") return false;
|
|
196
|
+
return subpath.slice(2).split(/[/\\]/).some((raw) => {
|
|
197
|
+
const segment = raw.toLowerCase();
|
|
198
|
+
return segment === "" || segment === "." || segment === ".." || segment === "node_modules";
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
var ALIAS_DECLARATION_PROTOCOLS = [
|
|
202
|
+
{ prefix: "npm:", rangeRequired: false },
|
|
203
|
+
{ prefix: "workspace:", rangeRequired: true }
|
|
204
|
+
];
|
|
205
|
+
function declaredManifestName(declaration) {
|
|
206
|
+
const { packageName, specifier } = declaration;
|
|
207
|
+
if (specifier === void 0) return packageName;
|
|
208
|
+
const protocol = ALIAS_DECLARATION_PROTOCOLS.find((p) => specifier.indexOf(p.prefix) === 0);
|
|
209
|
+
if (protocol === void 0) return packageName;
|
|
210
|
+
const value = specifier.slice(protocol.prefix.length);
|
|
211
|
+
const at = value.lastIndexOf("@");
|
|
212
|
+
if (at <= 0 && protocol.rangeRequired) return packageName;
|
|
213
|
+
const name = at > 0 ? value.slice(0, at) : value;
|
|
214
|
+
return packageNameFromSpecifier(name) === name ? name : packageName;
|
|
215
|
+
}
|
|
216
|
+
function hostInstalledPackageDir(declaration) {
|
|
217
|
+
const { packageName, hostRoot } = declaration;
|
|
218
|
+
const linked = join(hostRoot, "node_modules", ...packageName.split("/"));
|
|
219
|
+
try {
|
|
220
|
+
const manifest = JSON.parse(readFileSync(join(linked, "package.json"), "utf8"));
|
|
221
|
+
if (manifest.name !== declaredManifestName(declaration)) return void 0;
|
|
222
|
+
} catch {
|
|
223
|
+
return void 0;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
return realpathSync(linked);
|
|
227
|
+
} catch {
|
|
228
|
+
return linked;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function declaredCjsResolveFallback(specifier, declaration) {
|
|
232
|
+
const { packageName } = declaration;
|
|
233
|
+
const packageDir = hostInstalledPackageDir(declaration);
|
|
234
|
+
if (packageDir === void 0) return { outcome: "absent" };
|
|
235
|
+
let exportsField;
|
|
236
|
+
try {
|
|
237
|
+
exportsField = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8")).exports;
|
|
238
|
+
} catch {
|
|
239
|
+
return { outcome: "absent" };
|
|
240
|
+
}
|
|
241
|
+
if (exportsField === void 0 || exportsField === null) return { outcome: "install-broken" };
|
|
242
|
+
const subpath = exportsSubpathOf(specifier, packageName);
|
|
243
|
+
if (hasInvalidExportsSubpathSegments(subpath)) return { outcome: "invalid-specifier" };
|
|
244
|
+
const importTarget = resolveExportsSubpath(exportsField, subpath, ESM_IMPORT_CONDITIONS);
|
|
245
|
+
if (typeof importTarget === "string" && importTarget.indexOf("./") === 0) {
|
|
246
|
+
const entry = resolve(packageDir, importTarget);
|
|
247
|
+
if (entry.indexOf(packageDir + sep) === 0 && existsSync(entry)) {
|
|
248
|
+
return { outcome: "entry", entry };
|
|
249
|
+
}
|
|
250
|
+
return { outcome: "install-broken" };
|
|
251
|
+
}
|
|
252
|
+
const requireTarget = resolveExportsSubpath(exportsField, subpath, CJS_REQUIRE_CONDITIONS);
|
|
253
|
+
if (typeof requireTarget === "string" && requireTarget.indexOf("./") === 0) {
|
|
254
|
+
return { outcome: "install-broken" };
|
|
255
|
+
}
|
|
256
|
+
return { outcome: "no-loadable-entry", packageDir };
|
|
257
|
+
}
|
|
258
|
+
function noLoadableEntryMessage(declaration, packageDir, subpath, cause) {
|
|
259
|
+
const { packageName, hostRoot, field, specifier } = declaration;
|
|
260
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
261
|
+
const subpathNote = subpath === "." ? 'its main entry (".")' : `the subpath '${subpath}'`;
|
|
262
|
+
return `Cannot load module '${packageName}': the host app DECLARES it (${field}: ${JSON.stringify(specifier)}) and it IS installed, but the package publishes no entry that Node can load.
|
|
263
|
+
host app: ${hostRoot}
|
|
264
|
+
installed at: ${packageDir}
|
|
265
|
+
|
|
266
|
+
This is a problem with the PACKAGE's own published shape, not with the app or
|
|
267
|
+
its install \u2014 the declaration is right and the package is on disk, so neither
|
|
268
|
+
re-reading package.json nor re-running \`pnpm install\` can change anything.
|
|
269
|
+
Measured from its manifest:
|
|
270
|
+
\u2022 its "exports" map names no \`require\`-condition entry for ${subpathNote},
|
|
271
|
+
so a CommonJS resolution cannot see it at all
|
|
272
|
+
\u2022 and no \`import\`-condition entry either, so there is nothing for the ESM
|
|
273
|
+
fallback to load
|
|
274
|
+
The remedy lives in the package: it must publish a runtime entry for this
|
|
275
|
+
subpath (an \`import\` condition suffices here; a dual build adds \`require\`).
|
|
276
|
+
A publish carrying only \`types\` / \`browser\`-style conditions cannot be loaded
|
|
277
|
+
by a Node host at all.
|
|
278
|
+
(resolver: ${detail})`;
|
|
279
|
+
}
|
|
101
280
|
function createHostImporter(hostRoot = process.cwd(), options = {}) {
|
|
102
281
|
const hostRequire = createHostRequire(hostRoot);
|
|
103
282
|
const { fallbackImport } = options;
|
|
@@ -118,13 +297,30 @@ function createHostImporter(hostRoot = process.cwd(), options = {}) {
|
|
|
118
297
|
try {
|
|
119
298
|
resolved = hostRequire.resolve(pkg);
|
|
120
299
|
} catch (cause) {
|
|
300
|
+
const fallback = declaredCjsResolveFallback(pkg, declaration);
|
|
301
|
+
if (fallback.outcome === "entry") {
|
|
302
|
+
return import(pathToFileURL(fallback.entry).href);
|
|
303
|
+
}
|
|
304
|
+
if (fallback.outcome === "no-loadable-entry") {
|
|
305
|
+
throw hostImportError(
|
|
306
|
+
"declared-no-loadable-entry",
|
|
307
|
+
noLoadableEntryMessage(
|
|
308
|
+
declaration,
|
|
309
|
+
fallback.packageDir,
|
|
310
|
+
exportsSubpathOf(pkg, declaration.packageName),
|
|
311
|
+
cause
|
|
312
|
+
),
|
|
313
|
+
cause
|
|
314
|
+
);
|
|
315
|
+
}
|
|
121
316
|
throw hostImportError(
|
|
122
317
|
"declared-unresolvable",
|
|
123
318
|
unresolvableMessage(declaration, cause),
|
|
124
319
|
cause
|
|
125
320
|
);
|
|
126
321
|
}
|
|
127
|
-
|
|
322
|
+
const entry = esmEntryForDeclared(pkg, declaration.packageName, resolved) ?? resolved;
|
|
323
|
+
return import(pathToFileURL(entry).href);
|
|
128
324
|
}
|
|
129
325
|
try {
|
|
130
326
|
return await importAsCaller(pkg);
|
package/dist/node.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/node.ts","../src/module-not-found.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `@objectstack/types/node` — the **node-only** slice of the shared utilities.\n *\n * WHY A SUBPATH AND NOT THE ROOT EXPORT. `@objectstack/types` is a dependency of\n * `@objectstack/hono`, whose whole reason to exist is \"edge-compatible REST API\n * server for Cloudflare Workers, Deno, Bun, and Node\" — and of the plugin/service\n * layer a `LiteKernel` boots on Workers. The root entry (`src/index.ts`) reaches\n * **zero** `node:` builtins today, and that is a property those consumers depend\n * on: a Workers bundle that pulls in `node:module` fails to build (or dies at\n * first call) even when nothing ever invokes it. Everything here needs\n * `node:module` / `node:url` by definition — it exists to drive Node's own\n * resolver — so it lives behind its own entry point instead.\n *\n * The isolation is structural, not conventional: `tsup` builds `src/index.ts` and\n * `src/node.ts` as separate entries with `splitting: false`, so the root bundle\n * contains no reference to this file, and `node-isolation.test.ts` fails the\n * build if anything reachable from the root ever imports a `node:` builtin. Same\n * arrangement `@objectstack/metadata` already ships for `./node`.\n *\n * ── What lives here ──────────────────────────────────────────────────────────\n *\n * Resolving optional packages from the **host app**, not from the framework\n * package doing the importing.\n *\n * Node ESM resolves a bare `import('pkg')` against the **importer's own\n * realpath**. Framework packages (the CLI, `@objectstack/verify`,\n * `@objectstack/dogfood`) are reached through `link:`/workspace dependencies, so\n * their realpath is inside the *framework* workspace — a bare import from any of\n * them can only ever see packages installed in the framework's own\n * `node_modules`. Every package that lives OUTSIDE that workspace and is supplied\n * by the app being served, verified or tested — a cloud-private package such as\n * `@objectstack/organizations` or `@objectstack/service-ai-studio`, or anything a\n * customer installs into their own project — is therefore invisible to a bare\n * import, no matter what the host app declares in its `package.json`\n * (cloud#1013: `objectstack serve` could never load the enterprise multi-org\n * runtime, so every self-hosted walled-posture deployment hit the ADR-0093 D5\n * fail-fast and exited 1; framework#4700: `bootStack({ multiTenant: true })` told\n * apps to install a package they had already installed, and the dogfood\n * multi-org probes were constant-false).\n *\n * The fix is to resolve from the host app's root and import the resolved\n * absolute path. The importing package's own resolution stays as the fallback,\n * for the framework-owned packages it depends on and the host does not declare\n * — and since #10943 that fallback is the base the CALLER hands in\n * ({@link HostImporterOptions.fallbackImport}), because a fallback written here\n * resolved from `@objectstack/types` and could only ever see\n * `@objectstack/spec`. Same defect class as the paragraph above, one level up:\n * a bare import resolves against the module that CONTAINS it, and this module\n * is not the one doing the asking.\n *\n * Resolution failure is the ONLY thing that falls back. A package the host\n * resolves but that throws while it evaluates is a genuine crash and propagates\n * unchanged: re-importing it bare would replace the real cause with a\n * `MODULE_NOT_FOUND`, which every caller here classifies as \"not installed\" —\n * turning a broken package into a silent skip (or, on the organizations path,\n * into a fatal message telling the operator to install what is already there).\n *\n * ── #4719: the host's DECLARATION gates the lookup, not its resolvability ────\n *\n * \"Resolve from the host app\" was implemented as a CJS `createRequire` anchored\n * at the host's `package.json`, and **CJS resolution honours `NODE_PATH`**\n * (`Module.globalPaths`). The first thing a pnpm-generated bin shim does is\n *\n * export NODE_PATH=\"<workspace>/node_modules/.pnpm/node_modules\"\n *\n * and every `serve` / `dev` child process inherits it. Everything any package in\n * the workspace transitively depends on lives in that hoisted store, so\n * `hostRequire.resolve(pkg)` succeeded for packages the host app had never\n * declared — the answer depended on HOW THE PROCESS WAS LAUNCHED, not on the\n * app. Measured on cloud's `apps/objectos-ee`, which did not declare\n * `@objectstack/organizations`: `pnpm start` (through the shim) booted with the\n * organizations plugin mounted and ADR-0093 D5 silent, while\n * `node node_modules/@objectstack/cli/bin/run.js serve` (no shim, no NODE_PATH)\n * hit the D5 fail-fast and exited 1. Same app, same `package.json`, same\n * posture. D5's own message told operators to \"declare it in the app's\n * package.json\" — the one thing the CLI never checked.\n *\n * So the host lookup is now gated on the host's **declaration**: a package name\n * is looked up in the host's `node_modules` only when it appears in the host\n * `package.json` (see {@link HOST_DECLARATION_FIELDS}). Reachability through a\n * hoisted store or `NODE_PATH` is deliberately not accepted — it is precisely\n * the accident that made the contract unenforced. This is the \"declared =\n * enforced\" shape the rest of the repo uses (Prime Directive #10): the\n * declaration is a deliberate authoring act, machine-checkable at the moment of\n * boot, and independent of launcher, package manager and hoist layout.\n *\n * The two failures it separates were, until now, one indistinguishable\n * `MODULE_NOT_FOUND`, with opposite remedies:\n *\n * - **undeclared** — the app never asked for this package. Remedy: declare it\n * in the app's `package.json` and install.\n * - **declared but unresolvable** — the app asked for it and the install is\n * broken/pruned/unbuilt. Remedy: fix the install. Re-reading the\n * `package.json` is wasted effort; the declaration is right there.\n *\n * {@link hostImportFailureKind} exposes that classification to callers so their\n * fail-fast text can say which one it is (`packages/cli` ADR-0093 D5,\n * `packages/verify` `bootStack`, `packages/qa/dogfood`'s enterprise probe).\n */\n\nimport { readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { isModuleNotFoundError } from './module-not-found.js';\n\n/**\n * Imports a package as the host app would see it.\n *\n * `any` is the module namespace of a package this repo does not compile against\n * (it is not a dependency of the importing package at all) — every call site\n * reads an export off it dynamically, exactly as the bare `import()` it replaces\n * did.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type HostImporter = (pkg: string) => Promise<any>;\n\n/**\n * The importing package's OWN dynamic import — write it literally, in the\n * calling module:\n *\n * createHostImporter(hostRoot, { fallbackImport: (s) => import(s) })\n *\n * `any` for the same reason {@link HostImporter} uses it: the module namespace\n * belongs to a package this repo does not compile against.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type FallbackImport = (specifier: string) => Promise<any>;\n\n/** Options for {@link createHostImporter}. */\nexport interface HostImporterOptions {\n /**\n * The resolution base for everything the host app does NOT declare — supplied\n * as the caller's own `import()` rather than as a URL string, because a\n * string base was MEASURED to be unimplementable without a regression. See\n * {@link createHostImporter}'s \"why a function\" note for both measurements.\n *\n * Omitted ⇒ the fallback resolves from `@objectstack/types`, which sees only\n * `@objectstack/types`'s own dependencies. That default is retained so an\n * out-of-tree caller cannot be broken by this parameter's arrival, and the\n * `undeclared` failure text names it explicitly so the gap reports itself\n * instead of being rediscovered.\n */\n fallbackImport?: FallbackImport;\n}\n\n/**\n * A `require` anchored at the **host app's** `package.json` — i.e. the project\n * `objectstack serve` was invoked in, or the app `bootStack` is verifying, whose\n * `node_modules` carries the packages it declares.\n *\n * @param hostRoot Directory holding the host app's `package.json` (default: the\n * process CWD, which is where the CLI reads `objectstack.config.ts` from too).\n */\nexport function createHostRequire(hostRoot: string = process.cwd()): NodeRequire {\n return createRequire(join(hostRoot, 'package.json'));\n}\n\n/**\n * The `package.json` fields whose KEYS count as a host-app declaration (#4719).\n *\n * All four are deliberate authoring acts in the app's own manifest that name the\n * package, which is the signal this gate is built on — not \"is it reachable\".\n * Why each is in:\n *\n * - `dependencies` — the obvious one: the app runs with it.\n * - `devDependencies` — the app being served / verified / dogfooded IS the\n * project, not a library someone else consumes, so its dev deps are installed\n * in exactly the environment this resolver runs in.\n * - `optionalDependencies` — npm/pnpm install them and tolerate an install\n * failure. \"Installed ⇒ declared\" holds; and if it did NOT install, the\n * declared-but-unresolvable branch says so precisely instead of pretending the\n * app never asked.\n * - `peerDependencies` — an app is nobody's peer, so this is an unusual place to\n * put an enterprise add-on; but it still NAMES the package on purpose, and\n * `packages/cli`'s own edition gate (`serve`'s AI-service opt-in, #1597) has\n * read all four since it was written. Accepting three here and four there\n * would fork \"declared\" into two dialects for one question — the shape Prime\n * Directive #12 exists to prevent. That gate now delegates to this list, so\n * there is one owner and one answer.\n *\n * `bundleDependencies` is absent on purpose: it is an array of names that must\n * ALSO appear in `dependencies`, so it can never be the only declaration.\n */\nexport const HOST_DECLARATION_FIELDS = [\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n 'peerDependencies',\n] as const;\n\nexport type HostDeclarationField = (typeof HOST_DECLARATION_FIELDS)[number];\n\n/** What the host app's `package.json` says about one package name. */\nexport interface HostDeclaration {\n /** Bare package name the specifier belongs to (subpath stripped). */\n packageName: string;\n /** Directory whose `package.json` was consulted. */\n hostRoot: string;\n /** True when {@link packageName} is a key of one of {@link HOST_DECLARATION_FIELDS}. */\n declared: boolean;\n /** Which field carried it (first match, in {@link HOST_DECLARATION_FIELDS} order). */\n field?: HostDeclarationField;\n /** The version range AS WRITTEN — `^1.2.3`, `workspace:*`, `npm:@acme/x@1`, `link:../x`. */\n specifier?: string;\n /** True when `hostRoot` has no readable / parseable `package.json` at all. */\n manifestMissing?: boolean;\n}\n\n/**\n * The package a bare specifier belongs to, or `undefined` when the specifier is\n * not a bare package name at all (a relative/absolute path, a `file:`/`data:`\n * URL, or a `node:`-prefixed builtin). Those bypass the declaration gate: they\n * are not things a `package.json` can declare.\n *\n * Subpaths are stripped, so `@objectstack/platform-objects/plugin` is declared\n * by `\"@objectstack/platform-objects\"`, which is the only key that can exist.\n * Scoped names keep both segments.\n *\n * Alias dependencies need no special case, and that is the point: with\n * `\"foo\": \"npm:bar@1\"` the importable specifier is `foo` and the manifest key is\n * `foo`, so keying on the KEY (never the value) is exactly right — `import('bar')`\n * correctly reads as undeclared unless `bar` is itself a key. Same for\n * `workspace:` / `link:` / `file:` specifiers: the key is the name, the value is\n * the package manager's business.\n */\nexport function packageNameFromSpecifier(specifier: string): string | undefined {\n if (!specifier || specifier.startsWith('.') || specifier.startsWith('/')) return undefined;\n // A URL-ish or protocol-prefixed specifier (`node:fs`, `file:///…`, `data:…`).\n if (/^[a-z][a-z0-9+.-]*:/i.test(specifier)) return undefined;\n const segments = specifier.split('/');\n if (specifier.startsWith('@')) {\n if (segments.length < 2 || !segments[0] || !segments[1]) return undefined;\n return `${segments[0]}/${segments[1]}`;\n }\n return segments[0] || undefined;\n}\n\n/**\n * Read what the host app's `package.json` declares about `specifier`.\n *\n * Deliberately a plain manifest READ, never a resolution attempt: resolvability\n * is the property #4719 proved unreliable (it moved with `NODE_PATH` and the\n * hoist layout), while the manifest is the same fact in every launcher.\n */\nexport function readHostDeclaration(\n specifier: string,\n hostRoot: string = process.cwd(),\n): HostDeclaration {\n const packageName = packageNameFromSpecifier(specifier) ?? specifier;\n const base: HostDeclaration = { packageName, hostRoot, declared: false };\n\n let manifest: Record<string, unknown>;\n try {\n manifest = JSON.parse(readFileSync(join(hostRoot, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >;\n } catch {\n // No manifest ⇒ nothing is declared. Recorded rather than swallowed so the\n // failure text can say \"there is no package.json here\" instead of the\n // misleading \"you did not declare it\".\n return { ...base, manifestMissing: true };\n }\n\n for (const field of HOST_DECLARATION_FIELDS) {\n const entries = manifest[field];\n if (!entries || typeof entries !== 'object') continue;\n const specifierValue = (entries as Record<string, unknown>)[packageName];\n if (specifierValue === undefined) continue;\n return { ...base, declared: true, field, specifier: String(specifierValue) };\n }\n return base;\n}\n\n/** Convenience predicate over {@link readHostDeclaration}. */\nexport function isDeclaredByHost(specifier: string, hostRoot?: string): boolean {\n return readHostDeclaration(specifier, hostRoot).declared;\n}\n\n/**\n * Why a {@link HostImporter} could not produce a module.\n *\n * - `undeclared` — the host app's `package.json` never names the package, and\n * the importing framework package cannot supply it either. Remedy: DECLARE it\n * in the app and install.\n * - `declared-unresolvable` — the app declares it and it still would not\n * resolve. Remedy: fix the INSTALL. Re-reading the manifest is wasted effort.\n *\n * An evaluation crash is neither: it propagates untouched and carries no kind.\n */\nexport type HostImportFailureKind = 'undeclared' | 'declared-unresolvable';\n\n/**\n * Property carrying {@link HostImportFailureKind} on a thrown error.\n *\n * A string property, read by {@link hostImportFailureKind} — never `instanceof`.\n * `serve` loads plugins through this importer, so CLI and package can hold\n * different module instances of anything class-shaped; the #4818 comment in\n * `serve.ts` names that trap explicitly.\n */\nexport const HOST_IMPORT_FAILURE_KIND = 'objectstackHostImportFailureKind';\n\n/** The classification on an error thrown by a {@link HostImporter}, if any. */\nexport function hostImportFailureKind(err: unknown): HostImportFailureKind | undefined {\n const kind = (err as Record<string, unknown> | null | undefined)?.[HOST_IMPORT_FAILURE_KIND];\n return kind === 'undeclared' || kind === 'declared-unresolvable' ? kind : undefined;\n}\n\nfunction hostImportError(\n kind: HostImportFailureKind,\n message: string,\n cause: unknown,\n): Error {\n // `cause` is assigned rather than passed to the constructor: this package\n // compiles against a lib without the ES2022 `ErrorOptions` overload.\n const err = new Error(message);\n // Every caller classifies \"missing vs crashed\" through\n // `isModuleNotFoundError`; both of these ARE the missing case, just with\n // different remedies, so they must keep answering true to it.\n return Object.assign(err, {\n cause,\n code: 'MODULE_NOT_FOUND',\n [HOST_IMPORT_FAILURE_KIND]: kind,\n });\n}\n\n/**\n * @param callerBaseSupplied Did the caller state its own resolution base\n * ({@link HostImporterOptions.fallbackImport})? When it did not, the fallback\n * ran from `@objectstack/types`, which sees only `@objectstack/spec` — so the\n * absence being reported may be an artefact of the missing base rather than a\n * real one. #10943 kept that default for out-of-tree callers; saying so here is\n * what stops it being silent, because the alternative is a reader re-deriving\n * the whole measurement from a `MODULE_NOT_FOUND` that names nothing.\n */\nfunction undeclaredMessage(\n declaration: HostDeclaration,\n cause: unknown,\n callerBaseSupplied: boolean,\n): string {\n const { packageName, hostRoot, manifestMissing } = declaration;\n const detail = cause instanceof Error ? cause.message : String(cause);\n const baseNote = callerBaseSupplied\n ? ''\n : '\\n (the caller did not pass `fallbackImport`, so that fallback resolved from\\n' +\n \" @objectstack/types, which can see only its own dependencies — a caller that\\n\" +\n ' needs its own resolution passes `{ fallbackImport: (s) => import(s) }`, #10943)';\n return (\n `Cannot find package '${packageName}': the host app does not declare it.\\n` +\n ` host app: ${hostRoot}\\n` +\n (manifestMissing\n ? ' no readable package.json was found there — nothing can be declared\\n'\n : ` checked: ${HOST_DECLARATION_FIELDS.join(', ')}\\n`) +\n `\\n Declare it in that app's package.json and install it, e.g.\\n` +\n ` cd ${hostRoot} && pnpm add ${packageName}\\n` +\n '\\n Being merely REACHABLE is not enough and is rejected on purpose (#4719):\\n' +\n ' a package hoisted into a workspace store — which is what NODE_PATH points\\n' +\n \" at in every pnpm bin shim — used to resolve here regardless of the app's\\n\" +\n ' package.json, so the same app booted or refused depending on how the\\n' +\n ' process was launched. The declaration is the contract.\\n' +\n ` (fallback resolution also failed: ${detail})${baseNote}`\n );\n}\n\nfunction unresolvableMessage(declaration: HostDeclaration, cause: unknown): string {\n const { packageName, hostRoot, field, specifier } = declaration;\n const detail = cause instanceof Error ? cause.message : String(cause);\n return (\n `Cannot find module '${packageName}': the host app DECLARES it ` +\n `(${field}: ${JSON.stringify(specifier)}) but it could not be resolved.\\n` +\n ` host app: ${hostRoot}\\n` +\n '\\n This is an INSTALL problem, not a declaration problem — the declaration is\\n' +\n ' already there, so re-reading the package.json will not help. Check:\\n' +\n ` • dependencies never installed, or installed before the declaration was added → run \\`pnpm install\\` in ${hostRoot}\\n` +\n ' • a production prune / filtered deploy dropped it (devDependencies and\\n' +\n ' optionalDependencies go first)\\n' +\n ' • it IS installed but its \"main\"/\"exports\" points at a dist that was never built\\n' +\n ` (resolver: ${detail})`\n );\n}\n\n/**\n * Build an importer that loads a package **as the host app declares it**, and\n * otherwise falls back to the importing package's own resolution.\n *\n * Order of operations, and why (#4719):\n *\n * 1. The host `package.json` is READ. Only a declared name is looked up in the\n * host's `node_modules`. An undeclared name never reaches the host resolver,\n * so no amount of `NODE_PATH` / hoisting can make it appear to be the app's.\n * 2. Declared but unresolvable is reported AS SUCH — the app asked for it and\n * the install is broken. It is not retried bare: falling back there would\n * reintroduce exactly the \"some other package happens to supply it\" accident\n * this gate closes, and would report an install problem as an absence.\n * 3. Undeclared falls back to the CALLER's own resolution, which is what keeps\n * every framework-owned load working (`serve`'s plugin-auth / service-i18n\n * path, `bootStack`'s service plugins). Bare `import()` is ESM, and ESM does\n * not honour `NODE_PATH`, so the fallback cannot re-open the hole either.\n * Only when that fails as module-not-found does the undeclared error\n * surface; a package that RESOLVES and then throws while evaluating is a\n * genuine crash and propagates untouched, as before.\n *\n * ── The caller supplies that base, and why it is a FUNCTION (#10943) ─────────\n *\n * Step 3 said \"the importing package's own resolution\" long before anything\n * made it true. The fallback was a bare `import()` written HERE, and ESM\n * resolves a bare specifier against the module containing the call — so it\n * resolved from `@objectstack/types`, which under a pnpm-isolated layout can\n * see only `@objectstack/types`'s own dependencies. Measured on `main` from an\n * app declaring nothing, `@objectstack/plugin-auth`, `@objectstack/plugin-audit`\n * and `chalk` all resolve from `packages/cli` and all failed through this\n * helper; `@objectstack/spec` — the one dependency this package declares — was\n * the only name that came back OK, which is the whole pattern. Under a hoisted\n * npm/yarn layout the same fallback usually DOES find the caller's\n * dependencies, so the claim was green in some installs and absent in others:\n * the layout-dependence class cloud#1013 and #10645 exist to close, one level\n * up. A declared contract the implementation does not keep is the thing this\n * repo fixes at the producer (Prime Directive #12), so the mechanism moved\n * rather than the sentence.\n *\n * The base arrives as the caller's own `import()` and NOT as a `parentURL` /\n * `import.meta.url` string. Both string spellings were measured on Node\n * v22.22.2 and both are wrong:\n *\n * - `import.meta.resolve(specifier, parentURL)` — the parent argument is\n * SILENTLY IGNORED without `--experimental-import-meta-resolve`. Measured:\n * resolving `@objectstack/plugin-auth` against a `packages/types` parent\n * returned `packages/cli/node_modules/...`, i.e. the caller's own answer,\n * byte-identical to passing no parent at all. It would have compiled, run,\n * and pinned green while ignoring the base — a phantom fix of exactly the\n * kind this card is about.\n * - `createRequire(parentURL).resolve(specifier)` — CJS resolution, which\n * honours `NODE_PATH`. Measured against a store reachable only through\n * `NODE_PATH`: the CJS resolve found it (with and without the `paths`\n * option, since GLOBAL_FOLDERS are always appended) while the ESM bare\n * `import()` did not. That is #4719's hole re-opened on the fallback path,\n * and it would have falsified the \"ESM does not honour NODE_PATH\" sentence\n * three lines above.\n *\n * A function written in the calling module is the only spelling that uses\n * Node's real ESM resolver anchored where the caller actually lives: no flag,\n * no `NODE_PATH`, no second resolution algorithm to drift from the first.\n *\n * @param hostRoot Directory holding the host app's `package.json` (default: the\n * process CWD, which is where the CLI reads `objectstack.config.ts` from too).\n * Note this used to take a pre-built `NodeRequire`; it needs the ROOT now,\n * because a `NodeRequire` cannot be asked where it was anchored and the manifest\n * has to be read from there.\n * @param options {@link HostImporterOptions.fallbackImport} carries the caller's\n * resolution base. Omitting it keeps the pre-#10943 behaviour (this package's\n * own resolution) so no out-of-tree caller changes under its feet.\n */\nexport function createHostImporter(\n hostRoot: string = process.cwd(),\n options: HostImporterOptions = {},\n): HostImporter {\n const hostRequire = createHostRequire(hostRoot);\n const { fallbackImport } = options;\n const importAsCaller: FallbackImport =\n fallbackImport ?? ((specifier) => import(/* webpackIgnore: true */ specifier));\n return async (pkg: string): Promise<any> => {\n // Not a bare package name (a path, a URL, a `node:` builtin) — nothing a\n // manifest could declare. Hand it to the normal resolver untouched.\n //\n // ⚠️ Deliberately NOT re-based onto `fallbackImport` (#10943). Every\n // base-INDEPENDENT spelling here — `file://`, `node:`, `data:`, an absolute\n // path — means the same module whoever imports it, so the base is not a\n // question they can even ask. The one spelling it WOULD move is a RELATIVE\n // one, and where that should resolve from is an open policy question owned\n // by #10944 (`serve` refuses a relative `plugins: [...]` entry rather than\n // silently re-basing it) — with a measured consumer count of zero here:\n // `serve` handles non-package specifiers before this helper is reached, and\n // `bootStack` / the dogfood probe pass package names only. Answering half\n // of another card's undecided question, for nobody, is not a repair.\n if (packageNameFromSpecifier(pkg) === undefined) {\n return import(/* webpackIgnore: true */ pkg);\n }\n\n const declaration = readHostDeclaration(pkg, hostRoot);\n\n if (declaration.declared) {\n let resolved: string;\n try {\n resolved = hostRequire.resolve(pkg);\n } catch (cause) {\n throw hostImportError(\n 'declared-unresolvable',\n unresolvableMessage(declaration, cause),\n cause,\n );\n }\n return import(pathToFileURL(resolved).href);\n }\n\n try {\n return await importAsCaller(pkg);\n } catch (cause) {\n // A package that resolved and then exploded is a crash, not an absence.\n if (!isModuleNotFoundError(cause)) throw cause;\n throw hostImportError(\n 'undeclared',\n undeclaredMessage(declaration, cause, fallbackImport !== undefined),\n cause,\n );\n }\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * True when a dynamic `import()` / `require.resolve()` failed because the\n * module is simply NOT INSTALLED — as opposed to the module being present but\n * throwing while it loads (a real crash). Checking `err.code` FIRST matters:\n * ESM reports a missing package as `err.code === 'ERR_MODULE_NOT_FOUND'` with\n * the human message `Cannot find package '...'`; matching only the older\n * `Cannot find module` string mis-classifies that as a crash (framework#1595).\n *\n * Single shared owner for this classification (framework#3265): the CLI's\n * optional-plugin guards and `requires` capability resolver delegate here, and\n * cloud's `objectos-runtime` capability loader is expected to adopt it at its\n * next framework pin bump — so the parallel loaders cannot drift apart and\n * re-introduce the #1595 false-alarm class.\n */\nexport function isModuleNotFoundError(err: unknown): boolean {\n const code = (err as { code?: string } | null | undefined)?.code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') return true;\n const msg = err instanceof Error ? err.message : String(err);\n return msg.includes('Cannot find module') || msg.includes('Cannot find package');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/node.ts","../src/module-not-found.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `@objectstack/types/node` — the **node-only** slice of the shared utilities.\n *\n * WHY A SUBPATH AND NOT THE ROOT EXPORT. `@objectstack/types` is a dependency of\n * `@objectstack/hono`, whose whole reason to exist is \"edge-compatible REST API\n * server for Cloudflare Workers, Deno, Bun, and Node\" — and of the plugin/service\n * layer a `LiteKernel` boots on Workers. The root entry (`src/index.ts`) reaches\n * **zero** `node:` builtins today, and that is a property those consumers depend\n * on: a Workers bundle that pulls in `node:module` fails to build (or dies at\n * first call) even when nothing ever invokes it. Everything here needs\n * `node:module` / `node:url` by definition — it exists to drive Node's own\n * resolver — so it lives behind its own entry point instead.\n *\n * The isolation is structural, not conventional: `tsup` builds `src/index.ts` and\n * `src/node.ts` as separate entries with `splitting: false`, so the root bundle\n * contains no reference to this file, and `node-isolation.test.ts` fails the\n * build if anything reachable from the root ever imports a `node:` builtin. Same\n * arrangement `@objectstack/metadata` already ships for `./node`.\n *\n * ── What lives here ──────────────────────────────────────────────────────────\n *\n * Resolving optional packages from the **host app**, not from the framework\n * package doing the importing.\n *\n * Node ESM resolves a bare `import('pkg')` against the **importer's own\n * realpath**. Framework packages (the CLI, `@objectstack/verify`,\n * `@objectstack/dogfood`) are reached through `link:`/workspace dependencies, so\n * their realpath is inside the *framework* workspace — a bare import from any of\n * them can only ever see packages installed in the framework's own\n * `node_modules`. Every package that lives OUTSIDE that workspace and is supplied\n * by the app being served, verified or tested — a cloud-private package such as\n * `@objectstack/organizations` or `@objectstack/service-ai-studio`, or anything a\n * customer installs into their own project — is therefore invisible to a bare\n * import, no matter what the host app declares in its `package.json`\n * (cloud#1013: `objectstack serve` could never load the enterprise multi-org\n * runtime, so every self-hosted walled-posture deployment hit the ADR-0093 D5\n * fail-fast and exited 1; framework#4700: `bootStack({ multiTenant: true })` told\n * apps to install a package they had already installed, and the dogfood\n * multi-org probes were constant-false).\n *\n * The fix is to resolve from the host app's root and import the resolved\n * absolute path. The importing package's own resolution stays as the fallback,\n * for the framework-owned packages it depends on and the host does not declare\n * — and since #10943 that fallback is the base the CALLER hands in\n * ({@link HostImporterOptions.fallbackImport}), because a fallback written here\n * resolved from `@objectstack/types` and could only ever see\n * `@objectstack/spec`. Same defect class as the paragraph above, one level up:\n * a bare import resolves against the module that CONTAINS it, and this module\n * is not the one doing the asking.\n *\n * Resolution failure is the ONLY thing that falls back. A package the host\n * resolves but that throws while it evaluates is a genuine crash and propagates\n * unchanged: re-importing it bare would replace the real cause with a\n * `MODULE_NOT_FOUND`, which every caller here classifies as \"not installed\" —\n * turning a broken package into a silent skip (or, on the organizations path,\n * into a fatal message telling the operator to install what is already there).\n *\n * ── #4719: the host's DECLARATION gates the lookup, not its resolvability ────\n *\n * \"Resolve from the host app\" was implemented as a CJS `createRequire` anchored\n * at the host's `package.json`, and **CJS resolution honours `NODE_PATH`**\n * (`Module.globalPaths`). The first thing a pnpm-generated bin shim does is\n *\n * export NODE_PATH=\"<workspace>/node_modules/.pnpm/node_modules\"\n *\n * and every `serve` / `dev` child process inherits it. Everything any package in\n * the workspace transitively depends on lives in that hoisted store, so\n * `hostRequire.resolve(pkg)` succeeded for packages the host app had never\n * declared — the answer depended on HOW THE PROCESS WAS LAUNCHED, not on the\n * app. Measured on cloud's `apps/objectos-ee`, which did not declare\n * `@objectstack/organizations`: `pnpm start` (through the shim) booted with the\n * organizations plugin mounted and ADR-0093 D5 silent, while\n * `node node_modules/@objectstack/cli/bin/run.js serve` (no shim, no NODE_PATH)\n * hit the D5 fail-fast and exited 1. Same app, same `package.json`, same\n * posture. D5's own message told operators to \"declare it in the app's\n * package.json\" — the one thing the CLI never checked.\n *\n * So the host lookup is now gated on the host's **declaration**: a package name\n * is looked up in the host's `node_modules` only when it appears in the host\n * `package.json` (see {@link HOST_DECLARATION_FIELDS}). Reachability through a\n * hoisted store or `NODE_PATH` is deliberately not accepted — it is precisely\n * the accident that made the contract unenforced. This is the \"declared =\n * enforced\" shape the rest of the repo uses (Prime Directive #10): the\n * declaration is a deliberate authoring act, machine-checkable at the moment of\n * boot, and independent of launcher, package manager and hoist layout.\n *\n * The two failures it separates were, until now, one indistinguishable\n * `MODULE_NOT_FOUND`, with opposite remedies:\n *\n * - **undeclared** — the app never asked for this package. Remedy: declare it\n * in the app's `package.json` and install.\n * - **declared but unresolvable** — the app asked for it and the install is\n * broken/pruned/unbuilt. Remedy: fix the install. Re-reading the\n * `package.json` is wasted effort; the declaration is right there.\n *\n * #14041 adds a third, split OUT of the second: **declared, installed, and the\n * package publishes no entry Node can load** — a shape problem in the package\n * itself, which no install action can ever fix (the `HostImportFailureKind`\n * doc carries the split; the \"#14041\" section note below carries the finder\n * that makes an ESM-only publish load instead of failing at all).\n *\n * {@link hostImportFailureKind} exposes that classification to callers so their\n * fail-fast text can say which one it is (`packages/cli` ADR-0093 D5,\n * `packages/verify` `bootStack`, `packages/qa/dogfood`'s enterprise probe).\n */\n\nimport { existsSync, readFileSync, realpathSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join, resolve, sep } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { isModuleNotFoundError } from './module-not-found.js';\n\n/**\n * Imports a package as the host app would see it.\n *\n * `any` is the module namespace of a package this repo does not compile against\n * (it is not a dependency of the importing package at all) — every call site\n * reads an export off it dynamically, exactly as the bare `import()` it replaces\n * did.\n */\nexport type HostImporter = (pkg: string) => Promise<any>;\n\n/**\n * The importing package's OWN dynamic import — write it literally, in the\n * calling module:\n *\n * createHostImporter(hostRoot, { fallbackImport: (s) => import(s) })\n *\n * `any` for the same reason {@link HostImporter} uses it: the module namespace\n * belongs to a package this repo does not compile against.\n */\nexport type FallbackImport = (specifier: string) => Promise<any>;\n\n/** Options for {@link createHostImporter}. */\nexport interface HostImporterOptions {\n /**\n * The resolution base for everything the host app does NOT declare — supplied\n * as the caller's own `import()` rather than as a URL string, because a\n * string base was MEASURED to be unimplementable without a regression. See\n * {@link createHostImporter}'s \"why a function\" note for both measurements.\n *\n * Omitted ⇒ the fallback resolves from `@objectstack/types`, which sees only\n * `@objectstack/types`'s own dependencies. That default is retained so an\n * out-of-tree caller cannot be broken by this parameter's arrival, and the\n * `undeclared` failure text names it explicitly so the gap reports itself\n * instead of being rediscovered.\n */\n fallbackImport?: FallbackImport;\n}\n\n/**\n * A `require` anchored at the **host app's** `package.json` — i.e. the project\n * `objectstack serve` was invoked in, or the app `bootStack` is verifying, whose\n * `node_modules` carries the packages it declares.\n *\n * @param hostRoot Directory holding the host app's `package.json` (default: the\n * process CWD, which is where the CLI reads `objectstack.config.ts` from too).\n */\nexport function createHostRequire(hostRoot: string = process.cwd()): NodeRequire {\n return createRequire(join(hostRoot, 'package.json'));\n}\n\n/**\n * The `package.json` fields whose KEYS count as a host-app declaration (#4719).\n *\n * All four are deliberate authoring acts in the app's own manifest that name the\n * package, which is the signal this gate is built on — not \"is it reachable\".\n * Why each is in:\n *\n * - `dependencies` — the obvious one: the app runs with it.\n * - `devDependencies` — the app being served / verified / dogfooded IS the\n * project, not a library someone else consumes, so its dev deps are installed\n * in exactly the environment this resolver runs in.\n * - `optionalDependencies` — npm/pnpm install them and tolerate an install\n * failure. \"Installed ⇒ declared\" holds; and if it did NOT install, the\n * declared-but-unresolvable branch says so precisely instead of pretending the\n * app never asked.\n * - `peerDependencies` — an app is nobody's peer, so this is an unusual place to\n * put an enterprise add-on; but it still NAMES the package on purpose, and\n * `packages/cli`'s own edition gate (`serve`'s AI-service opt-in, #1597) has\n * read all four since it was written. Accepting three here and four there\n * would fork \"declared\" into two dialects for one question — the shape Prime\n * Directive #12 exists to prevent. That gate now delegates to this list, so\n * there is one owner and one answer.\n *\n * `bundleDependencies` is absent on purpose: it is an array of names that must\n * ALSO appear in `dependencies`, so it can never be the only declaration.\n */\nexport const HOST_DECLARATION_FIELDS = [\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n 'peerDependencies',\n] as const;\n\nexport type HostDeclarationField = (typeof HOST_DECLARATION_FIELDS)[number];\n\n/** What the host app's `package.json` says about one package name. */\nexport interface HostDeclaration {\n /** Bare package name the specifier belongs to (subpath stripped). */\n packageName: string;\n /** Directory whose `package.json` was consulted. */\n hostRoot: string;\n /** True when {@link packageName} is a key of one of {@link HOST_DECLARATION_FIELDS}. */\n declared: boolean;\n /** Which field carried it (first match, in {@link HOST_DECLARATION_FIELDS} order). */\n field?: HostDeclarationField;\n /** The version range AS WRITTEN — `^1.2.3`, `workspace:*`, `npm:@acme/x@1`, `link:../x`. */\n specifier?: string;\n /** True when `hostRoot` has no readable / parseable `package.json` at all. */\n manifestMissing?: boolean;\n}\n\n/**\n * The package a bare specifier belongs to, or `undefined` when the specifier is\n * not a bare package name at all (a relative/absolute path, a `file:`/`data:`\n * URL, or a `node:`-prefixed builtin). Those bypass the declaration gate: they\n * are not things a `package.json` can declare.\n *\n * Subpaths are stripped, so `@objectstack/platform-objects/plugin` is declared\n * by `\"@objectstack/platform-objects\"`, which is the only key that can exist.\n * Scoped names keep both segments.\n *\n * Alias dependencies need no special case, and that is the point: with\n * `\"foo\": \"npm:bar@1\"` the importable specifier is `foo` and the manifest key is\n * `foo`, so keying on the KEY (never the value) is exactly right — `import('bar')`\n * correctly reads as undeclared unless `bar` is itself a key. Same for\n * `workspace:` / `link:` / `file:` specifiers: the key is the name, the value is\n * the package manager's business.\n */\nexport function packageNameFromSpecifier(specifier: string): string | undefined {\n if (!specifier || specifier.startsWith('.') || specifier.startsWith('/')) return undefined;\n // A URL-ish or protocol-prefixed specifier (`node:fs`, `file:///…`, `data:…`).\n if (/^[a-z][a-z0-9+.-]*:/i.test(specifier)) return undefined;\n const segments = specifier.split('/');\n if (specifier.startsWith('@')) {\n if (segments.length < 2 || !segments[0] || !segments[1]) return undefined;\n return `${segments[0]}/${segments[1]}`;\n }\n return segments[0] || undefined;\n}\n\n/**\n * Read what the host app's `package.json` declares about `specifier`.\n *\n * Deliberately a plain manifest READ, never a resolution attempt: resolvability\n * is the property #4719 proved unreliable (it moved with `NODE_PATH` and the\n * hoist layout), while the manifest is the same fact in every launcher.\n */\nexport function readHostDeclaration(\n specifier: string,\n hostRoot: string = process.cwd(),\n): HostDeclaration {\n const packageName = packageNameFromSpecifier(specifier) ?? specifier;\n const base: HostDeclaration = { packageName, hostRoot, declared: false };\n\n let manifest: Record<string, unknown>;\n try {\n manifest = JSON.parse(readFileSync(join(hostRoot, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >;\n } catch {\n // No manifest ⇒ nothing is declared. Recorded rather than swallowed so the\n // failure text can say \"there is no package.json here\" instead of the\n // misleading \"you did not declare it\".\n return { ...base, manifestMissing: true };\n }\n\n for (const field of HOST_DECLARATION_FIELDS) {\n const entries = manifest[field];\n if (!entries || typeof entries !== 'object') continue;\n const specifierValue = (entries as Record<string, unknown>)[packageName];\n if (specifierValue === undefined) continue;\n return { ...base, declared: true, field, specifier: String(specifierValue) };\n }\n return base;\n}\n\n/** Convenience predicate over {@link readHostDeclaration}. */\nexport function isDeclaredByHost(specifier: string, hostRoot?: string): boolean {\n return readHostDeclaration(specifier, hostRoot).declared;\n}\n\n/**\n * Why a {@link HostImporter} could not produce a module.\n *\n * - `undeclared` — the host app's `package.json` never names the package, and\n * the importing framework package cannot supply it either. Remedy: DECLARE it\n * in the app and install.\n * - `declared-unresolvable` — the app declares it and it still would not\n * resolve. Remedy: fix the INSTALL. Re-reading the manifest is wasted effort.\n * - `declared-no-loadable-entry` (#14041) — the app declares it, the install\n * delivered it, and the package's own `exports` names NO entry Node can load\n * for the requested subpath — no `require`-condition target (which is why the\n * CJS resolution refused) and no `import`-condition one for the fallback\n * either (a `types`-only or `browser`-only publish, or a subpath the map\n * never names). Remedy: change the PACKAGE — neither the app's manifest nor\n * its install can ever fix this, which is exactly why it must not share the\n * `declared-unresolvable` INSTALL wording.\n *\n * An evaluation crash is none of these: it propagates untouched and carries no\n * kind.\n */\nexport type HostImportFailureKind =\n | 'undeclared'\n | 'declared-unresolvable'\n | 'declared-no-loadable-entry';\n\n/**\n * Property carrying {@link HostImportFailureKind} on a thrown error.\n *\n * A string property, read by {@link hostImportFailureKind} — never `instanceof`.\n * `serve` loads plugins through this importer, so CLI and package can hold\n * different module instances of anything class-shaped; the #4818 comment in\n * `serve.ts` names that trap explicitly.\n */\nexport const HOST_IMPORT_FAILURE_KIND = 'objectstackHostImportFailureKind';\n\n/** The classification on an error thrown by a {@link HostImporter}, if any. */\nexport function hostImportFailureKind(err: unknown): HostImportFailureKind | undefined {\n const kind = (err as Record<string, unknown> | null | undefined)?.[HOST_IMPORT_FAILURE_KIND];\n return kind === 'undeclared' ||\n kind === 'declared-unresolvable' ||\n kind === 'declared-no-loadable-entry'\n ? kind\n : undefined;\n}\n\nfunction hostImportError(\n kind: HostImportFailureKind,\n message: string,\n cause: unknown,\n): Error {\n // `cause` is assigned rather than passed to the constructor: this package\n // compiles against a lib without the ES2022 `ErrorOptions` overload.\n const err = new Error(message);\n // Every caller classifies \"missing vs crashed\" through\n // `isModuleNotFoundError`; both of these ARE the missing case, just with\n // different remedies, so they must keep answering true to it.\n return Object.assign(err, {\n cause,\n code: 'MODULE_NOT_FOUND',\n [HOST_IMPORT_FAILURE_KIND]: kind,\n });\n}\n\n/**\n * @param callerBaseSupplied Did the caller state its own resolution base\n * ({@link HostImporterOptions.fallbackImport})? When it did not, the fallback\n * ran from `@objectstack/types`, which sees only `@objectstack/spec` — so the\n * absence being reported may be an artefact of the missing base rather than a\n * real one. #10943 kept that default for out-of-tree callers; saying so here is\n * what stops it being silent, because the alternative is a reader re-deriving\n * the whole measurement from a `MODULE_NOT_FOUND` that names nothing.\n */\nfunction undeclaredMessage(\n declaration: HostDeclaration,\n cause: unknown,\n callerBaseSupplied: boolean,\n): string {\n const { packageName, hostRoot, manifestMissing } = declaration;\n const detail = cause instanceof Error ? cause.message : String(cause);\n const baseNote = callerBaseSupplied\n ? ''\n : '\\n (the caller did not pass `fallbackImport`, so that fallback resolved from\\n' +\n \" @objectstack/types, which can see only its own dependencies — a caller that\\n\" +\n ' needs its own resolution passes `{ fallbackImport: (s) => import(s) }`, #10943)';\n return (\n `Cannot find package '${packageName}': the host app does not declare it.\\n` +\n ` host app: ${hostRoot}\\n` +\n (manifestMissing\n ? ' no readable package.json was found there — nothing can be declared\\n'\n : ` checked: ${HOST_DECLARATION_FIELDS.join(', ')}\\n`) +\n `\\n Declare it in that app's package.json and install it, e.g.\\n` +\n ` cd ${hostRoot} && pnpm add ${packageName}\\n` +\n '\\n Being merely REACHABLE is not enough and is rejected on purpose (#4719):\\n' +\n ' a package hoisted into a workspace store — which is what NODE_PATH points\\n' +\n \" at in every pnpm bin shim — used to resolve here regardless of the app's\\n\" +\n ' package.json, so the same app booted or refused depending on how the\\n' +\n ' process was launched. The declaration is the contract.\\n' +\n ` (fallback resolution also failed: ${detail})${baseNote}`\n );\n}\n\nfunction unresolvableMessage(declaration: HostDeclaration, cause: unknown): string {\n const { packageName, hostRoot, field, specifier } = declaration;\n const detail = cause instanceof Error ? cause.message : String(cause);\n return (\n `Cannot find module '${packageName}': the host app DECLARES it ` +\n `(${field}: ${JSON.stringify(specifier)}) but it could not be resolved.\\n` +\n ` host app: ${hostRoot}\\n` +\n '\\n This is an INSTALL problem, not a declaration problem — the declaration is\\n' +\n ' already there, so re-reading the package.json will not help. Check:\\n' +\n ` • dependencies never installed, or installed before the declaration was added → run \\`pnpm install\\` in ${hostRoot}\\n` +\n ' • a production prune / filtered deploy dropped it (devDependencies and\\n' +\n ' optionalDependencies go first)\\n' +\n ' • it IS installed but its \"main\"/\"exports\" points at a dist that was never built\\n' +\n ` (resolver: ${detail})`\n );\n}\n\n/**\n * ── #13330: the DECLARED leg must resolve with ESM semantics ─────────────────\n *\n * `hostRequire.resolve(pkg)` is a **CommonJS** resolution, and CJS resolution\n * answers the `require` condition. Every `tsup` dual build in this repo — and\n * essentially every dual build anywhere — publishes\n *\n * \"exports\": { \".\": { \"import\": \"./dist/index.js\", \"require\": \"./dist/index.cjs\" } }\n *\n * so that resolve returns `dist/index.cjs`, and `import()`ing a `.cjs` file\n * evaluates the package's **CommonJS** build. Everything that build then\n * `require`s is CJS too, all the way down.\n *\n * The importer's callers are ESM (`packages/cli` is `\"type\": \"module\"`), so\n * anything they load through their OWN import chain is the ESM build of the\n * same package. Loading a package here therefore produced a SECOND instance of\n * every module it shares with the caller — with its own module-scope state.\n *\n * That is not a theoretical difference. `serve` loads a cluster driver through\n * this leg; the driver's whole job is the side effect\n * `registerClusterDriver('redis', …)` against `@objectstack/service-cluster`'s\n * module-scope registry. Measured on the EE image, in one process:\n *\n * ESM instance: redis REGISTERED <- after a bare import() of the driver\n * CJS instance: NOT registered <- after this leg loaded the driver\n *\n * The Runtime reads the ESM instance, so `OS_CLUSTER_DRIVER=redis` on a\n * three-replica deployment died at `defineCluster()` with `Cluster driver\n * \"redis\" is not registered` while the package was installed, declared and\n * resolvable. Any module-scope registry crossing this seam has the same defect;\n * the cluster driver is simply the one that shipped.\n *\n * The fix is to select the entry the `import` condition names. There is no\n * flagless Node API that resolves a bare specifier against an arbitrary parent\n * (`import.meta.resolve`'s parent argument is ignored without\n * `--experimental-import-meta-resolve` — measured, see `createHostImporter`),\n * so the host-anchored ANSWER still comes from the CJS resolver, and only the\n * CONDITION is re-decided here: the CJS-resolved file locates the package on\n * disk, and the `import` entry of THAT package is what gets imported.\n *\n * Deliberately narrow at the RESOLUTION level — no load that works today\n * resolves differently unless the package itself publishes a valid, existing\n * import-condition target:\n *\n * - a package with no `exports` map is untouched — CJS resolution already\n * returned `main`, which is the only entry it publishes;\n * - a package whose `exports` names no import-condition target (CJS-only) is\n * untouched, and so is one whose two conditions name the same file;\n * - anything unreadable, unresolvable or absent on disk falls back to the\n * CJS-resolved path, i.e. to exactly the pre-#13330 behaviour.\n *\n * That narrowness does NOT extend to EVALUATION: every fallback above keys on\n * the `import` target being absent, unreadable or escaping the package root,\n * so none of them catches an `import` target that is present and broken. A\n * dual-published package whose `import` build throws while its `require` build\n * works used to mask that break by silently loading the CJS build; it now\n * surfaces it. Surfacing a broken published build is arguably the correct\n * reading, but it is a behaviour change, not a no-op.\n *\n * A residual split is still possible above this seam — an app and a framework\n * package holding two PHYSICAL copies of the same package are two instances in\n * any module system, and no resolver condition can merge them. That case is not\n * silent any more: `serve` reads the registry after the load and reports it\n * (`packages/cli/src/commands/serve.ts`, the cluster block).\n */\n\n/**\n * The conditions Node matches on an `import()` here.\n *\n * MEMBERSHIP, not priority: Node walks an exports object's KEYS in insertion\n * order and takes the first that names an active condition, so the manifest\n * decides precedence and this set only decides eligibility. `require` is absent\n * on purpose — selecting it is the defect above.\n */\nconst ESM_IMPORT_CONDITIONS: ReadonlySet<string> = new Set([\n 'node-addons',\n 'node',\n 'import',\n 'default',\n]);\n\n/**\n * The conditions a CommonJS `require()` matches — what `hostRequire.resolve`\n * itself answers. Used by the #14041 failure-kind split ONLY as a manifest\n * READ, never as a second resolution: when the CJS resolver has already\n * thrown, \"does the map name a `require`-condition target at all?\" is what\n * separates a broken install (it names one, the files are missing) from a\n * package that publishes no CommonJS entry in the first place.\n */\nconst CJS_REQUIRE_CONDITIONS: ReadonlySet<string> = new Set([\n 'node-addons',\n 'node',\n 'require',\n 'default',\n]);\n\n/**\n * Pick a target from one `exports` node under the given active conditions\n * (membership, not priority — see {@link ESM_IMPORT_CONDITIONS}).\n *\n * A string is a target; an array is a fallback list (first resolvable wins);\n * `null` blocks the subpath; an object is a condition map. Nesting is arbitrary\n * (`{ import: { types: …, default: … } }` is the shape `tsup` emits).\n */\nfunction selectConditionTarget(node: unknown, conditions: ReadonlySet<string>): string | undefined {\n if (typeof node === 'string') return node;\n if (Array.isArray(node)) {\n for (const alternative of node) {\n const hit = selectConditionTarget(alternative, conditions);\n if (hit !== undefined) return hit;\n }\n return undefined;\n }\n if (node === null || typeof node !== 'object') return undefined;\n for (const entry of Object.entries(node as Record<string, unknown>)) {\n if (!conditions.has(entry[0])) continue;\n const hit = selectConditionTarget(entry[1], conditions);\n if (hit !== undefined) return hit;\n }\n return undefined;\n}\n\n/**\n * Resolve one subpath (`.`, `./node`, `./forms/x`) of an `exports` field to the\n * relative target its import condition names.\n *\n * A map is recognised by its KEYS: exports whose keys all begin with `.` is a\n * subpath map, anything else is the root-condition sugar for `\".\"` — the same\n * test Node applies, and the reason `{ \"import\": …, \"require\": … }` needs no\n * special case here.\n */\nfunction resolveExportsSubpath(\n exportsField: unknown,\n subpath: string,\n conditions: ReadonlySet<string> = ESM_IMPORT_CONDITIONS,\n): string | undefined {\n if (exportsField === undefined) return undefined;\n\n const keys =\n typeof exportsField === 'object' && exportsField !== null && !Array.isArray(exportsField)\n ? Object.keys(exportsField as Record<string, unknown>)\n : undefined;\n const isSubpathMap =\n keys !== undefined && keys.length > 0 && keys.every((key) => key === '.' || key.indexOf('./') === 0);\n\n if (!isSubpathMap) {\n return subpath === '.' ? selectConditionTarget(exportsField, conditions) : undefined;\n }\n\n const map = exportsField as Record<string, unknown>;\n if (Object.prototype.hasOwnProperty.call(map, subpath)) {\n return selectConditionTarget(map[subpath], conditions);\n }\n\n // Pattern keys (`\"./*\": \"./dist/*.js\"`). Node takes the key with the longest\n // static prefix, breaking ties on the longest suffix, and substitutes the\n // matched span into the target's own `*`.\n let best: { prefix: string; suffix: string; target: unknown } | undefined;\n for (const entry of Object.entries(map)) {\n const star = entry[0].indexOf('*');\n if (star < 0 || entry[0].indexOf('*', star + 1) >= 0) continue;\n const prefix = entry[0].slice(0, star);\n const suffix = entry[0].slice(star + 1);\n if (subpath.indexOf(prefix) !== 0) continue;\n if (suffix !== '' && subpath.slice(subpath.length - suffix.length) !== suffix) continue;\n if (subpath.length < prefix.length + suffix.length) continue;\n if (\n best !== undefined &&\n (best.prefix.length > prefix.length ||\n (best.prefix.length === prefix.length && best.suffix.length >= suffix.length))\n ) {\n continue;\n }\n best = { prefix, suffix, target: entry[1] };\n }\n if (best === undefined) return undefined;\n const matched = subpath.slice(best.prefix.length, subpath.length - best.suffix.length);\n const target = selectConditionTarget(best.target, conditions);\n return target === undefined ? undefined : target.split('*').join(matched);\n}\n\n/** The `exports` subpath a specifier addresses (`.`, `./plugin`, `./deep/x`). */\nfunction exportsSubpathOf(specifier: string, packageName: string): string {\n return specifier === packageName ? '.' : `.${specifier.slice(packageName.length)}`;\n}\n\n/**\n * The directory of the package named `packageName` that owns `resolvedFile`.\n *\n * Walked up from the resolved entry rather than computed from the specifier,\n * because the resolver's answer is a REALPATH: under pnpm that is inside\n * `.pnpm/<pkg>@<version>/node_modules/<pkg>`, which is exactly the directory\n * whose `node_modules` the package's own transitive imports resolve against —\n * and exactly what makes one physical copy shared between the app and the\n * framework.\n */\nfunction packageRootOf(resolvedFile: string, packageName: string): string | undefined {\n let dir = dirname(resolvedFile);\n // Bounded on purpose: a package root is a few segments above its entry, and\n // an unbounded walk on a broken layout would stat every ancestor up to `/`.\n for (let hop = 0; hop < 64; hop += 1) {\n try {\n const manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as {\n name?: unknown;\n };\n // A NESTED manifest — the `{\"type\":\"commonjs\"}` marker a dual build drops\n // in `dist/` — carries no name, so it is walked THROUGH, not stopped at.\n if (manifest.name === packageName) return dir;\n } catch {\n // Not a manifest, or not readable. Keep walking.\n }\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n return undefined;\n}\n\n/**\n * The file the `import` condition names for `specifier`, or `undefined` when\n * this seam has nothing to change — see the narrowness list in the #13330 note.\n *\n * @param cjsResolved What `hostRequire.resolve(specifier)` answered. It is the\n * host-anchored part of the answer and is never second-guessed here; only the\n * CONDITION is re-decided.\n */\nfunction esmEntryForDeclared(\n specifier: string,\n packageName: string,\n cjsResolved: string,\n): string | undefined {\n const root = packageRootOf(cjsResolved, packageName);\n if (root === undefined) return undefined;\n\n let exportsField: unknown;\n try {\n exportsField = (\n JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as { exports?: unknown }\n ).exports;\n } catch {\n return undefined;\n }\n // No `exports` map ⇒ nothing to choose between: `main` is the only entry the\n // package publishes and CJS resolution already returned it.\n if (exportsField === undefined || exportsField === null) return undefined;\n\n const subpath = exportsSubpathOf(specifier, packageName);\n const target = resolveExportsSubpath(exportsField, subpath);\n if (typeof target !== 'string' || target.indexOf('./') !== 0) return undefined;\n\n const entry = resolve(root, target);\n // Node refuses an exports target that escapes its package; so does this.\n if (entry.indexOf(root + sep) !== 0) return undefined;\n return existsSync(entry) ? entry : undefined;\n}\n\n/**\n * ── #14041: an ESM-only package needs a finder the CJS resolver is not ───────\n *\n * The #13330 note above re-decides the CONDITION for a package the CJS\n * resolver already LOCATED. A package publishing only an `import` condition —\n * `{\"exports\": {\".\": {\"import\": \"./dist/index.js\"}}}`, ordinary outside this\n * workspace — never gets that far: `hostRequire.resolve` throws\n * `ERR_PACKAGE_PATH_NOT_EXPORTED`, and the declared leg classified EVERY\n * resolver throw as `declared-unresolvable` — an INSTALL-problem message about\n * an install that is fine, prescribing remedies (`pnpm install`, un-prune,\n * rebuild) none of which can ever help.\n *\n * The fallback finder is a `node_modules` lookup anchored at `hostRoot`, and\n * it is deliberately STRICTLY TIGHTER than the CJS resolution it backs up:\n *\n * - ONE directory — `<hostRoot>/node_modules/<name>` — the single place a\n * dependency the host declares and installs must physically appear;\n * - no `NODE_PATH` (the #4719 hole; honouring it here would reopen the\n * declaration gate from the fallback side);\n * - no walk above `hostRoot` (CJS resolution climbs every parent's\n * `node_modules`; a package that exists only up there is someone else's);\n * - no bare `require`/`import` of the specifier (a second resolver would\n * re-import every looseness one call at a time);\n * - Node's invalid-segment refusal, mirrored BEFORE exports resolution\n * ({@link hasInvalidExportsSubpathSegments}): a subpath carrying `''`,\n * `.`, `..` or `node_modules` segments is refused exactly as both of\n * Node's resolvers refuse it — the one validation the specifier has NOT\n * already passed by the time it reaches this catch (#14271 review).\n *\n * `import.meta.resolve` with a parent URL is NOT the mechanism, on the same\n * measurement the #10943 note below records: without\n * `--experimental-import-meta-resolve` the parent argument is SILENTLY\n * IGNORED, so it answers from the WRONG base with full confidence — the exact\n * failure class this card removes.\n *\n * It fires ONLY inside `hostRequire.resolve`'s catch — a path that was a hard\n * failure before — so no currently-succeeding load can change behaviour.\n *\n * When even this finder cannot produce an entry, the failure KIND is split on\n * one criterion: **can any install action ever help?**\n *\n * - the package is not in the host's `node_modules`, or its manifest NAMES a\n * runtime target whose file is missing (a dist never built, a partial\n * publish) → `declared-unresolvable`, the existing INSTALL wording,\n * unchanged — it is right for both;\n * - the package is installed and its manifest names NO runtime entry for the\n * requested subpath under either the `require` or the `import` conditions\n * (`types`-only, `browser`-only, an unexported subpath) →\n * `declared-no-loadable-entry`, a message about the PACKAGE's own shape —\n * no edit to the app and no install action can change what the package\n * publishes.\n */\ntype DeclaredCjsResolveFallback =\n /** Not present in the host's own `node_modules` — the install really is the problem. */\n | { outcome: 'absent' }\n /** Rescued: the `import`-condition entry to load. */\n | { outcome: 'entry'; entry: string }\n /** Present, and its manifest names a runtime target — the FILES are the problem. */\n | { outcome: 'install-broken' }\n /** Present, and its manifest names nothing loadable — the PACKAGE is the problem. */\n | { outcome: 'no-loadable-entry'; packageDir: string }\n /**\n * The SPECIFIER is the problem: its subpath carries segments Node's own\n * resolvers refuse (see {@link hasInvalidExportsSubpathSegments}). Never\n * rescued and never re-worded — it keeps exactly the hard failure and the\n * `declared-unresolvable` kind these specifiers get on the CJS path today.\n */\n | { outcome: 'invalid-specifier' };\n\n/**\n * Mirror of Node's `PACKAGE_TARGET_RESOLVE` invalid-segment refusal, applied\n * to the requested subpath BEFORE any exports resolution in the fallback\n * (#14271 contract review).\n *\n * Both of Node's resolvers refuse an exports subpath whose segments include\n * `''`, `.`, `..` or `node_modules` (case-insensitive) —\n * `ERR_INVALID_MODULE_SPECIFIER`, or `ERR_PACKAGE_PATH_NOT_EXPORTED` when an\n * import-only condition map refuses first. On the resolve-SUCCEEDED path\n * (#13330) the specifier has therefore already been validated by the real\n * resolver before the exports walk here ever sees it. Inside the fallback's\n * catch it has NOT: without this mirror, a pattern key (`./deep/*`) would\n * substitute a traversal span (`../../secret/hidden`) into its target and\n * resolve a NON-EXPORTED file inside the package — the byte-containment check\n * on the resolved entry permits any `..` traversal that lands back inside the\n * package root, by design (it guards escape, not encapsulation). Measured on\n * Node v22.22.2: `require.resolve` of such a specifier throws on both an\n * import-only and a dual-published pattern map, so refusing here keeps the\n * fallback strictly tighter than the CJS resolution it backs up on the\n * VALIDATION axis, exactly as it is on the location axes.\n */\nfunction hasInvalidExportsSubpathSegments(subpath: string): boolean {\n if (subpath === '.') return false;\n // `exportsSubpathOf` yields `./…`; validate every segment after that prefix.\n return subpath\n .slice(2)\n .split(/[/\\\\]/)\n .some((raw) => {\n const segment = raw.toLowerCase();\n return segment === '' || segment === '.' || segment === '..' || segment === 'node_modules';\n });\n}\n\n/**\n * Declaration values whose grammar is `<protocol>:<name>[@<range>]` — the two\n * spellings in which a host DECLARES that a key is an alias for a package with\n * a different name (#14278).\n *\n * `npm:` always names a package: `npm:bar@1`, `npm:@acme/x@^2`, or `npm:bar`\n * with no range at all. `workspace:` names one ONLY in its aliased form\n * (`workspace:bar@*`) — a bare `workspace:*` / `workspace:^1.2.3` is a RANGE,\n * so the key stays the name. Everything else — a plain range, `link:`,\n * `file:`, a git or tarball URL — carries no package name to expect: those\n * name a LOCATION or a version, and the manifest name they install under is\n * not derivable from the declaration at all.\n */\nconst ALIAS_DECLARATION_PROTOCOLS = [\n { prefix: 'npm:', rangeRequired: false },\n { prefix: 'workspace:', rangeRequired: true },\n] as const;\n\n/**\n * The manifest `name` the host's own declaration says\n * `<hostRoot>/node_modules/<key>` must carry — the key itself for an ordinary\n * dependency, the ALIASED package's name for `\"foo\": \"npm:bar@1\"` (#14278).\n *\n * ⚠️ This moves the finder's EXPECTATION, never its strictness. The\n * manifest-name check is what keeps the fallback strictly tighter than the CJS\n * resolution it backs up (#14041's property, #4719's gate): a finder that\n * accepted a directory without confirming it holds the declared package would\n * be a looser finder, and loosening it would trade a confidently-wrong remedy\n * for a wrong LOAD — the worse direction. So the expectation is still authored\n * by the host, read out of the same `package.json` the declaration gate reads;\n * only the sentence it spells changes, from \"the key\" to \"what the host says\n * the key is an alias for\". An aliased install pointing at one package still\n * refuses a directory holding another.\n *\n * Anything that does not parse as a bare package name yields no expectation to\n * move to, so the key stays and the pre-#14278 refusal is kept: a `workspace:`\n * range, an alias value carrying a subpath, a malformed value. Deliberate —\n * {@link packageNameFromSpecifier} is the one authority on what a package name\n * is here, and its own documentation blesses the aliased declaration shape.\n */\nfunction declaredManifestName(declaration: HostDeclaration): string {\n const { packageName, specifier } = declaration;\n if (specifier === undefined) return packageName;\n const protocol = ALIAS_DECLARATION_PROTOCOLS.find((p) => specifier.indexOf(p.prefix) === 0);\n if (protocol === undefined) return packageName;\n const value = specifier.slice(protocol.prefix.length);\n // `<name>@<range>`: the LAST `@` separates them, so a scoped name's own\n // leading `@` (index 0) is never mistaken for the separator.\n const at = value.lastIndexOf('@');\n if (at <= 0 && protocol.rangeRequired) return packageName;\n const name = at > 0 ? value.slice(0, at) : value;\n return packageNameFromSpecifier(name) === name ? name : packageName;\n}\n\n/**\n * The one directory the fallback finder consults, verified to hold the\n * declared package (a `package.json` whose `name` is the one\n * {@link declaredManifestName} reads out of the host's declaration) and then\n * realpath'd — under pnpm the link target is\n * `.pnpm/<pkg>@<version>/node_modules/<pkg>`, the directory the package's own\n * transitive imports resolve against, exactly as the CJS resolver's realpath\n * answer behaves on the succeeding path.\n */\nfunction hostInstalledPackageDir(declaration: HostDeclaration): string | undefined {\n const { packageName, hostRoot } = declaration;\n const linked = join(hostRoot, 'node_modules', ...packageName.split('/'));\n try {\n const manifest = JSON.parse(readFileSync(join(linked, 'package.json'), 'utf8')) as {\n name?: unknown;\n };\n if (manifest.name !== declaredManifestName(declaration)) return undefined;\n } catch {\n return undefined;\n }\n try {\n return realpathSync(linked);\n } catch {\n // The manifest read above already succeeded through this path; an exotic\n // realpath failure does not un-install the package.\n return linked;\n }\n}\n\n/** The #14041 fallback: see the section note above for the shape and the split. */\nfunction declaredCjsResolveFallback(\n specifier: string,\n declaration: HostDeclaration,\n): DeclaredCjsResolveFallback {\n const { packageName } = declaration;\n const packageDir = hostInstalledPackageDir(declaration);\n if (packageDir === undefined) return { outcome: 'absent' };\n\n let exportsField: unknown;\n try {\n exportsField = (\n JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8')) as { exports?: unknown }\n ).exports;\n } catch {\n return { outcome: 'absent' };\n }\n // No `exports` map ⇒ CJS resolution already tried everything such a package\n // publishes (`main`, the index files) and still threw: missing files.\n if (exportsField === undefined || exportsField === null) return { outcome: 'install-broken' };\n\n const subpath = exportsSubpathOf(specifier, packageName);\n // Refused BEFORE exports resolution — the specifier reaches this walk\n // unvalidated by any real resolver, unlike the #13330 path (see\n // hasInvalidExportsSubpathSegments).\n if (hasInvalidExportsSubpathSegments(subpath)) return { outcome: 'invalid-specifier' };\n\n const importTarget = resolveExportsSubpath(exportsField, subpath, ESM_IMPORT_CONDITIONS);\n if (typeof importTarget === 'string' && importTarget.indexOf('./') === 0) {\n const entry = resolve(packageDir, importTarget);\n // Node refuses an exports target that escapes its package; so does this.\n if (entry.indexOf(packageDir + sep) === 0 && existsSync(entry)) {\n return { outcome: 'entry', entry };\n }\n // The manifest names an `import` target and the file is not there — a\n // dist never built or a partial publish. An install/build problem, with\n // the existing wording's remedies intact.\n return { outcome: 'install-broken' };\n }\n\n const requireTarget = resolveExportsSubpath(exportsField, subpath, CJS_REQUIRE_CONDITIONS);\n if (typeof requireTarget === 'string' && requireTarget.indexOf('./') === 0) {\n // The package DOES publish a CommonJS entry for this subpath; the CJS\n // resolver threw over the files behind it, not over the shape.\n return { outcome: 'install-broken' };\n }\n\n return { outcome: 'no-loadable-entry', packageDir };\n}\n\nfunction noLoadableEntryMessage(\n declaration: HostDeclaration,\n packageDir: string,\n subpath: string,\n cause: unknown,\n): string {\n const { packageName, hostRoot, field, specifier } = declaration;\n const detail = cause instanceof Error ? cause.message : String(cause);\n const subpathNote = subpath === '.' ? 'its main entry (\".\")' : `the subpath '${subpath}'`;\n return (\n `Cannot load module '${packageName}': the host app DECLARES it ` +\n `(${field}: ${JSON.stringify(specifier)}) and it IS installed, but the package ` +\n 'publishes no entry that Node can load.\\n' +\n ` host app: ${hostRoot}\\n` +\n ` installed at: ${packageDir}\\n` +\n \"\\n This is a problem with the PACKAGE's own published shape, not with the app or\\n\" +\n ' its install — the declaration is right and the package is on disk, so neither\\n' +\n ' re-reading package.json nor re-running `pnpm install` can change anything.\\n' +\n ' Measured from its manifest:\\n' +\n ` • its \"exports\" map names no \\`require\\`-condition entry for ${subpathNote},\\n` +\n ' so a CommonJS resolution cannot see it at all\\n' +\n ' • and no `import`-condition entry either, so there is nothing for the ESM\\n' +\n ' fallback to load\\n' +\n ' The remedy lives in the package: it must publish a runtime entry for this\\n' +\n ' subpath (an `import` condition suffices here; a dual build adds `require`).\\n' +\n ' A publish carrying only `types` / `browser`-style conditions cannot be loaded\\n' +\n ' by a Node host at all.\\n' +\n ` (resolver: ${detail})`\n );\n}\n\n/**\n * Build an importer that loads a package **as the host app declares it**, and\n * otherwise falls back to the importing package's own resolution.\n *\n * Order of operations, and why (#4719):\n *\n * 1. The host `package.json` is READ. Only a declared name is looked up in the\n * host's `node_modules`. An undeclared name never reaches the host resolver,\n * so no amount of `NODE_PATH` / hoisting can make it appear to be the app's.\n * 2. Declared but unresolvable is reported AS SUCH — the app asked for it and\n * the install is broken. It is not retried bare: falling back there would\n * reintroduce exactly the \"some other package happens to supply it\" accident\n * this gate closes, and would report an install problem as an absence.\n * 3. Undeclared falls back to the CALLER's own resolution, which is what keeps\n * every framework-owned load working (`serve`'s plugin-auth / service-i18n\n * path, `bootStack`'s service plugins). Bare `import()` is ESM, and ESM does\n * not honour `NODE_PATH`, so the fallback cannot re-open the hole either.\n * Only when that fails as module-not-found does the undeclared error\n * surface; a package that RESOLVES and then throws while evaluating is a\n * genuine crash and propagates untouched, as before.\n *\n * ── The caller supplies that base, and why it is a FUNCTION (#10943) ─────────\n *\n * Step 3 said \"the importing package's own resolution\" long before anything\n * made it true. The fallback was a bare `import()` written HERE, and ESM\n * resolves a bare specifier against the module containing the call — so it\n * resolved from `@objectstack/types`, which under a pnpm-isolated layout can\n * see only `@objectstack/types`'s own dependencies. Measured on `main` from an\n * app declaring nothing, `@objectstack/plugin-auth`, `@objectstack/plugin-audit`\n * and `chalk` all resolve from `packages/cli` and all failed through this\n * helper; `@objectstack/spec` — the one dependency this package declares — was\n * the only name that came back OK, which is the whole pattern. Under a hoisted\n * npm/yarn layout the same fallback usually DOES find the caller's\n * dependencies, so the claim was green in some installs and absent in others:\n * the layout-dependence class cloud#1013 and #10645 exist to close, one level\n * up. A declared contract the implementation does not keep is the thing this\n * repo fixes at the producer (Prime Directive #12), so the mechanism moved\n * rather than the sentence.\n *\n * The base arrives as the caller's own `import()` and NOT as a `parentURL` /\n * `import.meta.url` string. Both string spellings were measured on Node\n * v22.22.2 and both are wrong:\n *\n * - `import.meta.resolve(specifier, parentURL)` — the parent argument is\n * SILENTLY IGNORED without `--experimental-import-meta-resolve`. Measured:\n * resolving `@objectstack/plugin-auth` against a `packages/types` parent\n * returned `packages/cli/node_modules/...`, i.e. the caller's own answer,\n * byte-identical to passing no parent at all. It would have compiled, run,\n * and pinned green while ignoring the base — a phantom fix of exactly the\n * kind this card is about.\n * - `createRequire(parentURL).resolve(specifier)` — CJS resolution, which\n * honours `NODE_PATH`. Measured against a store reachable only through\n * `NODE_PATH`: the CJS resolve found it (with and without the `paths`\n * option, since GLOBAL_FOLDERS are always appended) while the ESM bare\n * `import()` did not. That is #4719's hole re-opened on the fallback path,\n * and it would have falsified the \"ESM does not honour NODE_PATH\" sentence\n * three lines above.\n *\n * A function written in the calling module is the only spelling that uses\n * Node's real ESM resolver anchored where the caller actually lives: no flag,\n * no `NODE_PATH`, no second resolution algorithm to drift from the first.\n *\n * @param hostRoot Directory holding the host app's `package.json` (default: the\n * process CWD, which is where the CLI reads `objectstack.config.ts` from too).\n * Note this used to take a pre-built `NodeRequire`; it needs the ROOT now,\n * because a `NodeRequire` cannot be asked where it was anchored and the manifest\n * has to be read from there.\n * @param options {@link HostImporterOptions.fallbackImport} carries the caller's\n * resolution base. Omitting it keeps the pre-#10943 behaviour (this package's\n * own resolution) so no out-of-tree caller changes under its feet.\n */\nexport function createHostImporter(\n hostRoot: string = process.cwd(),\n options: HostImporterOptions = {},\n): HostImporter {\n const hostRequire = createHostRequire(hostRoot);\n const { fallbackImport } = options;\n const importAsCaller: FallbackImport =\n fallbackImport ?? ((specifier) => import(/* webpackIgnore: true */ specifier));\n return async (pkg: string): Promise<any> => {\n // Not a bare package name (a path, a URL, a `node:` builtin) — nothing a\n // manifest could declare. Hand it to the normal resolver untouched.\n //\n // ⚠️ Deliberately NOT re-based onto `fallbackImport` (#10943). Every\n // base-INDEPENDENT spelling here — `file://`, `node:`, `data:`, an absolute\n // path — means the same module whoever imports it, so the base is not a\n // question they can even ask. The one spelling it WOULD move is a RELATIVE\n // one, and where that should resolve from is an open policy question owned\n // by #10944 (`serve` refuses a relative `plugins: [...]` entry rather than\n // silently re-basing it) — with a measured consumer count of zero here:\n // `serve` handles non-package specifiers before this helper is reached, and\n // `bootStack` / the dogfood probe pass package names only. Answering half\n // of another card's undecided question, for nobody, is not a repair.\n if (packageNameFromSpecifier(pkg) === undefined) {\n return import(/* webpackIgnore: true */ pkg);\n }\n\n const declaration = readHostDeclaration(pkg, hostRoot);\n\n if (declaration.declared) {\n let resolved: string;\n try {\n resolved = hostRequire.resolve(pkg);\n } catch (cause) {\n // #14041: the CJS resolver cannot see an ESM-only publish at all. Try\n // the strictly-tighter hostRoot node_modules finder before concluding\n // anything — this catch was a hard failure before, so the fallback is\n // strictly additive — and when it cannot help either, report the kind\n // the walk actually measured (see the #14041 section note).\n const fallback = declaredCjsResolveFallback(pkg, declaration);\n if (fallback.outcome === 'entry') {\n return import(pathToFileURL(fallback.entry).href);\n }\n if (fallback.outcome === 'no-loadable-entry') {\n throw hostImportError(\n 'declared-no-loadable-entry',\n noLoadableEntryMessage(\n declaration,\n fallback.packageDir,\n exportsSubpathOf(pkg, declaration.packageName),\n cause,\n ),\n cause,\n );\n }\n throw hostImportError(\n 'declared-unresolvable',\n unresolvableMessage(declaration, cause),\n cause,\n );\n }\n // #13330: re-decide the CONDITION, never the host anchor. `resolved`\n // stays the authority on WHERE the package is; this asks that package\n // which entry an `import()` gets, so the caller's ESM chain and this\n // load share one instance of everything the package brings with it.\n const entry = esmEntryForDeclared(pkg, declaration.packageName, resolved) ?? resolved;\n return import(pathToFileURL(entry).href);\n }\n\n try {\n return await importAsCaller(pkg);\n } catch (cause) {\n // A package that resolved and then exploded is a crash, not an absence.\n if (!isModuleNotFoundError(cause)) throw cause;\n throw hostImportError(\n 'undeclared',\n undeclaredMessage(declaration, cause, fallbackImport !== undefined),\n cause,\n );\n }\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * True when a dynamic `import()` / `require.resolve()` failed because the\n * module is simply NOT INSTALLED — as opposed to the module being present but\n * throwing while it loads (a real crash). Checking `err.code` FIRST matters:\n * ESM reports a missing package as `err.code === 'ERR_MODULE_NOT_FOUND'` with\n * the human message `Cannot find package '...'`; matching only the older\n * `Cannot find module` string mis-classifies that as a crash (framework#1595).\n *\n * Single shared owner for this classification (framework#3265): the CLI's\n * optional-plugin guards and `requires` capability resolver delegate here, and\n * cloud's `objectos-runtime` capability loader is expected to adopt it at its\n * next framework pin bump — so the parallel loaders cannot drift apart and\n * re-introduce the #1595 false-alarm class.\n */\nexport function isModuleNotFoundError(err: unknown): boolean {\n const code = (err as { code?: string } | null | undefined)?.code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') return true;\n const msg = err instanceof Error ? err.message : String(err);\n return msg.includes('Cannot find module') || msg.includes('Cannot find package');\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/types",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.3.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Shared interfaces describing the ObjectStack Runtime environment",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@objectstack/spec": "17.
|
|
21
|
+
"@objectstack/spec": "17.3.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"typescript": "^6.0.3",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"node": ">=22.0.0"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
|
-
"build": "tsup",
|
|
53
|
+
"build": "tsup && node ../../scripts/check-dts-emitted.mjs",
|
|
54
54
|
"typecheck": "tsc --noEmit",
|
|
55
55
|
"test": "vitest run"
|
|
56
56
|
}
|