@_linked/server 2.14.3 → 2.16.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 +36 -0
- package/lib/esm/shapes/LinkedServer.d.ts +37 -6
- package/lib/esm/shapes/LinkedServer.js +175 -59
- package/lib/esm/shapes/LinkedServer.js.map +1 -1
- package/lib/esm/utils/linkedDependencies.d.ts +66 -0
- package/lib/esm/utils/linkedDependencies.js +123 -0
- package/lib/esm/utils/linkedDependencies.js.map +1 -0
- package/lib/esm/utils/syncShapes.d.ts +42 -0
- package/lib/esm/utils/syncShapes.js +46 -0
- package/lib/esm/utils/syncShapes.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @\_linked/server
|
|
2
2
|
|
|
3
|
+
## 2.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#122](https://github.com/linked-fw/server/pull/122) [`b6c2c9a`](https://github.com/linked-fw/server/commit/b6c2c9ac689c614966f97f39cf18c0bab7683e16) Thanks [@flyon](https://github.com/flyon)! - Load the backend providers of installed linked packages at boot, not only those of local workspace packages.
|
|
8
|
+
|
|
9
|
+
Every package in the app's dependency tree whose package.json declares `"linkedPackage": true` — registry install, localized checkout or workspace member — now has its `/backend` providers indexed at startup, each after the linked packages it depends on. Before, a registry-installed package's provider was indexed only on the first `/call/<pkg>/...`, so its `initRequest` / `supplyDataForRequest` hooks were silent until then and its `setupBeforeControllers` / `setupAfterControllers` never ran at all. With `@_linked/auth` installed from the registry, that meant a signed-in SSR page after a restart shipped an empty request-json and failed to hydrate.
|
|
10
|
+
|
|
11
|
+
The `"linkedPackage": true` flag is the only test, whatever the package's scope: an `@_linked/*` package without it (such as the `@_linked/localize` CLI tool) is not loaded, and a package in any other scope with it is. Legacy `"lincd": true` packages are still not loaded at boot.
|
|
12
|
+
|
|
13
|
+
Lazy indexing on `/call` stays as a fallback and shares one in-flight indexing per package with boot, so a provider is never constructed twice.
|
|
14
|
+
|
|
15
|
+
A provider whose hook throws no longer takes the server down with it. Boot lifecycle hooks (`setupBeforeControllers`, `setupBeforeCatchAllControllers`, `setupAfterControllers`) and per-request hooks (`initRequest`, `supplyDataForRequest`) now run per provider: an error is logged as `[linked] <package> <hook> failed: …` and the remaining providers still run. Before, a throwing boot hook aborted startup, and a synchronous throw in `supplyDataForRequest` failed every page request without naming the provider. This applies to local workspace providers as well as installed ones.
|
|
16
|
+
|
|
17
|
+
In dev, a package's backend is now loaded by the loader that owns the package: `vite.ssrLoadModule` for what the SSR runner bundles (`ssr.noExternal` — workspaces and `SSR_ENTRY_PACKAGES`), Node's `import()` for what it leaves external. Loading an external package's `/backend` through `ssrLoadModule` evaluated that package a second time, apart from the copy the app's own imports use.
|
|
18
|
+
|
|
19
|
+
Apps should be on `@_linked/sentry` 1.1.3 or later: before that, its `setupBeforeControllers` called `require` in ESM and threw `ReferenceError: require is not defined` whenever `SENTRY_DSN` and `SITE_ROOT` are set outside development. Now that that hook runs at boot, an older version logs the error instead of aborting startup, but Sentry stays inactive.
|
|
20
|
+
|
|
21
|
+
## 2.15.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- [#117](https://github.com/linked-fw/server/pull/117) [`05d29f1`](https://github.com/linked-fw/server/commit/05d29f11fa634436de4c5c8f9940790a90e3a222) Thanks [@flyon](https://github.com/flyon)! - Boot-time shape sync no longer deletes store shapes the app did not register.
|
|
26
|
+
|
|
27
|
+
`LinkedServer` materializes the app's registered shapes into its app-data on every boot. It called
|
|
28
|
+
core `syncShapes(appData)` with no options, so on the cores where `orphanScope` defaults to `'all'`
|
|
29
|
+
it also deleted every NodeShape in the dataset that the process had not registered. An app whose
|
|
30
|
+
backend imports no shapes — the Create Now app template — therefore deleted every NodeShape in its
|
|
31
|
+
app-data on boot, including shapes Create Now put there with `bindShape` / `enableCapability`; one
|
|
32
|
+
that deep-imported a single shape file deleted the rest of that package.
|
|
33
|
+
|
|
34
|
+
Boot sync now passes `{orphanScope: 'none'}` explicitly, so it is additive whatever core version the
|
|
35
|
+
app resolves. An operator who wants the old pruning can set `LINKED_SYNC_SHAPES_PRUNE_ORPHANS=true`
|
|
36
|
+
(that exact value; anything else leaves pruning off). Each boot logs one line naming the mode that
|
|
37
|
+
ran. The helpers are exported from `@_linked/server/utils/syncShapes`.
|
|
38
|
+
|
|
3
39
|
## 2.14.3
|
|
4
40
|
|
|
5
41
|
### Patch Changes
|
|
@@ -38,6 +38,7 @@ export declare class LinkedServer extends Shape {
|
|
|
38
38
|
private analyse;
|
|
39
39
|
private shapeProviders;
|
|
40
40
|
private genericProviders;
|
|
41
|
+
private providerIndexing;
|
|
41
42
|
private resizePathsMap;
|
|
42
43
|
private api;
|
|
43
44
|
/**
|
|
@@ -54,15 +55,18 @@ export declare class LinkedServer extends Shape {
|
|
|
54
55
|
* Materialize this server's registered shapes into the RDF store as SHACL
|
|
55
56
|
* (core `syncShapes` → pure `sh:NodeShape` + property shapes), so a running
|
|
56
57
|
* app's app-data holds the shapes its instances validate/query against
|
|
57
|
-
* (plan-010 T1e.2).
|
|
58
|
-
*
|
|
59
|
-
*
|
|
58
|
+
* (plan-010 T1e.2). Whatever shapes the app package registers (published
|
|
59
|
+
* re-exports + generated) get written on boot (and re-run on shape-file HMR).
|
|
60
|
+
*
|
|
61
|
+
* Additive only: store shapes this process did not register are left alone
|
|
62
|
+
* unless the operator sets `LINKED_SYNC_SHAPES_PRUNE_ORPHANS=true` — see
|
|
63
|
+
* `bootOrphanScope` in utils/syncShapes for why.
|
|
60
64
|
*
|
|
61
65
|
* Gated to servers whose DEFAULT dataset is a **concrete** materializable store
|
|
62
66
|
* (detected via `rawQuery`) — i.e. an app pointing at its app-data FusekiStore.
|
|
63
|
-
* CN's default is the context-routing `AppDataRouter` (no `rawQuery
|
|
64
|
-
*
|
|
65
|
-
*
|
|
67
|
+
* CN's default is the context-routing `AppDataRouter` (no `rawQuery`), so CN is
|
|
68
|
+
* skipped here — it materializes its own pinned native shapes in its storage
|
|
69
|
+
* config.
|
|
66
70
|
*/
|
|
67
71
|
private materializeShapesIntoStore;
|
|
68
72
|
start(): Promise<this>;
|
|
@@ -73,6 +77,15 @@ export declare class LinkedServer extends Shape {
|
|
|
73
77
|
* @todo Check if default file store is set, if not, set it
|
|
74
78
|
*/
|
|
75
79
|
initStores(): Promise<any[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Run a lifecycle hook (setupBeforeControllers, setupBeforeCatchAllControllers,
|
|
82
|
+
* setupAfterControllers) on every generic provider, in indexing order.
|
|
83
|
+
*
|
|
84
|
+
* Each provider is isolated: a hook that throws (or rejects) is logged with
|
|
85
|
+
* the package and hook name and the next provider still runs, so one broken
|
|
86
|
+
* provider does not abort boot. That provider's routes or middleware from
|
|
87
|
+
* that hook may be missing, but the rest of the app starts.
|
|
88
|
+
*/
|
|
76
89
|
callGenericBackendProvidersMethod(method: string, ...args: any[]): Promise<void>;
|
|
77
90
|
/**
|
|
78
91
|
* Removes temporary nodes from memory if they've been seen twice
|
|
@@ -81,6 +94,24 @@ export declare class LinkedServer extends Shape {
|
|
|
81
94
|
*/
|
|
82
95
|
initGarbageCollection(): void;
|
|
83
96
|
initBackendProviders(): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Index the backend providers of every linked package in the app's
|
|
99
|
+
* dependency tree, dependencies first. Workspace members (`localPackages`)
|
|
100
|
+
* 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.
|
|
102
|
+
*/
|
|
103
|
+
private indexLinkedDependencyProviders;
|
|
104
|
+
/**
|
|
105
|
+
* Index a package's backend providers once. Shared by boot and the lazy
|
|
106
|
+
* path in callBackendMethod / callShapeMethod. `onSourceChange` bypasses it
|
|
107
|
+
* on purpose: an HMR reload must re-index.
|
|
108
|
+
*/
|
|
109
|
+
ensurePackageBackendProviders(pkg: string, warnIfNotFound?: boolean): Promise<void>;
|
|
110
|
+
/**
|
|
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).
|
|
113
|
+
*/
|
|
114
|
+
protected importModule(specifier: string): Promise<any>;
|
|
84
115
|
/**
|
|
85
116
|
* Filters local workspace packages to only those reachable from the app's
|
|
86
117
|
* dependency tree. Mirrors the logic used by `buildAll` in lincd-cli.
|
|
@@ -36,6 +36,42 @@ import { autoLoadOntologyData } from '@_linked/core/utils/Package';
|
|
|
36
36
|
import { getShapeClass, getSuperShapesClasses, } from '@_linked/core/utils/ShapeClass';
|
|
37
37
|
import path from 'path';
|
|
38
38
|
import { pathToFileURL } from 'node:url';
|
|
39
|
+
import { discoverLinkedDependencies, isBundledBySsr, ssrNoExternalOf, } from '../utils/linkedDependencies.js';
|
|
40
|
+
/**
|
|
41
|
+
* `instanceof ShapeProvider`, tolerating a second copy of server-utils.
|
|
42
|
+
*
|
|
43
|
+
* A registry-installed package's backend is loaded by Node (see
|
|
44
|
+
* isBundledBySsr), so in a dev runner that BUNDLES server-utils — the
|
|
45
|
+
* standalone case — its providers extend Node's copy of ShapeProvider, not the
|
|
46
|
+
* one this module imported. Fall back to the class name along the prototype
|
|
47
|
+
* chain. Production has one loader and one copy, so instanceof decides there.
|
|
48
|
+
*/
|
|
49
|
+
function isShapeProvider(provider) {
|
|
50
|
+
var _a;
|
|
51
|
+
if (provider instanceof ShapeProvider)
|
|
52
|
+
return true;
|
|
53
|
+
for (let proto = provider ? Object.getPrototypeOf(provider) : null; proto && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
|
|
54
|
+
if (((_a = proto.constructor) === null || _a === void 0 ? void 0 : _a.name) === 'ShapeProvider')
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Call one provider's hook and contain its failure: a synchronous throw or a
|
|
61
|
+
* rejection is logged, naming the package and the hook, and swallowed. Used
|
|
62
|
+
* for hooks that run for every provider (boot lifecycle hooks and per-request
|
|
63
|
+
* hooks), where one provider's bug must not take down boot or every request
|
|
64
|
+
* without saying which provider it was.
|
|
65
|
+
*/
|
|
66
|
+
async function runProviderHook(pkg, hook, call) {
|
|
67
|
+
var _a;
|
|
68
|
+
try {
|
|
69
|
+
await call();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.error(chalk.red(`[linked] ${pkg} ${hook} failed: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`), (err === null || err === void 0 ? void 0 : err.stack) ? `\n${err.stack}` : '');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
39
75
|
import process from 'process';
|
|
40
76
|
import * as React from 'react';
|
|
41
77
|
import { renderToPipeableStream, renderToStaticMarkup } from 'react-dom/server';
|
|
@@ -51,6 +87,7 @@ import { resolveBootstrapEntry, resolveViteMainEntry, resolveViteServingMode, }
|
|
|
51
87
|
import { resolveRouteAssets } from '../utils/routeAssets.js';
|
|
52
88
|
import { resolveRouteComponent, ssrCssInlineUrls, ssrCssScope, } from '../utils/ssrCss.js';
|
|
53
89
|
import { indexShapesIntoMemory } from '../utils/Shapes.js';
|
|
90
|
+
import { materializeShapes } from '../utils/syncShapes.js';
|
|
54
91
|
import { installSpaFallback, repinSpaFallback, } from '../utils/spaFallback.js';
|
|
55
92
|
import { LincdAPI } from './LincdAPI.js';
|
|
56
93
|
//prevent errors in node.js when (s)css files are imported in js
|
|
@@ -124,6 +161,10 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
124
161
|
this.analyse = false;
|
|
125
162
|
this.shapeProviders = new Map();
|
|
126
163
|
this.genericProviders = new Map();
|
|
164
|
+
// One indexing per package: boot and the lazy /call path share it, so two
|
|
165
|
+
// concurrent first calls (or a call racing boot) never construct a
|
|
166
|
+
// package's providers twice.
|
|
167
|
+
this.providerIndexing = new Map();
|
|
127
168
|
//from resizedFileName to full resized path (CDN or similar)
|
|
128
169
|
this.resizePathsMap = new Map();
|
|
129
170
|
if (config && typeof config !== 'string' && !('id' in config)) {
|
|
@@ -193,15 +234,18 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
193
234
|
* Materialize this server's registered shapes into the RDF store as SHACL
|
|
194
235
|
* (core `syncShapes` → pure `sh:NodeShape` + property shapes), so a running
|
|
195
236
|
* app's app-data holds the shapes its instances validate/query against
|
|
196
|
-
* (plan-010 T1e.2).
|
|
197
|
-
*
|
|
198
|
-
*
|
|
237
|
+
* (plan-010 T1e.2). Whatever shapes the app package registers (published
|
|
238
|
+
* re-exports + generated) get written on boot (and re-run on shape-file HMR).
|
|
239
|
+
*
|
|
240
|
+
* Additive only: store shapes this process did not register are left alone
|
|
241
|
+
* unless the operator sets `LINKED_SYNC_SHAPES_PRUNE_ORPHANS=true` — see
|
|
242
|
+
* `bootOrphanScope` in utils/syncShapes for why.
|
|
199
243
|
*
|
|
200
244
|
* Gated to servers whose DEFAULT dataset is a **concrete** materializable store
|
|
201
245
|
* (detected via `rawQuery`) — i.e. an app pointing at its app-data FusekiStore.
|
|
202
|
-
* CN's default is the context-routing `AppDataRouter` (no `rawQuery
|
|
203
|
-
*
|
|
204
|
-
*
|
|
246
|
+
* CN's default is the context-routing `AppDataRouter` (no `rawQuery`), so CN is
|
|
247
|
+
* skipped here — it materializes its own pinned native shapes in its storage
|
|
248
|
+
* config.
|
|
205
249
|
*/
|
|
206
250
|
async materializeShapesIntoStore() {
|
|
207
251
|
var _a;
|
|
@@ -221,16 +265,8 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
221
265
|
try {
|
|
222
266
|
const { syncShapes } = await import('@_linked/core');
|
|
223
267
|
// Explicit target: materialize EVERY registered shape into the app's own
|
|
224
|
-
// data store regardless of per-shape routing/pins
|
|
225
|
-
|
|
226
|
-
// "whatever the default resolves to per shape".)
|
|
227
|
-
const thunks = await syncShapes(appData);
|
|
228
|
-
// Batched (not all-at-once) so we don't overwhelm Fuseki — the API hands
|
|
229
|
-
// back unexecuted thunks precisely so the caller paces them.
|
|
230
|
-
for (let i = 0; i < thunks.length; i += 8) {
|
|
231
|
-
await Promise.all(thunks.slice(i, i + 8).map((run) => run()));
|
|
232
|
-
}
|
|
233
|
-
console.log(`[LinkedServer] materialized ${thunks.length} shape(s) into app-data`);
|
|
268
|
+
// data store regardless of per-shape routing/pins.
|
|
269
|
+
await materializeShapes(appData, syncShapes);
|
|
234
270
|
}
|
|
235
271
|
catch (err) {
|
|
236
272
|
console.warn('[LinkedServer] shape materialization failed (non-fatal):', err);
|
|
@@ -503,15 +539,21 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
503
539
|
return store.init ? store.init() : Promise.resolve();
|
|
504
540
|
}));
|
|
505
541
|
}
|
|
506
|
-
|
|
542
|
+
/**
|
|
543
|
+
* Run a lifecycle hook (setupBeforeControllers, setupBeforeCatchAllControllers,
|
|
544
|
+
* setupAfterControllers) on every generic provider, in indexing order.
|
|
545
|
+
*
|
|
546
|
+
* Each provider is isolated: a hook that throws (or rejects) is logged with
|
|
547
|
+
* the package and hook name and the next provider still runs, so one broken
|
|
548
|
+
* provider does not abort boot. That provider's routes or middleware from
|
|
549
|
+
* that hook may be missing, but the rest of the app starts.
|
|
550
|
+
*/
|
|
507
551
|
async callGenericBackendProvidersMethod(method, ...args) {
|
|
508
|
-
for (let genericProvider of this.genericProviders.
|
|
509
|
-
if (!genericProvider ||
|
|
552
|
+
for (let [pkg, genericProvider] of this.genericProviders.entries()) {
|
|
553
|
+
if (!genericProvider || typeof genericProvider[method] !== 'function') {
|
|
510
554
|
continue;
|
|
511
555
|
}
|
|
512
|
-
|
|
513
|
-
await Promise.resolve(genericProvider[method](...args));
|
|
514
|
-
}
|
|
556
|
+
await runProviderHook(pkg, method, () => genericProvider[method](...args));
|
|
515
557
|
}
|
|
516
558
|
}
|
|
517
559
|
/**
|
|
@@ -539,16 +581,21 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
539
581
|
// // }, 1000 * 60 * 60); //every hour
|
|
540
582
|
}
|
|
541
583
|
async initBackendProviders() {
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
//legacy packages that may use an old version of the core.
|
|
584
|
+
// Local workspace packages in this app's dependency tree. These are also
|
|
585
|
+
// imported whole at boot (indexLincdPackage), not only their /backend.
|
|
545
586
|
const allLocalPackages = getLincdPackages();
|
|
546
587
|
const localPackageMap = new Map(allLocalPackages.map((pkg) => [pkg.packageName, pkg]));
|
|
547
588
|
const relevantPackages = this.filterPackagesByDependencyTree(localPackageMap, this.package);
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
589
|
+
// Every linked package in the dependency tree, installed or local, each
|
|
590
|
+
// after the linked packages it depends on. Registry installs and
|
|
591
|
+
// localized checkouts are not workspace members, and used to be indexed
|
|
592
|
+
// only lazily, on the first /call/<pkg>/... A provider can hook every
|
|
593
|
+
// request (initRequest, supplyDataForRequest, setupBeforeControllers
|
|
594
|
+
// routes), so those hooks stayed silent until some RPC to the package
|
|
595
|
+
// happened — an installed @_linked/auth did not put the session into a
|
|
596
|
+
// page's request data until then, and its setupBeforeControllers
|
|
597
|
+
// middleware never registered at all.
|
|
598
|
+
await this.indexLinkedDependencyProviders(relevantPackages);
|
|
552
599
|
try {
|
|
553
600
|
await fs
|
|
554
601
|
.readFile(path.join(process.cwd(), 'package.json'), 'utf-8')
|
|
@@ -573,6 +620,67 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
573
620
|
console.warn(err);
|
|
574
621
|
}
|
|
575
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Index the backend providers of every linked package in the app's
|
|
625
|
+
* dependency tree, dependencies first. Workspace members (`localPackages`)
|
|
626
|
+
* 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.
|
|
628
|
+
*/
|
|
629
|
+
async indexLinkedDependencyProviders(localPackages) {
|
|
630
|
+
var _a;
|
|
631
|
+
const started = Date.now();
|
|
632
|
+
const installed = [];
|
|
633
|
+
const ordered = discoverLinkedDependencies(process.cwd(), this.package);
|
|
634
|
+
// A workspace member the walk cannot reach through node_modules still
|
|
635
|
+
// loads, after the rest, as it always did.
|
|
636
|
+
const remaining = new Set(localPackages.keys());
|
|
637
|
+
for (const { packageName } of ordered) {
|
|
638
|
+
const isLocal = remaining.delete(packageName);
|
|
639
|
+
await this.ensurePackageBackendProviders(packageName);
|
|
640
|
+
if (isLocal) {
|
|
641
|
+
await this.indexLincdPackage(packageName);
|
|
642
|
+
}
|
|
643
|
+
else if (this.genericProviders.get(packageName) ||
|
|
644
|
+
((_a = this.shapeProviders.get(packageName)) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
645
|
+
installed.push(packageName);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
for (const packageName of remaining) {
|
|
649
|
+
await this.ensurePackageBackendProviders(packageName);
|
|
650
|
+
await this.indexLincdPackage(packageName);
|
|
651
|
+
}
|
|
652
|
+
if (installed.length > 0) {
|
|
653
|
+
console.log(chalk.gray(`[linked] indexed backend providers of ${ordered.length} linked package(s) in ${Date.now() - started}ms; installed ones with providers: ${installed.join(', ')}`));
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Index a package's backend providers once. Shared by boot and the lazy
|
|
658
|
+
* path in callBackendMethod / callShapeMethod. `onSourceChange` bypasses it
|
|
659
|
+
* on purpose: an HMR reload must re-index.
|
|
660
|
+
*/
|
|
661
|
+
async ensurePackageBackendProviders(pkg, warnIfNotFound = false) {
|
|
662
|
+
if (this.genericProviders.has(pkg) && this.shapeProviders.has(pkg)) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
let indexing = this.providerIndexing.get(pkg);
|
|
666
|
+
if (!indexing) {
|
|
667
|
+
indexing = this.indexPackageBackendProviders(pkg, warnIfNotFound).then(() => undefined);
|
|
668
|
+
this.providerIndexing.set(pkg, indexing);
|
|
669
|
+
}
|
|
670
|
+
try {
|
|
671
|
+
await indexing;
|
|
672
|
+
}
|
|
673
|
+
finally {
|
|
674
|
+
this.providerIndexing.delete(pkg);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
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).
|
|
680
|
+
*/
|
|
681
|
+
importModule(specifier) {
|
|
682
|
+
return import(/* @vite-ignore */ specifier);
|
|
683
|
+
}
|
|
576
684
|
/**
|
|
577
685
|
* Filters local workspace packages to only those reachable from the app's
|
|
578
686
|
* dependency tree. Mirrors the logic used by `buildAll` in lincd-cli.
|
|
@@ -901,7 +1009,7 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
901
1009
|
}
|
|
902
1010
|
}
|
|
903
1011
|
async indexPackageBackendProviders(pkg, warnIfNotFound = false, backendIndexFilePath) {
|
|
904
|
-
var _a;
|
|
1012
|
+
var _a, _b;
|
|
905
1013
|
if (!backendIndexFilePath) {
|
|
906
1014
|
backendIndexFilePath = `${pkg}/backend`;
|
|
907
1015
|
}
|
|
@@ -914,9 +1022,19 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
914
1022
|
// @semantu/create-now/backend), which Node's ESM resolver can't resolve
|
|
915
1023
|
// from inside an installed package like @_linked/server.
|
|
916
1024
|
const vite = (_a = this.config.server) === null || _a === void 0 ? void 0 : _a.vite;
|
|
1025
|
+
// In dev, load a package's backend with the loader that owns the package
|
|
1026
|
+
// (see isBundledBySsr): Vite for the app itself and for what the SSR
|
|
1027
|
+
// runner bundles (workspaces, SSR_ENTRY_PACKAGES), Node for what it
|
|
1028
|
+
// leaves external (registry installs). ssrLoadModule of an EXTERNAL
|
|
1029
|
+
// package's /backend evaluated that package a second time, apart from the
|
|
1030
|
+
// copy the app's own imports reach.
|
|
1031
|
+
const viteOwnsPackage = !!vite &&
|
|
1032
|
+
typeof vite.ssrLoadModule === 'function' &&
|
|
1033
|
+
(pkg === ((_b = this.package) === null || _b === void 0 ? void 0 : _b.name) ||
|
|
1034
|
+
isBundledBySsr(pkg, ssrNoExternalOf(vite)));
|
|
917
1035
|
const loadModule = async (specifier) => {
|
|
918
1036
|
var _a, _b, _c;
|
|
919
|
-
if (
|
|
1037
|
+
if (viteOwnsPackage) {
|
|
920
1038
|
// For the user app's own backend, resolve to ./src/backend.ts directly.
|
|
921
1039
|
// Vite reads the app's package.json exports.development condition.
|
|
922
1040
|
if (specifier === `${this.package.name}/backend`) {
|
|
@@ -956,7 +1074,8 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
956
1074
|
}
|
|
957
1075
|
return await vite.ssrLoadModule(specifier);
|
|
958
1076
|
}
|
|
959
|
-
//
|
|
1077
|
+
// Node path: production runtime, or a package the dev SSR runner
|
|
1078
|
+
// leaves external.
|
|
960
1079
|
//
|
|
961
1080
|
// The app's own backend cannot be imported by name here. A self-reference
|
|
962
1081
|
// resolves only from inside the package that declares it, and this code
|
|
@@ -971,7 +1090,7 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
971
1090
|
return await import(/* @vite-ignore */ pathToFileURL(target).href);
|
|
972
1091
|
}
|
|
973
1092
|
// Fall back to Node's resolver. The dynamic specifier is intentional.
|
|
974
|
-
return await
|
|
1093
|
+
return await this.importModule(specifier);
|
|
975
1094
|
};
|
|
976
1095
|
await loadModule(backendIndexFilePath)
|
|
977
1096
|
.then((backendProviderExports) => {
|
|
@@ -981,7 +1100,7 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
981
1100
|
//always send an instance of the express server
|
|
982
1101
|
//TODO: do not create an instance, just save the class and instantiate it when needed
|
|
983
1102
|
let provider = new providerClass(this.server, this);
|
|
984
|
-
if (provider
|
|
1103
|
+
if (isShapeProvider(provider)) {
|
|
985
1104
|
shapeProviders.push(provider);
|
|
986
1105
|
if (!Object.getOwnPropertyNames(provider).includes('shape')) {
|
|
987
1106
|
console.warn(chalk.red(`${Object.getPrototypeOf(provider).constructor.name} in package ${pkg}
|
|
@@ -1196,7 +1315,7 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
1196
1315
|
var _a;
|
|
1197
1316
|
//- index module providers if not done yet
|
|
1198
1317
|
if (!this.shapeProviders.has(pkg)) {
|
|
1199
|
-
await this.
|
|
1318
|
+
await this.ensurePackageBackendProviders(pkg, true);
|
|
1200
1319
|
}
|
|
1201
1320
|
let packageShapeProviders = this.shapeProviders.get(pkg);
|
|
1202
1321
|
let findProviderForShape = (nodeShapeId) => {
|
|
@@ -1204,7 +1323,7 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
1204
1323
|
var _a, _b;
|
|
1205
1324
|
//access the static shape (which is a linkedShape() / Shape class)
|
|
1206
1325
|
//then access the SHACL NodeShape of that shape class, and its id
|
|
1207
|
-
return (provider
|
|
1326
|
+
return (isShapeProvider(provider) &&
|
|
1208
1327
|
((_b = (_a = provider.shape) === null || _a === void 0 ? void 0 : _a.shape) === null || _b === void 0 ? void 0 : _b.id) === nodeShapeId);
|
|
1209
1328
|
});
|
|
1210
1329
|
};
|
|
@@ -1653,32 +1772,29 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
1653
1772
|
res.json(jsonObject);
|
|
1654
1773
|
}
|
|
1655
1774
|
async initRequest(request, response) {
|
|
1656
|
-
// initialise the request for all providers,
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
.catch((err) => {
|
|
1666
|
-
console.warn(`Error during initRequest for provider ${Object.getPrototypeOf(backendProvider).constructor.name}: `, err);
|
|
1667
|
-
});
|
|
1668
|
-
});
|
|
1669
|
-
return p;
|
|
1775
|
+
// initialise the request for all providers, one after the other (order
|
|
1776
|
+
// matters: later providers may read what earlier ones put on the request).
|
|
1777
|
+
// A provider that throws is logged by package name and skipped.
|
|
1778
|
+
for (const [pkg, backendProvider] of this.genericProviders.entries()) {
|
|
1779
|
+
if (!backendProvider || typeof backendProvider.initRequest !== 'function') {
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
await runProviderHook(pkg, 'initRequest', () => backendProvider.initRequest(request, response));
|
|
1783
|
+
}
|
|
1670
1784
|
}
|
|
1671
1785
|
async getRequestData(request, response) {
|
|
1672
|
-
//
|
|
1673
|
-
// requestLD is kept as empty string for now
|
|
1674
|
-
//
|
|
1786
|
+
// Providers return plain JSON data via supplyDataForRequest.
|
|
1787
|
+
// requestLD is kept as empty string for now; SSR data seeding will
|
|
1788
|
+
// inject query results instead of graph data.
|
|
1789
|
+
// A provider that throws (synchronously or not) is logged by package
|
|
1790
|
+
// name; the page still renders with the other providers' data.
|
|
1675
1791
|
let requestData = {};
|
|
1676
|
-
await Promise.all([...this.genericProviders.
|
|
1677
|
-
if (backendProvider
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
});
|
|
1792
|
+
await Promise.all([...this.genericProviders.entries()].map(([pkg, backendProvider]) => {
|
|
1793
|
+
if (!backendProvider ||
|
|
1794
|
+
typeof backendProvider.supplyDataForRequest !== 'function') {
|
|
1795
|
+
return;
|
|
1681
1796
|
}
|
|
1797
|
+
return runProviderHook(pkg, 'supplyDataForRequest', () => backendProvider.supplyDataForRequest(request, response, requestData));
|
|
1682
1798
|
}));
|
|
1683
1799
|
let requestLD = '';
|
|
1684
1800
|
let requestObject = JSONWriter.stringify(request['frontendData']);
|
|
@@ -1686,7 +1802,7 @@ let LinkedServer = class LinkedServer extends Shape {
|
|
|
1686
1802
|
}
|
|
1687
1803
|
async callBackendMethod(pkg, method, args, request, response) {
|
|
1688
1804
|
if (!this.genericProviders.has(pkg)) {
|
|
1689
|
-
await this.
|
|
1805
|
+
await this.ensurePackageBackendProviders(pkg, true);
|
|
1690
1806
|
}
|
|
1691
1807
|
//retrieve the indexed provider class and create a new instance for this request
|
|
1692
1808
|
let genericBackendProvider = this.genericProviders.get(pkg);
|