@_linked/server 2.18.0 → 2.20.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.
@@ -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';
@@ -81,7 +104,7 @@ import sharp from 'sharp';
81
104
  import { findStoredKey, resolveResizeSource, } from '../utils/resizeSource.js';
82
105
  import { Transform } from 'stream';
83
106
  import { CookieJar } from 'tough-cookie';
84
- import { lincdServer } from '../ontologies/lincd-server.js';
107
+ import { server } from '../ontologies/server.js';
85
108
  import { linkedShape } from '../package.js';
86
109
  import { resolveStaticAccessURL, staticAssetURL, } from '../utils/releaseManifest.js';
87
110
  import { resolveBootstrapEntry, resolveViteMainEntry, resolveViteServingMode, } from '../utils/bootstrapEntry.js';
@@ -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)) {
@@ -1888,7 +1972,7 @@ let LinkedServer = class LinkedServer extends Shape {
1888
1972
  /**
1889
1973
  * indicates that instances of this shape need to have this rdf.type
1890
1974
  */
1891
- LinkedServer.targetClass = lincdServer.LincdServer;
1975
+ LinkedServer.targetClass = server.LincdServer;
1892
1976
  LinkedServer = __decorate([
1893
1977
  linkedShape({ name: 'LinkedServer' }),
1894
1978
  __metadata("design:paramtypes", [Object])