@_linked/server 3.6.0 → 3.6.1

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,11 @@
1
1
  # @\_linked/server
2
2
 
3
+ ## 3.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#200](https://github.com/linked-fw/server/pull/200) [`935d0ca`](https://github.com/linked-fw/server/commit/935d0caf014cae8c71ae3b40963305ac8d43792c) Thanks [@flyon](https://github.com/flyon)! - `POST /admin/sync-shapes` now answers which packages it reloaded (`reloaded`) and the node shapes registered afterwards (`shapes`), refuses (400) package names that are not valid npm names, `@_linked/*` packages, or neither the app nor one of its direct dependencies, and times out a hung sync (`LINKED_SHAPE_SYNC_TIMEOUT_MS`, default 120 s) or source reload (`LINKED_SOURCE_RELOAD_TIMEOUT_MS`, default 60 s) so the queue is released. The README documents the control channel and the side effects of a reload and a restart.
8
+
3
9
  ## 3.6.0
4
10
 
5
11
  ### Minor Changes
@@ -135,16 +135,25 @@ export declare class LinkedServer extends Shape {
135
135
  * changed the app's shape code, so the app — the only process that loads that
136
136
  * code — writes its own shapes and their hashes.
137
137
  *
138
- * `packages` names the packages whose source changed. In dev (Vite) their
139
- * modules are invalidated and their backends reloaded first
140
- * ({@link onSourceChange}), so a shape added since the boot is registered
141
- * before the sync; without Vite (a built app) the sync covers the shapes the
142
- * process loaded. Runs as the system, never as the caller of a request.
138
+ * `packages` names the packages whose source changed: the app itself or its
139
+ * direct dependencies, never `@_linked/*` ({@link assertReloadablePackages}). In
140
+ * dev (Vite) their modules are invalidated and their backends reloaded first
141
+ * ({@link onSourceChange}), so a shape added since the boot is registered before
142
+ * the sync; without Vite (a built app) nothing is reloaded. The answer names the
143
+ * packages reloaded and the node shapes registered after it, so the host can check
144
+ * that the shape it wrote is there. Runs as the system, never as the caller of a
145
+ * request, one at a time; a run that takes longer than
146
+ * `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120 s) fails and releases the queue.
143
147
  */
144
148
  resyncShapes({ packages }?: {
145
149
  packages?: string[];
146
150
  }): Promise<ShapeSyncResult>;
147
- /** Re-read one package's source from disk (dev only); see {@link resyncShapes}. */
151
+ /** The packages a host may ask to reload: the app itself and its direct dependencies. */
152
+ private reloadablePackages;
153
+ /**
154
+ * Re-read one package's source from disk (dev only); see {@link resyncShapes}.
155
+ * Resolves whether it reloaded anything.
156
+ */
148
157
  private reloadPackageFromDisk;
149
158
  /**
150
159
  * Boot the server and listen. Rejects — and makes the process exit non-zero —
@@ -257,7 +266,8 @@ export declare class LinkedServer extends Shape {
257
266
  * don't accumulate), drops them from the registry, and re-runs
258
267
  * indexPackageBackendProviders to pick up the freshly-imported module.
259
268
  * Reloads run one at a time: each re-registers routes on the shared express
260
- * stack.
269
+ * stack. One that takes longer than `LINKED_SOURCE_RELOAD_TIMEOUT_MS` (default
270
+ * 60 s) rejects and releases the queue.
261
271
  *
262
272
  * Dispose calls have a 5 s soft timeout. A hanging dispose logs a
263
273
  * warning and is abandoned so HMR doesn't stall the whole dev loop.
@@ -37,7 +37,7 @@ import { nativeImport } from '../utils/nativeImport.js';
37
37
  import { createJsonBodyParser } from '../utils/jsonBodyParser.js';
38
38
  import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
39
39
  import { autoLoadOntologyData } from '@_linked/core/utils/Package';
40
- import { getShapeClass, getSuperShapesClasses, } from '@_linked/core/utils/ShapeClass';
40
+ import { getAllShapeClasses, getShapeClass, getSuperShapesClasses, } from '@_linked/core/utils/ShapeClass';
41
41
  import path from 'path';
42
42
  import { pathToFileURL } from 'node:url';
43
43
  import { isFatalError } from '../utils/fatalError.js';
@@ -139,7 +139,8 @@ import { resolveBootstrapEntry, resolveViteMainEntry, resolveViteServingMode, }
139
139
  import { resolveRouteAssets } from '../utils/routeAssets.js';
140
140
  import { resolveRouteComponent, ssrCssInlineUrls, ssrCssScope, } from '../utils/ssrCss.js';
141
141
  import { materializeShapes } from '../utils/syncShapes.js';
142
- import { AdminRequestError, registerAdminRoutes, } from '../utils/adminChannel.js';
142
+ import { AdminRequestError, assertReloadablePackages, registerAdminRoutes, } from '../utils/adminChannel.js';
143
+ import { envMs, withTimeout } from '../utils/withTimeout.js';
143
144
  import { invalidateSourceUnder } from '../utils/reloadSource.js';
144
145
  import { installSpaFallback, repinSpaFallback, } from '../utils/spaFallback.js';
145
146
  import { LincdAPI } from './LincdAPI.js';
@@ -415,22 +416,53 @@ let LinkedServer = class LinkedServer extends Shape {
415
416
  * changed the app's shape code, so the app — the only process that loads that
416
417
  * code — writes its own shapes and their hashes.
417
418
  *
418
- * `packages` names the packages whose source changed. In dev (Vite) their
419
- * modules are invalidated and their backends reloaded first
420
- * ({@link onSourceChange}), so a shape added since the boot is registered
421
- * before the sync; without Vite (a built app) the sync covers the shapes the
422
- * process loaded. Runs as the system, never as the caller of a request.
419
+ * `packages` names the packages whose source changed: the app itself or its
420
+ * direct dependencies, never `@_linked/*` ({@link assertReloadablePackages}). In
421
+ * dev (Vite) their modules are invalidated and their backends reloaded first
422
+ * ({@link onSourceChange}), so a shape added since the boot is registered before
423
+ * the sync; without Vite (a built app) nothing is reloaded. The answer names the
424
+ * packages reloaded and the node shapes registered after it, so the host can check
425
+ * that the shape it wrote is there. Runs as the system, never as the caller of a
426
+ * request, one at a time; a run that takes longer than
427
+ * `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120 s) fails and releases the queue.
423
428
  */
424
429
  async resyncShapes({ packages = [] } = {}) {
425
- const run = this.shapeSyncQueue.then(() => runAsSystem(async () => {
426
- for (const pkg of packages)
427
- await this.reloadPackageFromDisk(pkg);
428
- return this.syncRegisteredShapes();
429
- }, 'shape sync requested by the host'));
430
+ assertReloadablePackages(packages, this.reloadablePackages());
431
+ const timeoutMs = envMs('LINKED_SHAPE_SYNC_TIMEOUT_MS', 120000);
432
+ const run = this.shapeSyncQueue.then(() => withTimeout(runAsSystem(async () => {
433
+ const reloaded = [];
434
+ for (const pkg of packages) {
435
+ if (await this.reloadPackageFromDisk(pkg))
436
+ reloaded.push(pkg);
437
+ }
438
+ const result = await this.syncRegisteredShapes();
439
+ return { ...result, reloaded, shapes: [...getAllShapeClasses().keys()].sort() };
440
+ }, 'shape sync requested by the host'), timeoutMs, `the shape sync did not finish within ${timeoutMs} ms`));
430
441
  this.shapeSyncQueue = run.catch(() => undefined);
431
442
  return run;
432
443
  }
433
- /** Re-read one package's source from disk (dev only); see {@link resyncShapes}. */
444
+ /** The packages a host may ask to reload: the app itself and its direct dependencies. */
445
+ reloadablePackages() {
446
+ var _a, _b;
447
+ const names = new Set();
448
+ if ((_a = this.package) === null || _a === void 0 ? void 0 : _a.name)
449
+ names.add(this.package.name);
450
+ try {
451
+ const json = JSON.parse(fsNative.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
452
+ if (json.name)
453
+ names.add(json.name);
454
+ for (const field of ['dependencies', 'devDependencies']) {
455
+ for (const name of Object.keys((_b = json[field]) !== null && _b !== void 0 ? _b : {}))
456
+ names.add(name);
457
+ }
458
+ }
459
+ catch (_c) { }
460
+ return names;
461
+ }
462
+ /**
463
+ * Re-read one package's source from disk (dev only); see {@link resyncShapes}.
464
+ * Resolves whether it reloaded anything.
465
+ */
434
466
  async reloadPackageFromDisk(pkg) {
435
467
  var _a, _b, _c;
436
468
  const root = pkg === ((_a = this.package) === null || _a === void 0 ? void 0 : _a.name) ? process.cwd() : this.packageDirFor(pkg);
@@ -438,7 +470,7 @@ let LinkedServer = class LinkedServer extends Shape {
438
470
  throw new AdminRequestError(`unknown package: ${pkg}`);
439
471
  const graph = (_c = (_b = this.config.server) === null || _b === void 0 ? void 0 : _b.vite) === null || _c === void 0 ? void 0 : _c.moduleGraph;
440
472
  if (!graph)
441
- return; // a built app: there is no source to reload
473
+ return false; // a built app: there is no source to reload
442
474
  const roots = [root];
443
475
  try {
444
476
  roots.push(fsNative.realpathSync(root));
@@ -446,6 +478,7 @@ let LinkedServer = class LinkedServer extends Shape {
446
478
  catch (_d) { }
447
479
  invalidateSourceUnder(graph, roots);
448
480
  await this.onSourceChange(pkg);
481
+ return true;
449
482
  }
450
483
  // async serveData(req,res) {
451
484
  // let nodeURI = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
@@ -1416,13 +1449,15 @@ let LinkedServer = class LinkedServer extends Shape {
1416
1449
  * don't accumulate), drops them from the registry, and re-runs
1417
1450
  * indexPackageBackendProviders to pick up the freshly-imported module.
1418
1451
  * Reloads run one at a time: each re-registers routes on the shared express
1419
- * stack.
1452
+ * stack. One that takes longer than `LINKED_SOURCE_RELOAD_TIMEOUT_MS` (default
1453
+ * 60 s) rejects and releases the queue.
1420
1454
  *
1421
1455
  * Dispose calls have a 5 s soft timeout. A hanging dispose logs a
1422
1456
  * warning and is abandoned so HMR doesn't stall the whole dev loop.
1423
1457
  */
1424
1458
  async onSourceChange(pkg) {
1425
- const run = this.sourceReloadQueue.then(() => this.reloadPackageSource(pkg));
1459
+ const timeoutMs = envMs('LINKED_SOURCE_RELOAD_TIMEOUT_MS', 60000);
1460
+ const run = this.sourceReloadQueue.then(() => withTimeout(this.reloadPackageSource(pkg), timeoutMs, `the reload of ${pkg} did not finish within ${timeoutMs} ms`));
1426
1461
  this.sourceReloadQueue = run.catch(() => undefined);
1427
1462
  return run;
1428
1463
  }