@_linked/server 2.17.0 → 2.19.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,19 @@
1
1
  # @\_linked/server
2
2
 
3
+ ## 2.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#130](https://github.com/linked-fw/server/pull/130) [`e554786`](https://github.com/linked-fw/server/commit/e554786ed4b43738d820290c8ece89d0788c8fd8) Thanks [@flyon](https://github.com/flyon)! - A provider can refuse to let the server start. An error with an own `fatal: true` property thrown at boot — from a provider constructor, a backend module's top level, or a boot hook — is no longer isolated: it is logged as `[linked] <pkg> <hook>: fatal error, refusing to start: …`, `start()` / `initOnly()` reject with it, and the process exits with code 1. After boot (per-request hooks, lazy loads, HMR) it is logged as fatal and contained. Non-fatal errors are isolated as before. Adds `FatalStartupError` and `isFatalError` (`@_linked/server/utils/fatalError`, also exported from the package root).
8
+
9
+ ## 2.18.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#128](https://github.com/linked-fw/server/pull/128) [`2b8e8ac`](https://github.com/linked-fw/server/commit/2b8e8acde912d91d8b706f09ca0a6e2663155c0c) Thanks [@flyon](https://github.com/flyon)! - The server ontology moves from `http://lincd.org/ont/lincd-server/` to `https://linked.cm/ont/server/`, the first-party scheme every public package uses (`https://linked.cm/ont/{publicSlug}/`, next to its shapes at `https://linked.cm/shape/server/`).
14
+
15
+ No data migration is needed. No stored data is typed with these terms (`LincdServer`, `LincdAPI`, `LincdWebApp`, `hasAPI`, …). The only store triples that carried them are the synced shape descriptions of `LinkedServer`, `LincdAPI` and `LincdWebApp` (`sh:targetClass`, `sh:path`); boot sync deletes and recreates each registered shape's description, so they move to the new IRIs the next time the server starts. With `syncShapesOnBoot: false` they keep the old IRIs until the next sync. The prefix key (`lincd-server`) and the `ontologies/lincd-server` module are unchanged; code that hard-codes `http://lincd.org/ont/lincd-server/` must be updated.
16
+
3
17
  ## 2.17.0
4
18
 
5
19
  ### Minor Changes
@@ -4,7 +4,7 @@
4
4
  "owl": "http://www.w3.org/2002/07/owl#",
5
5
  "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
6
6
  "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
7
- "lincd-server": "http://lincd.org/ont/lincd-server/"
7
+ "lincd-server": "https://linked.cm/ont/server/"
8
8
  },
9
9
  "@graph": [
10
10
  {
@@ -4,4 +4,6 @@ import './shapes/index.js';
4
4
  import './shapes/quadstores/BackendAPIStore.js';
5
5
  import './shapes/filestores/LocalFileStore.js';
6
6
  import './utils/accessUrl.js';
7
+ export { FatalStartupError, isFatalError } from './utils/fatalError.js';
8
+ export type { FatalError } from './utils/fatalError.js';
7
9
  export type { RouteConfig, RoutesConfig, RoutesModule, } from '@_linked/server-utils/types/RouteConfig';
package/lib/esm/index.js CHANGED
@@ -7,4 +7,6 @@ import './shapes/quadstores/BackendAPIStore.js';
7
7
  import './shapes/filestores/LocalFileStore.js';
8
8
  //THEN COMPONENTS
9
9
  import './utils/accessUrl.js';
10
+ // The fatal-error contract a provider uses to refuse to let the server start.
11
+ export { FatalStartupError, isFatalError } from './utils/fatalError.js';
10
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AACpB,OAAO,uCAAuC,CAAC;AAE/C,cAAc;AACd,OAAO,mBAAmB,CAAC;AAC3B,mFAAmF;AACnF,OAAO,wCAAwC,CAAC;AAChD,OAAO,uCAAuC,CAAC;AAE/C,iBAAiB;AACjB,OAAO,sBAAsB,CAAC","sourcesContent":["import './types.js';\nimport './ontologies/lincd-server.register.js';\n\n//SHAPES FIRST\nimport './shapes/index.js';\n// Not shapes (plain store classes); kept so the entry's module graph is unchanged.\nimport './shapes/quadstores/BackendAPIStore.js';\nimport './shapes/filestores/LocalFileStore.js';\n\n//THEN COMPONENTS\nimport './utils/accessUrl.js';\n\n//TYPES — re-exported from @_linked/server-utils (canonical location)\nexport type {\n RouteConfig,\n RoutesConfig,\n RoutesModule,\n} from '@_linked/server-utils/types/RouteConfig';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AACpB,OAAO,uCAAuC,CAAC;AAE/C,cAAc;AACd,OAAO,mBAAmB,CAAC;AAC3B,mFAAmF;AACnF,OAAO,wCAAwC,CAAC;AAChD,OAAO,uCAAuC,CAAC;AAE/C,iBAAiB;AACjB,OAAO,sBAAsB,CAAC;AAE9B,8EAA8E;AAC9E,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["import './types.js';\nimport './ontologies/lincd-server.register.js';\n\n//SHAPES FIRST\nimport './shapes/index.js';\n// Not shapes (plain store classes); kept so the entry's module graph is unchanged.\nimport './shapes/quadstores/BackendAPIStore.js';\nimport './shapes/filestores/LocalFileStore.js';\n\n//THEN COMPONENTS\nimport './utils/accessUrl.js';\n\n// The fatal-error contract a provider uses to refuse to let the server start.\nexport { FatalStartupError, isFatalError } from './utils/fatalError.js';\nexport type { FatalError } from './utils/fatalError.js';\n\n//TYPES — re-exported from @_linked/server-utils (canonical location)\nexport type {\n RouteConfig,\n RoutesConfig,\n RoutesModule,\n} from '@_linked/server-utils/types/RouteConfig';\n"]}
@@ -20,6 +20,18 @@ export declare var loadData: () => Promise<{
20
20
  "rdfs:label": string;
21
21
  }[];
22
22
  }>;
23
+ /**
24
+ * The namespace of this ontology.
25
+ *
26
+ * First-party ontologies live on linked.cm: `https://linked.cm/ont/{ontologySlug}/`, and a
27
+ * package's own ontology takes the package's publicSlug (`@_linked/server` → `server`), the same
28
+ * slug its shapes use under `https://linked.cm/shape/server/`.
29
+ *
30
+ * Until this release it was `http://lincd.org/ont/lincd-server/`. No stored data is typed with
31
+ * these terms; the only store triples that carried them were the synced shape descriptions of
32
+ * `LinkedServer`, `LincdAPI` and `LincdWebApp` (`sh:targetClass`, `sh:path`), which boot sync
33
+ * rewrites (delete, then recreate) the next time the server starts.
34
+ */
23
35
  export declare var ns: (term: string) => import("@_linked/core/utils/NodeReference").NodeReferenceValue;
24
36
  export declare var _self: import("@_linked/core/utils/NodeReference").NodeReferenceValue;
25
37
  export declare const lincdServer: {
@@ -10,7 +10,19 @@ export var loadData = async () => {
10
10
  });
11
11
  return data.default || data;
12
12
  };
13
- export var ns = createNameSpace('http://lincd.org/ont/lincd-server/');
13
+ /**
14
+ * The namespace of this ontology.
15
+ *
16
+ * First-party ontologies live on linked.cm: `https://linked.cm/ont/{ontologySlug}/`, and a
17
+ * package's own ontology takes the package's publicSlug (`@_linked/server` → `server`), the same
18
+ * slug its shapes use under `https://linked.cm/shape/server/`.
19
+ *
20
+ * Until this release it was `http://lincd.org/ont/lincd-server/`. No stored data is typed with
21
+ * these terms; the only store triples that carried them were the synced shape descriptions of
22
+ * `LinkedServer`, `LincdAPI` and `LincdWebApp` (`sh:targetClass`, `sh:path`), which boot sync
23
+ * rewrites (delete, then recreate) the next time the server starts.
24
+ */
25
+ export var ns = createNameSpace('https://linked.cm/ont/server/');
14
26
  export var _self = ns('');
15
27
  // Terms are properties of the namespace object below, not module-scope
16
28
  // exports. Most of these names are also SHAPE CLASS names in this package
@@ -1 +1 @@
1
- {"version":3,"file":"lincd-server.js","sourceRoot":"","sources":["../../../src/ontologies/lincd-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE;;;GAGG;AACH,MAAM,CAAC,IAAI,QAAQ,GAAG,KAAK,IAAI,EAAE;IAC/B,YAAY;IACZ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,2BAA2B,EAAE;QACrD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;KACvB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,CAAC,IAAI,EAAE,GAAG,eAAe,CAAC,oCAAoC,CAAC,CAAC;AAEtE,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;AAE1B,uEAAuE;AACvE,0EAA0E;AAC1E,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,gBAAgB,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,aAAa,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC;AAC1C,MAAM,YAAY,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC;AACxC,MAAM,eAAe,GAAG,EAAE,CAAC,iBAAiB,CAAC,CAAC;AAC9C,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC;AACpC,MAAM,gBAAgB,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;AAChC,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAE5B,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW;IACX,gBAAgB;IAChB,aAAa;IACb,YAAY;IACZ,WAAW;IACX,eAAe;IACf,WAAW;IACX,UAAU;IACV,gBAAgB;IAChB,QAAQ;IACR,MAAM;CACP,CAAC","sourcesContent":["import { createNameSpace } from '@_linked/core/utils/NameSpace';\n\n/**\n * Load the data of this ontology.\n * In @_linked/core, loadData returns the raw JSON import — no JSONLD.parse() needed.\n */\nexport var loadData = async () => {\n //@ts-ignore\n const data = await import('../data/lincd-server.json', {\n with: { type: 'json' },\n });\n return data.default || data;\n};\n\nexport var ns = createNameSpace('http://lincd.org/ont/lincd-server/');\n\nexport var _self = ns('');\n\n// Terms are properties of the namespace object below, not module-scope\n// exports. Most of these names are also SHAPE CLASS names in this package\n// (BackendAPIStore, LincdWebApp, LincdAPI, ...). Two bindings of the same name\n// in one bundle scope make the bundler rename one of them, and when the loser\n// is the class, `constructor.name` becomes `BackendAPIStore2` -- which is the\n// shape's IRI and the name `Server.call` routes on. The client then asks a\n// backend that has no such shape, and every store round-trip answers 501.\nconst LincdServer = ns('LincdServer');\nconst BackendFileStore = ns('BackendFileStore');\nconst NodeFileStore = ns('NodeFileStore');\nconst BackendStore = ns('BackendStore');\nconst BackendAPIStore = ns('BackendAPIStore');\nconst LincdWebApp = ns('LincdWebApp');\nconst ownPackage = ns('ownPackage');\nconst maintainsPackage = ns('maintainsPackage');\nconst N3FileStore = ns('N3FileStore');\nconst LincdAPI = ns('LincdAPI');\nconst hasAPI = ns('hasAPI');\n\nexport const lincdServer = {\n LincdServer,\n BackendFileStore,\n NodeFileStore,\n BackendStore,\n N3FileStore,\n BackendAPIStore,\n LincdWebApp,\n ownPackage,\n maintainsPackage,\n LincdAPI,\n hasAPI,\n};\n\n"]}
1
+ {"version":3,"file":"lincd-server.js","sourceRoot":"","sources":["../../../src/ontologies/lincd-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE;;;GAGG;AACH,MAAM,CAAC,IAAI,QAAQ,GAAG,KAAK,IAAI,EAAE;IAC/B,YAAY;IACZ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,2BAA2B,EAAE;QACrD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;KACvB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;AAC9B,CAAC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,IAAI,EAAE,GAAG,eAAe,CAAC,+BAA+B,CAAC,CAAC;AAEjE,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;AAE1B,uEAAuE;AACvE,0EAA0E;AAC1E,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,gBAAgB,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,aAAa,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC;AAC1C,MAAM,YAAY,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC;AACxC,MAAM,eAAe,GAAG,EAAE,CAAC,iBAAiB,CAAC,CAAC;AAC9C,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC;AACpC,MAAM,gBAAgB,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC;AAChD,MAAM,WAAW,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;AACtC,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;AAChC,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;AAE5B,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW;IACX,gBAAgB;IAChB,aAAa;IACb,YAAY;IACZ,WAAW;IACX,eAAe;IACf,WAAW;IACX,UAAU;IACV,gBAAgB;IAChB,QAAQ;IACR,MAAM;CACP,CAAC","sourcesContent":["import { createNameSpace } from '@_linked/core/utils/NameSpace';\n\n/**\n * Load the data of this ontology.\n * In @_linked/core, loadData returns the raw JSON import — no JSONLD.parse() needed.\n */\nexport var loadData = async () => {\n //@ts-ignore\n const data = await import('../data/lincd-server.json', {\n with: { type: 'json' },\n });\n return data.default || data;\n};\n\n/**\n * The namespace of this ontology.\n *\n * First-party ontologies live on linked.cm: `https://linked.cm/ont/{ontologySlug}/`, and a\n * package's own ontology takes the package's publicSlug (`@_linked/server` → `server`), the same\n * slug its shapes use under `https://linked.cm/shape/server/`.\n *\n * Until this release it was `http://lincd.org/ont/lincd-server/`. No stored data is typed with\n * these terms; the only store triples that carried them were the synced shape descriptions of\n * `LinkedServer`, `LincdAPI` and `LincdWebApp` (`sh:targetClass`, `sh:path`), which boot sync\n * rewrites (delete, then recreate) the next time the server starts.\n */\nexport var ns = createNameSpace('https://linked.cm/ont/server/');\n\nexport var _self = ns('');\n\n// Terms are properties of the namespace object below, not module-scope\n// exports. Most of these names are also SHAPE CLASS names in this package\n// (BackendAPIStore, LincdWebApp, LincdAPI, ...). Two bindings of the same name\n// in one bundle scope make the bundler rename one of them, and when the loser\n// is the class, `constructor.name` becomes `BackendAPIStore2` -- which is the\n// shape's IRI and the name `Server.call` routes on. The client then asks a\n// backend that has no such shape, and every store round-trip answers 501.\nconst LincdServer = ns('LincdServer');\nconst BackendFileStore = ns('BackendFileStore');\nconst NodeFileStore = ns('NodeFileStore');\nconst BackendStore = ns('BackendStore');\nconst BackendAPIStore = ns('BackendAPIStore');\nconst LincdWebApp = ns('LincdWebApp');\nconst ownPackage = ns('ownPackage');\nconst maintainsPackage = ns('maintainsPackage');\nconst N3FileStore = ns('N3FileStore');\nconst LincdAPI = ns('LincdAPI');\nconst hasAPI = ns('hasAPI');\n\nexport const lincdServer = {\n LincdServer,\n BackendFileStore,\n NodeFileStore,\n BackendStore,\n N3FileStore,\n BackendAPIStore,\n LincdWebApp,\n ownPackage,\n maintainsPackage,\n LincdAPI,\n hasAPI,\n};\n\n"]}
@@ -51,6 +51,12 @@ export declare class LinkedServer extends Shape {
51
51
  private warnedDuplicateInstalls;
52
52
  private resizePathsMap;
53
53
  private api;
54
+ /**
55
+ * True while start() / initOnly() runs. A fatal provider error (see
56
+ * utils/fatalError) thrown while booting stops the boot; after it, the same
57
+ * error is logged and contained.
58
+ */
59
+ protected booting: boolean;
54
60
  /**
55
61
  * yarn linked start sends the contents of linked.config.js as an object to this constructor
56
62
  * @param n
@@ -61,6 +67,7 @@ export declare class LinkedServer extends Shape {
61
67
  get app(): ExpressServer;
62
68
  initPackage(): void;
63
69
  initOnly(): Promise<this>;
70
+ private initOnlyBoot;
64
71
  /**
65
72
  * Materialize this server's registered shapes into the RDF store as SHACL
66
73
  * (core `syncShapes` → pure `sh:NodeShape` + property shapes), so a running
@@ -79,7 +86,32 @@ export declare class LinkedServer extends Shape {
79
86
  * config.
80
87
  */
81
88
  private materializeShapesIntoStore;
89
+ /**
90
+ * Boot the server and listen. Rejects — and makes the process exit non-zero —
91
+ * when a provider throws a fatal error while booting (see utils/fatalError).
92
+ */
82
93
  start(): Promise<this>;
94
+ /**
95
+ * Run a boot with `booting` set. A fatal provider error rejects it and hands
96
+ * the error to exitAfterFatalStartupError; any other boot error rejects as it
97
+ * always did.
98
+ */
99
+ private bootOrRefuse;
100
+ /**
101
+ * Make sure a server that refused to start does not stay up as a process.
102
+ *
103
+ * start() rejects either way, so the caller can report the error. But a
104
+ * rejected start() does not end the process on its own: the CLI's `linked
105
+ * start` turns an unhandled rejection into a log line, and the Vite dev
106
+ * server, a store connection or a provider's timer keeps the event loop
107
+ * alive. So the exit code is set now — a process with nothing left running
108
+ * exits 1 — and, as a backstop, the process is exited after a moment, on an
109
+ * unref'd timer that never itself keeps the process alive.
110
+ *
111
+ * Override to embed the server somewhere that must not exit (tests).
112
+ */
113
+ protected exitAfterFatalStartupError(_err: unknown): void;
114
+ private startBoot;
83
115
  initOntologies(): Promise<void>;
84
116
  /**
85
117
  *
@@ -37,6 +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 { isFatalError } from '../utils/fatalError.js';
40
41
  import { discoverLinkedDependencies, isBundledBySsr, NODE_IMPORT_CONDITIONS, readInstalledPackage, resolveBackendEntry, ssrNoExternalOf, } from '../utils/linkedDependencies.js';
41
42
  /**
42
43
  * `instanceof ShapeProvider`, tolerating a second copy of server-utils.
@@ -63,16 +64,38 @@ function isShapeProvider(provider) {
63
64
  * for hooks that run for every provider (boot lifecycle hooks and per-request
64
65
  * hooks), where one provider's bug must not take down boot or every request
65
66
  * without saying which provider it was.
67
+ *
68
+ * The exception is a fatal error (see utils/fatalError): with `atBoot` it is
69
+ * logged and re-thrown, so the server refuses to start. Without it — the
70
+ * server is already serving — it is logged as fatal and contained like any
71
+ * other error: one provider must not take a running server down.
66
72
  */
67
- async function runProviderHook(pkg, hook, call) {
73
+ async function runProviderHook(pkg, hook, call, atBoot = false) {
68
74
  var _a;
69
75
  try {
70
76
  await call();
71
77
  }
72
78
  catch (err) {
79
+ if (isFatalError(err)) {
80
+ logFatalError(pkg, hook, err, atBoot);
81
+ if (atBoot)
82
+ throw err;
83
+ return;
84
+ }
73
85
  console.error(paint('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}` : '');
74
86
  }
75
87
  }
88
+ /** Log a fatal error once, at the point it was thrown (see runProviderHook). */
89
+ function logFatalError(pkg, hook, err, atBoot) {
90
+ var _a, _b;
91
+ if (loggedFatalErrors.has(err))
92
+ return;
93
+ loggedFatalErrors.add(err);
94
+ console.error(paint('red', atBoot
95
+ ? `[linked] ${pkg} ${hook}: fatal error, refusing to start: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`
96
+ : `[linked] ${pkg} ${hook}: fatal error after start, server keeps running: ${(_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err}`), (err === null || err === void 0 ? void 0 : err.stack) ? `\n${err.stack}` : '');
97
+ }
98
+ const loggedFatalErrors = new WeakSet();
76
99
  import process from 'process';
77
100
  import * as React from 'react';
78
101
  import { renderToPipeableStream, renderToStaticMarkup } from 'react-dom/server';
@@ -178,6 +201,12 @@ let LinkedServer = class LinkedServer extends Shape {
178
201
  this.warnedDuplicateInstalls = new Set();
179
202
  //from resizedFileName to full resized path (CDN or similar)
180
203
  this.resizePathsMap = new Map();
204
+ /**
205
+ * True while start() / initOnly() runs. A fatal provider error (see
206
+ * utils/fatalError) thrown while booting stops the boot; after it, the same
207
+ * error is logged and contained.
208
+ */
209
+ this.booting = false;
181
210
  if (config && typeof config !== 'string' && !('id' in config)) {
182
211
  // The absence of `id` is the runtime discriminator for a LinkedConfig;
183
212
  // the widened `{id?: string}` member of the union keeps TS from narrowing
@@ -232,6 +261,9 @@ let LinkedServer = class LinkedServer extends Shape {
232
261
  this.package = JSON.parse(fsNative.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'));
233
262
  }
234
263
  async initOnly() {
264
+ return this.bootOrRefuse(() => this.initOnlyBoot());
265
+ }
266
+ async initOnlyBoot() {
235
267
  await this.initOntologies();
236
268
  await this.initStores();
237
269
  this.initPackage();
@@ -290,7 +322,50 @@ let LinkedServer = class LinkedServer extends Shape {
290
322
  // return nodeURI.includes(store.namedNode.uri)
291
323
  // })
292
324
  // }
325
+ /**
326
+ * Boot the server and listen. Rejects — and makes the process exit non-zero —
327
+ * when a provider throws a fatal error while booting (see utils/fatalError).
328
+ */
293
329
  async start() {
330
+ return this.bootOrRefuse(() => this.startBoot());
331
+ }
332
+ /**
333
+ * Run a boot with `booting` set. A fatal provider error rejects it and hands
334
+ * the error to exitAfterFatalStartupError; any other boot error rejects as it
335
+ * always did.
336
+ */
337
+ async bootOrRefuse(boot) {
338
+ this.booting = true;
339
+ try {
340
+ return await boot();
341
+ }
342
+ catch (err) {
343
+ if (isFatalError(err))
344
+ this.exitAfterFatalStartupError(err);
345
+ throw err;
346
+ }
347
+ finally {
348
+ this.booting = false;
349
+ }
350
+ }
351
+ /**
352
+ * Make sure a server that refused to start does not stay up as a process.
353
+ *
354
+ * start() rejects either way, so the caller can report the error. But a
355
+ * rejected start() does not end the process on its own: the CLI's `linked
356
+ * start` turns an unhandled rejection into a log line, and the Vite dev
357
+ * server, a store connection or a provider's timer keeps the event loop
358
+ * alive. So the exit code is set now — a process with nothing left running
359
+ * exits 1 — and, as a backstop, the process is exited after a moment, on an
360
+ * unref'd timer that never itself keeps the process alive.
361
+ *
362
+ * Override to embed the server somewhere that must not exit (tests).
363
+ */
364
+ exitAfterFatalStartupError(_err) {
365
+ process.exitCode = 1;
366
+ setTimeout(() => process.exit(1), 1000).unref();
367
+ }
368
+ async startBoot() {
294
369
  var _a, _b, _c, _d;
295
370
  this.initPackage();
296
371
  // Static assets should come from the static store URL (versioned path),
@@ -563,7 +638,7 @@ let LinkedServer = class LinkedServer extends Shape {
563
638
  if (!genericProvider || typeof genericProvider[method] !== 'function') {
564
639
  continue;
565
640
  }
566
- await runProviderHook(pkg, method, () => genericProvider[method](...args));
641
+ await runProviderHook(pkg, method, () => genericProvider[method](...args), this.booting);
567
642
  }
568
643
  }
569
644
  /**
@@ -627,6 +702,9 @@ let LinkedServer = class LinkedServer extends Shape {
627
702
  });
628
703
  }
629
704
  catch (err) {
705
+ // Already logged where it was thrown; it must reach start().
706
+ if (isFatalError(err))
707
+ throw err;
630
708
  console.warn(err);
631
709
  }
632
710
  }
@@ -1053,6 +1131,12 @@ let LinkedServer = class LinkedServer extends Shape {
1053
1131
  // console.log(`✅ Successfully loaded: ${pkg}`);
1054
1132
  }
1055
1133
  catch (e) {
1134
+ if (isFatalError(e)) {
1135
+ logFatalError(pkg, 'module load', e, this.booting);
1136
+ if (this.booting)
1137
+ throw e;
1138
+ return;
1139
+ }
1056
1140
  let providerNotFound = e.code === 'MODULE_NOT_FOUND' &&
1057
1141
  e.message.indexOf(`Cannot find package '${pkg}'`) !== -1;
1058
1142
  if (providerNotFound) {
@@ -1168,7 +1252,7 @@ let LinkedServer = class LinkedServer extends Shape {
1168
1252
  await runProviderHook(pkg, `backend ${entry}`, async () => {
1169
1253
  backendProviderExports = await load();
1170
1254
  loaded = true;
1171
- });
1255
+ }, this.booting);
1172
1256
  if (loaded) {
1173
1257
  for (const key of Object.keys(backendProviderExports)) {
1174
1258
  const providerClass = backendProviderExports[key];
@@ -1177,7 +1261,7 @@ let LinkedServer = class LinkedServer extends Shape {
1177
1261
  //TODO: do not create an instance, just save the class and instantiate it when needed
1178
1262
  await runProviderHook(pkg, `${key} constructor`, () => {
1179
1263
  provider = new providerClass(this.server, this);
1180
- });
1264
+ }, this.booting);
1181
1265
  if (!provider)
1182
1266
  continue;
1183
1267
  if (isShapeProvider(provider)) {