@_linked/server 2.16.2 → 2.17.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @\_linked/server
2
2
 
3
+ ## 2.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#126](https://github.com/linked-fw/server/pull/126) [`93ebd50`](https://github.com/linked-fw/server/commit/93ebd5002d76ad9208cac63c09ad92e5f4f74c0a) Thanks [@flyon](https://github.com/flyon)! - Load each linked package's backend from the directory it is installed in, not by its bare name.
8
+
9
+ The backend entry used to be loaded with `import('<pkg>/backend')`, which Node resolves from @_linked/server's own location. A linked package installed only nested inside another package's `node_modules` (npm does this when the root cannot share one version, e.g. `@_linked/owl/node_modules/@_linked/rdfs`) could therefore never be loaded: boot logged a red "Cannot find package" error for packages without a backend, and a nested package WITH a backend never had its providers constructed, its request hooks never ran, and `/call` to it answered 501.
10
+
11
+ Now the server keeps the path discovery found for every linked package, resolves the `./backend` entry from that package's own `exports` (new `resolveBackendEntry`: exact key, `./*` patterns, `import`/`node`/`default` conditions; legacy layout without `exports`) and imports it by file URL. Whether a package has a backend is decided before anything is imported: no entry is quiet, and a backend file that fails to load is a loud error naming the package and the file, without stopping other packages. The error-text matching that used to tell the two apart is gone.
12
+
13
+ When a linked package is installed more than once, only the copy the app itself resolves is loaded, and the server warns once with every copy's version and path and a hint to run `npm dedupe`.
14
+
3
15
  ## 2.16.2
4
16
 
5
17
  ### Patch Changes
@@ -39,6 +39,16 @@ export declare class LinkedServer extends Shape {
39
39
  private shapeProviders;
40
40
  private genericProviders;
41
41
  private providerIndexing;
42
+ /**
43
+ * Package name → realpath of the installed copy whose backend is loaded,
44
+ * as discovery found it. A package's backend is resolved from THIS
45
+ * directory, never by its bare name from @_linked/server's own location:
46
+ * Node would never look inside another package's nested node_modules, so a
47
+ * linked package installed only there (owl/node_modules/@_linked/rdfs)
48
+ * could not be loaded at all.
49
+ */
50
+ private linkedPackageDirs;
51
+ private warnedDuplicateInstalls;
42
52
  private resizePathsMap;
43
53
  private api;
44
54
  /**
@@ -98,9 +108,22 @@ export declare class LinkedServer extends Shape {
98
108
  * Index the backend providers of every linked package in the app's
99
109
  * dependency tree, dependencies first. Workspace members (`localPackages`)
100
110
  * are also imported whole, as before; for anything else only its `/backend`
101
- * entry is loaded, and a package without one costs a failed resolve.
111
+ * entry is loaded — resolved from the directory discovery found the package
112
+ * in, and skipped without an import when the package has none.
102
113
  */
103
114
  private indexLinkedDependencyProviders;
115
+ /**
116
+ * Two installed copies of one linked package mean two copies of its module
117
+ * state, and only one of them can own the backend. The one the app resolves
118
+ * is loaded (see discoverLinkedDependencies); the other is never loaded.
119
+ */
120
+ private warnIfInstalledMoreThanOnce;
121
+ /**
122
+ * The directory a package's backend is resolved from: the copy discovery
123
+ * chose, or — for a package outside the linked tree, reached only by a lazy
124
+ * /call — the copy the app itself resolves.
125
+ */
126
+ private packageDirFor;
104
127
  /**
105
128
  * Index a package's backend providers once. Shared by boot and the lazy
106
129
  * path in callBackendMethod / callShapeMethod. `onSourceChange` bypasses it
@@ -108,8 +131,8 @@ export declare class LinkedServer extends Shape {
108
131
  */
109
132
  ensurePackageBackendProviders(pkg: string, warnIfNotFound?: boolean): Promise<void>;
110
133
  /**
111
- * Node's `import()`, as a method so a test can resolve a fixture app's
112
- * node_modules (a test runner resolves bare specifiers from this file).
134
+ * Node's `import()`. Called with the absolute file URL of a resolved backend
135
+ * entry (see resolveBackendEntry), never a bare name.
113
136
  */
114
137
  protected importModule(specifier: string): Promise<any>;
115
138
  /**
@@ -119,7 +142,7 @@ export declare class LinkedServer extends Shape {
119
142
  private filterPackagesByDependencyTree;
120
143
  resizeImage(req: any, res: any): Promise<void>;
121
144
  indexLincdPackage(pkg: string, warnIfNotFound?: boolean): Promise<void>;
122
- indexPackageBackendProviders(pkg: string, warnIfNotFound?: boolean, backendIndexFilePath?: string): Promise<{
145
+ indexPackageBackendProviders(pkg: string, warnIfNotFound?: boolean): Promise<{
123
146
  backendProviderExports: any;
124
147
  shapeProviders: any[];
125
148
  }>;
@@ -37,7 +37,7 @@ import { autoLoadOntologyData } from '@_linked/core/utils/Package';
37
37
  import { getShapeClass, getSuperShapesClasses, } from '@_linked/core/utils/ShapeClass';
38
38
  import path from 'path';
39
39
  import { pathToFileURL } from 'node:url';
40
- import { discoverLinkedDependencies, isBundledBySsr, ssrNoExternalOf, } from '../utils/linkedDependencies.js';
40
+ import { discoverLinkedDependencies, isBundledBySsr, NODE_IMPORT_CONDITIONS, readInstalledPackage, resolveBackendEntry, ssrNoExternalOf, } from '../utils/linkedDependencies.js';
41
41
  /**
42
42
  * `instanceof ShapeProvider`, tolerating a second copy of server-utils.
43
43
  *
@@ -166,6 +166,16 @@ let LinkedServer = class LinkedServer extends Shape {
166
166
  // concurrent first calls (or a call racing boot) never construct a
167
167
  // package's providers twice.
168
168
  this.providerIndexing = new Map();
169
+ /**
170
+ * Package name → realpath of the installed copy whose backend is loaded,
171
+ * as discovery found it. A package's backend is resolved from THIS
172
+ * directory, never by its bare name from @_linked/server's own location:
173
+ * Node would never look inside another package's nested node_modules, so a
174
+ * linked package installed only there (owl/node_modules/@_linked/rdfs)
175
+ * could not be loaded at all.
176
+ */
177
+ this.linkedPackageDirs = new Map();
178
+ this.warnedDuplicateInstalls = new Set();
169
179
  //from resizedFileName to full resized path (CDN or similar)
170
180
  this.resizePathsMap = new Map();
171
181
  if (config && typeof config !== 'string' && !('id' in config)) {
@@ -624,16 +634,32 @@ let LinkedServer = class LinkedServer extends Shape {
624
634
  * Index the backend providers of every linked package in the app's
625
635
  * dependency tree, dependencies first. Workspace members (`localPackages`)
626
636
  * are also imported whole, as before; for anything else only its `/backend`
627
- * entry is loaded, and a package without one costs a failed resolve.
637
+ * entry is loaded — resolved from the directory discovery found the package
638
+ * in, and skipped without an import when the package has none.
628
639
  */
629
640
  async indexLinkedDependencyProviders(localPackages) {
630
- var _a;
641
+ var _a, _b;
631
642
  const started = Date.now();
632
643
  const installed = [];
633
644
  const ordered = discoverLinkedDependencies(process.cwd(), this.package);
634
645
  // A workspace member the walk cannot reach through node_modules still
635
646
  // loads, after the rest, as it always did.
636
647
  const remaining = new Set(localPackages.keys());
648
+ (_a = this.linkedPackageDirs) !== null && _a !== void 0 ? _a : (this.linkedPackageDirs = new Map());
649
+ for (const dep of ordered) {
650
+ this.linkedPackageDirs.set(dep.packageName, dep.path);
651
+ this.warnIfInstalledMoreThanOnce(dep);
652
+ }
653
+ for (const [packageName, local] of localPackages) {
654
+ if (!this.linkedPackageDirs.has(packageName) && (local === null || local === void 0 ? void 0 : local.path)) {
655
+ let dir = local.path;
656
+ try {
657
+ dir = fsNative.realpathSync(dir);
658
+ }
659
+ catch (_c) { }
660
+ this.linkedPackageDirs.set(packageName, dir);
661
+ }
662
+ }
637
663
  for (const { packageName } of ordered) {
638
664
  const isLocal = remaining.delete(packageName);
639
665
  await this.ensurePackageBackendProviders(packageName);
@@ -641,7 +667,7 @@ let LinkedServer = class LinkedServer extends Shape {
641
667
  await this.indexLincdPackage(packageName);
642
668
  }
643
669
  else if (this.genericProviders.get(packageName) ||
644
- ((_a = this.shapeProviders.get(packageName)) === null || _a === void 0 ? void 0 : _a.length)) {
670
+ ((_b = this.shapeProviders.get(packageName)) === null || _b === void 0 ? void 0 : _b.length)) {
645
671
  installed.push(packageName);
646
672
  }
647
673
  }
@@ -653,6 +679,44 @@ let LinkedServer = class LinkedServer extends Shape {
653
679
  console.log(paint('gray', `[linked] indexed backend providers of ${ordered.length} linked package(s) in ${Date.now() - started}ms; installed ones with providers: ${installed.join(', ')}`));
654
680
  }
655
681
  }
682
+ /**
683
+ * Two installed copies of one linked package mean two copies of its module
684
+ * state, and only one of them can own the backend. The one the app resolves
685
+ * is loaded (see discoverLinkedDependencies); the other is never loaded.
686
+ */
687
+ warnIfInstalledMoreThanOnce(dep) {
688
+ var _a, _b;
689
+ const copies = (_a = dep.copies) !== null && _a !== void 0 ? _a : [];
690
+ (_b = this.warnedDuplicateInstalls) !== null && _b !== void 0 ? _b : (this.warnedDuplicateInstalls = new Set());
691
+ if (copies.length < 2 || this.warnedDuplicateInstalls.has(dep.packageName)) {
692
+ return;
693
+ }
694
+ this.warnedDuplicateInstalls.add(dep.packageName);
695
+ const list = copies
696
+ .map((c) => { var _a; return `${(_a = c.version) !== null && _a !== void 0 ? _a : '?'} at ${c.path}`; })
697
+ .join(', ');
698
+ console.warn(paint('yellow', `[linked] ${dep.packageName} is installed ${copies.length} times (${list}); loaded ${dep.path}. Run npm dedupe.`));
699
+ }
700
+ /**
701
+ * The directory a package's backend is resolved from: the copy discovery
702
+ * chose, or — for a package outside the linked tree, reached only by a lazy
703
+ * /call — the copy the app itself resolves.
704
+ */
705
+ packageDirFor(pkg) {
706
+ var _a;
707
+ const known = (_a = this.linkedPackageDirs) === null || _a === void 0 ? void 0 : _a.get(pkg);
708
+ if (known)
709
+ return known;
710
+ const installed = readInstalledPackage(pkg, process.cwd());
711
+ if (!installed)
712
+ return null;
713
+ try {
714
+ return fsNative.realpathSync(installed.root);
715
+ }
716
+ catch (_b) {
717
+ return installed.root;
718
+ }
719
+ }
656
720
  /**
657
721
  * Index a package's backend providers once. Shared by boot and the lazy
658
722
  * path in callBackendMethod / callShapeMethod. `onSourceChange` bypasses it
@@ -675,8 +739,8 @@ let LinkedServer = class LinkedServer extends Shape {
675
739
  }
676
740
  }
677
741
  /**
678
- * Node's `import()`, as a method so a test can resolve a fixture app's
679
- * node_modules (a test runner resolves bare specifiers from this file).
742
+ * Node's `import()`. Called with the absolute file URL of a resolved backend
743
+ * entry (see resolveBackendEntry), never a bare name.
680
744
  */
681
745
  importModule(specifier) {
682
746
  return import(/* @vite-ignore */ specifier);
@@ -1008,19 +1072,11 @@ let LinkedServer = class LinkedServer extends Shape {
1008
1072
  }
1009
1073
  }
1010
1074
  }
1011
- async indexPackageBackendProviders(pkg, warnIfNotFound = false, backendIndexFilePath) {
1012
- var _a, _b;
1013
- if (!backendIndexFilePath) {
1014
- backendIndexFilePath = `${pkg}/backend`;
1015
- }
1075
+ async indexPackageBackendProviders(pkg, warnIfNotFound = false) {
1076
+ var _a, _b, _c, _d;
1016
1077
  let backendProviderExports;
1017
- let genericBackendProvider;
1078
+ let genericBackendProvider = null;
1018
1079
  let shapeProviders = [];
1019
- // Plan-010 phase 4/5: when Vite SSR is active, route module loading
1020
- // through vite.ssrLoadModule. This handles the case where the user app
1021
- // is the consuming workspace itself (self-reference like
1022
- // @semantu/create-now/backend), which Node's ESM resolver can't resolve
1023
- // from inside an installed package like @_linked/server.
1024
1080
  const vite = (_a = this.config.server) === null || _a === void 0 ? void 0 : _a.vite;
1025
1081
  // In dev, load a package's backend with the loader that owns the package
1026
1082
  // (see isBundledBySsr): Vite for the app itself and for what the SSR
@@ -1032,126 +1088,114 @@ let LinkedServer = class LinkedServer extends Shape {
1032
1088
  typeof vite.ssrLoadModule === 'function' &&
1033
1089
  (pkg === ((_b = this.package) === null || _b === void 0 ? void 0 : _b.name) ||
1034
1090
  isBundledBySsr(pkg, ssrNoExternalOf(vite)));
1035
- const loadModule = async (specifier) => {
1036
- var _a, _b, _c;
1091
+ // Decide whether there IS a backend before importing anything. A package
1092
+ // without one is the normal case (most linked packages have none) and is
1093
+ // quiet; once a backend file is known to exist, any failure to load it is
1094
+ // a real error. This used to be decided after the fact, by matching the
1095
+ // loader's error text — which Node and Vite word differently, so a nested
1096
+ // package's "Cannot find package" was reported as a broken backend.
1097
+ let entry = null;
1098
+ let load = null;
1099
+ if (pkg === ((_c = this.package) === null || _c === void 0 ? void 0 : _c.name)) {
1100
+ // The app's own backend cannot be imported by name: a self-reference
1101
+ // resolves only from inside the package that declares it, and this code
1102
+ // lives in node_modules/@_linked/server. Load it by path.
1103
+ const cwd = process.cwd();
1037
1104
  if (viteOwnsPackage) {
1038
- // For the user app's own backend, resolve to ./src/backend.ts directly.
1039
- // Vite reads the app's package.json exports.development condition.
1040
- if (specifier === `${this.package.name}/backend`) {
1041
- return await vite.ssrLoadModule('/src/backend.ts');
1105
+ const source = path.join(cwd, 'src', 'backend.ts');
1106
+ if (fsNative.existsSync(source)) {
1107
+ entry = source;
1108
+ load = () => vite.ssrLoadModule('/src/backend.ts');
1042
1109
  }
1043
- // Plan-011: ssr.external is now an npm-only allowlist. Workspace
1044
- // packages MUST go through Vite's SSR loader so they share the
1045
- // same module instance as everything else in the SSR call graph
1046
- // (otherwise we get duplicate React contexts, duplicate Linked
1047
- // package registrations, etc).
1048
- //
1049
- // Pre-check whether the specifier actually resolves before
1050
- // calling ssrLoadModule — many packages have no `/backend`
1051
- // export, and ssrLoadModule logs a "Failed to load url" error
1052
- // INTERNALLY before throwing, which spams stdout even when the
1053
- // surrounding catch handles it. pluginContainer.resolveId is
1054
- // quiet: it returns null when no plugin can resolve the id.
1110
+ }
1111
+ else {
1112
+ entry = (_d = [
1113
+ path.join(cwd, 'lib', 'backend.js'),
1114
+ path.join(cwd, 'src', 'backend.ts'),
1115
+ ].find((f) => fsNative.existsSync(f))) !== null && _d !== void 0 ? _d : null;
1116
+ if (entry) {
1117
+ const url = pathToFileURL(entry).href;
1118
+ load = () => this.importModule(url);
1119
+ }
1120
+ }
1121
+ }
1122
+ else {
1123
+ const dir = this.packageDirFor(pkg);
1124
+ let json = null;
1125
+ if (dir) {
1055
1126
  try {
1056
- const resolved = await ((_b = (_a = vite.pluginContainer) === null || _a === void 0 ? void 0 : _a.resolveId) === null || _b === void 0 ? void 0 : _b.call(_a, specifier));
1057
- if (!resolved) {
1058
- // Synthesise the same error shape the catch expects so the
1059
- // "no backend export, that's fine" branch fires.
1060
- const err = new Error(`Failed to load url ${specifier} (resolved id: ${specifier}). Does the file exist?`);
1061
- throw err;
1062
- }
1127
+ json = JSON.parse(fsNative.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
1063
1128
  }
1064
- catch (e) {
1065
- // If the pre-check itself errored (e.g. plugin threw on a
1066
- // pkg name it doesn't know), let the call below surface the
1067
- // real error path.
1068
- if (!((_c = e === null || e === void 0 ? void 0 : e.message) === null || _c === void 0 ? void 0 : _c.includes('Failed to load url'))) {
1069
- // fall through to ssrLoadModule which will throw + log
1070
- }
1071
- else {
1072
- throw e;
1129
+ catch (_e) { }
1130
+ }
1131
+ if (dir && json) {
1132
+ if (viteOwnsPackage) {
1133
+ // Vite resolves a bundled package with the `development` condition
1134
+ // (a workspace's `./backend` may point at src only there). It is
1135
+ // still loaded by its bare name, so it is the same module id the
1136
+ // rest of the SSR graph imports.
1137
+ entry = resolveBackendEntry(dir, json, [
1138
+ 'development',
1139
+ ...NODE_IMPORT_CONDITIONS,
1140
+ ]);
1141
+ if (entry)
1142
+ load = () => vite.ssrLoadModule(`${pkg}/backend`);
1143
+ }
1144
+ else {
1145
+ // Node caches ESM by resolved real file URL, and a bare import of
1146
+ // `<pkg>/backend` from the app resolves to this same real path — so
1147
+ // loading it by URL yields the instance the app's own imports get.
1148
+ entry = resolveBackendEntry(dir, json, NODE_IMPORT_CONDITIONS);
1149
+ if (entry) {
1150
+ const url = pathToFileURL(entry).href;
1151
+ load = () => this.importModule(url);
1073
1152
  }
1074
1153
  }
1075
- return await vite.ssrLoadModule(specifier);
1076
1154
  }
1077
- // Node path: production runtime, or a package the dev SSR runner
1078
- // leaves external.
1079
- //
1080
- // The app's own backend cannot be imported by name here. A self-reference
1081
- // resolves only from inside the package that declares it, and this code
1082
- // lives in node_modules/@_linked/server — so Node reports
1083
- // `Cannot find package '<app>'` and every one of the app's Providers
1084
- // silently fails to register. The Vite branch above already works around
1085
- // this; the compiled runtime needs the same treatment, by path.
1086
- if (specifier === `${this.package.name}/backend`) {
1087
- const compiled = path.join(process.cwd(), 'lib', 'backend.js');
1088
- const source = path.join(process.cwd(), 'src', 'backend.ts');
1089
- const target = fsNative.existsSync(compiled) ? compiled : source;
1090
- return await import(/* @vite-ignore */ pathToFileURL(target).href);
1155
+ }
1156
+ if (!load) {
1157
+ if (warnIfNotFound) {
1158
+ console.warn(paint('magenta', `Could not find backend file of package ${pkg}.
1159
+ Check:\n
1160
+ - Make sure backend.ts exists and is included in tsconfig.json\n
1161
+ - Make sure the package name in src/package.ts matches the package name in package.json`));
1091
1162
  }
1092
- // Fall back to Node's resolver. The dynamic specifier is intentional.
1093
- return await this.importModule(specifier);
1094
- };
1095
- await loadModule(backendIndexFilePath)
1096
- .then((backendProviderExports) => {
1097
- //instantiate the exported provider classes and add them to the right place
1098
- Object.keys(backendProviderExports).forEach((key) => {
1099
- let providerClass = backendProviderExports[key];
1100
- //always send an instance of the express server
1101
- //TODO: do not create an instance, just save the class and instantiate it when needed
1102
- let provider = new providerClass(this.server, this);
1103
- if (isShapeProvider(provider)) {
1104
- shapeProviders.push(provider);
1105
- if (!Object.getOwnPropertyNames(provider).includes('shape')) {
1106
- console.warn(paint('red', `${Object.getPrototypeOf(provider).constructor.name} in package ${pkg}
1163
+ }
1164
+ else {
1165
+ // Loud, naming the package and the file, and contained: one package's
1166
+ // broken backend does not stop the others from loading.
1167
+ let loaded = false;
1168
+ await runProviderHook(pkg, `backend ${entry}`, async () => {
1169
+ backendProviderExports = await load();
1170
+ loaded = true;
1171
+ });
1172
+ if (loaded) {
1173
+ for (const key of Object.keys(backendProviderExports)) {
1174
+ const providerClass = backendProviderExports[key];
1175
+ let provider;
1176
+ //always send an instance of the express server
1177
+ //TODO: do not create an instance, just save the class and instantiate it when needed
1178
+ await runProviderHook(pkg, `${key} constructor`, () => {
1179
+ provider = new providerClass(this.server, this);
1180
+ });
1181
+ if (!provider)
1182
+ continue;
1183
+ if (isShapeProvider(provider)) {
1184
+ shapeProviders.push(provider);
1185
+ if (!Object.getOwnPropertyNames(provider).includes('shape')) {
1186
+ console.warn(paint('red', `${Object.getPrototypeOf(provider).constructor.name} in package ${pkg}
1107
1187
  is not properly linked to a shape. Use public shape = SomeShape.`));
1188
+ }
1108
1189
  }
1109
- }
1110
- else {
1111
- if (genericBackendProvider) {
1190
+ else if (genericBackendProvider) {
1112
1191
  console.warn(`Package ${pkg} exports two generic backend providers. Only one will work`);
1113
1192
  }
1114
1193
  else {
1115
1194
  genericBackendProvider = provider;
1116
1195
  }
1117
1196
  }
1118
- });
1119
- })
1120
- .catch((e) => {
1121
- var _a;
1122
- // Recognize "no /backend export" failures across two loader
1123
- // shapes:
1124
- // Node's import() → ERR_MODULE_NOT_FOUND + "Cannot find module 'X/backend'"
1125
- // Vite's ssrLoadModule() → "Failed to load url X/backend"
1126
- // package `exports` without a ./backend entry (Node + Vite)
1127
- // → 'Missing "./backend" specifier in "X" package'
1128
- // In either case, missing /backend on a package that doesn't
1129
- // ship a backend is expected; loud-error only on REAL load
1130
- // failures (syntax error inside an existing backend.ts, etc).
1131
- const nodeMatch = e.message.match(/module \'([^\']+)'/);
1132
- const viteMatch = e.message.match(/Failed to load url ([^\s]+)/);
1133
- const matchedSpec = (_a = nodeMatch === null || nodeMatch === void 0 ? void 0 : nodeMatch[1]) !== null && _a !== void 0 ? _a : viteMatch === null || viteMatch === void 0 ? void 0 : viteMatch[1];
1134
- const notExported = /Missing "\.\/backend" specifier in "[^"]+" package/.test(e.message);
1135
- let providerNotFound = notExported ||
1136
- (!!matchedSpec &&
1137
- matchedSpec.includes('/backend') &&
1138
- ((e.code === 'ERR_MODULE_NOT_FOUND' &&
1139
- e.message.indexOf(`Cannot find module`) !== -1) ||
1140
- e.message.indexOf('Failed to load url') !== -1));
1141
- if (providerNotFound) {
1142
- // console.warn('Error loading ' + providerPath + ': ' + e.stack);
1143
- if (warnIfNotFound) {
1144
- console.warn(paint('magenta', `Could not find backend file of package ${pkg}.
1145
- Check:\n
1146
- - Make sure backend.ts exists and is included in tsconfig.json\n
1147
- - Make sure the package name in src/package.ts matches the package name in package.json`));
1148
- }
1149
1197
  }
1150
- else {
1151
- console.warn(paint('red', `Could not load backend file of module '${pkg}' from ${process.cwd()}:\n`), e.stack);
1152
- }
1153
- genericBackendProvider = null;
1154
- });
1198
+ }
1155
1199
  this.genericProviders.set(pkg, genericBackendProvider);
1156
1200
  this.shapeProviders.set(pkg, shapeProviders);
1157
1201
  return { backendProviderExports, shapeProviders };