@_linked/server 3.13.0 → 3.14.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/lib/esm/backend.d.ts +23 -5
  3. package/lib/esm/backend.js +116 -25
  4. package/lib/esm/backend.js.map +1 -1
  5. package/lib/esm/shapes/LincdAPI.d.ts +1 -1
  6. package/lib/esm/shapes/LincdAPI.js +43 -7
  7. package/lib/esm/shapes/LincdAPI.js.map +1 -1
  8. package/lib/esm/shapes/LinkedServer.d.ts +50 -37
  9. package/lib/esm/shapes/LinkedServer.js +121 -76
  10. package/lib/esm/shapes/LinkedServer.js.map +1 -1
  11. package/lib/esm/shapes/quadstores/RemoteDataset.d.ts +35 -6
  12. package/lib/esm/shapes/quadstores/RemoteDataset.js +77 -12
  13. package/lib/esm/shapes/quadstores/RemoteDataset.js.map +1 -1
  14. package/lib/esm/utils/adminChannel.d.ts +22 -51
  15. package/lib/esm/utils/adminChannel.js +48 -36
  16. package/lib/esm/utils/adminChannel.js.map +1 -1
  17. package/lib/esm/utils/effectiveRoutes.d.ts +6 -0
  18. package/lib/esm/utils/effectiveRoutes.js +2 -1
  19. package/lib/esm/utils/effectiveRoutes.js.map +1 -1
  20. package/lib/esm/utils/forwardedTransactions.d.ts +46 -0
  21. package/lib/esm/utils/forwardedTransactions.js +155 -0
  22. package/lib/esm/utils/forwardedTransactions.js.map +1 -0
  23. package/lib/esm/utils/graphTargets.d.ts +37 -20
  24. package/lib/esm/utils/graphTargets.js +61 -82
  25. package/lib/esm/utils/graphTargets.js.map +1 -1
  26. package/lib/esm/utils/ontologySync.d.ts +40 -23
  27. package/lib/esm/utils/ontologySync.js +59 -37
  28. package/lib/esm/utils/ontologySync.js.map +1 -1
  29. package/lib/esm/utils/queryPlane.d.ts +13 -0
  30. package/lib/esm/utils/queryPlane.js +49 -2
  31. package/lib/esm/utils/queryPlane.js.map +1 -1
  32. package/lib/esm/utils/reloadSource.js +1 -1
  33. package/lib/esm/utils/reloadSource.js.map +1 -1
  34. package/lib/esm/utils/serverTransactions.d.ts +22 -0
  35. package/lib/esm/utils/serverTransactions.js +81 -0
  36. package/lib/esm/utils/serverTransactions.js.map +1 -0
  37. package/lib/esm/utils/syncShapes.d.ts +120 -8
  38. package/lib/esm/utils/syncShapes.js +151 -32
  39. package/lib/esm/utils/syncShapes.js.map +1 -1
  40. package/package.json +2 -2
  41. package/readme.md +134 -24
@@ -15,7 +15,7 @@ import { Shape } from '@_linked/core/shapes/Shape';
15
15
  * needs; a plain function, an arrow function or a non-function value is not.
16
16
  */
17
17
  export declare function isProviderClass(value: unknown): boolean;
18
- import { type ShapeSyncResult } from '../utils/adminChannel.js';
18
+ import { type GraphContentAnswer } from '../utils/adminChannel.js';
19
19
  /**
20
20
  * Serve the local upload folder at `/uploads` (where `LocalFileStore` files are
21
21
  * reachable), with the headers for untrusted files.
@@ -108,12 +108,14 @@ export declare class LinkedServer extends Shape {
108
108
  private initOnlyBoot;
109
109
  /**
110
110
  * Sync this server's registered shapes into app data, one named graph per package (see
111
- * utils/syncShapes): each package's shapes in `{package IRI}/graph/shapes`, from its built
111
+ * utils/syncShapes) in every dataset the storage routing needs it in (core's
112
+ * `syncShapeGraphsRouted`; the default dataset when nothing is pinned, and a graph routing no
113
+ * longer sends to the default dataset is dropped there): each package's shapes in
114
+ * `{package IRI}/graph/shapes`, from its built
112
115
  * `shapes.ttl` (an installed package without one gets no shape graph), the app's own packages
113
116
  * generated in process in dev (through Vite's loader), and the app's generated single-owner
114
- * shapes in `{DATA_ROOT}/graph/shapes`. Runs at boot, after the ontology sync, and again when
115
- * the host asks for it over the control channel ({@link resyncShapes}). A source reload (HMR)
116
- * does not sync shapes by itself.
117
+ * shapes in `{DATA_ROOT}/graph/shapes`. Runs at boot, after the ontology sync. A source reload
118
+ * (HMR) does not sync shapes.
117
119
  *
118
120
  * A graph whose stored content hash matches is not written, so a sync of unchanged shapes
119
121
  * writes nothing. Shapes an earlier sync wrote into the default graph are removed there once
@@ -123,62 +125,73 @@ export declare class LinkedServer extends Shape {
123
125
  * Shape graphs of packages no longer registered stay unless the operator sets
124
126
  * `LINKED_SYNC_SHAPES_PRUNE_ORPHANS=true` — see `bootOrphanScope` in utils/syncShapes.
125
127
  *
126
- * Opt-out via `linked.config` `syncShapesOnBoot` (default true); env
127
- * LINKED_SYNC_SHAPES_ON_BOOT overrides for deployment. A host whose default
128
- * dataset is a context router (one that can't write context-free) sets
129
- * `syncShapesOnBoot: false` and syncs its shapes itself.
128
+ * Runs when {@link bootSyncs}: the app writes its own graphs and `linked.config` does not set
129
+ * `syncShapesOnBoot: false`. A host that writes the app's graphs (`LINKED_GRAPH_WRITER=host`)
130
+ * reads their content through `POST /admin/graph-content` ({@link graphContent}).
130
131
  */
131
132
  private materializeShapesIntoStore;
132
- /** One shape sync into the default dataset; throws when it fails. */
133
+ /**
134
+ * Whether the boot syncs the shapes and the ontologies: the app writes its own graphs
135
+ * (`LINKED_GRAPH_WRITER` is `app`) and `linked.config` does not set `syncShapesOnBoot: false`.
136
+ */
137
+ private bootSyncs;
138
+ /** One shape sync, following the routing; throws when it fails in any dataset. */
133
139
  private syncRegisteredShapes;
134
140
  /** The root directory of a package with shapes: the app, a discovered linked package, or one the app resolves. */
135
141
  private shapePackageDir;
136
142
  /**
137
- * Boot: one warning when routing pins the shape shapes or the ontology term shapes to a dataset
138
- * other than the default one, since the syncs write into the default dataset only (see
139
- * {@link syncPinWarning}). Only for the syncs this boot runs.
143
+ * Boot: one line naming the datasets the boot syncs write into, when the routing sends any graph
144
+ * beyond the default dataset (see {@link syncTargetsLine}). Only for the syncs this boot runs.
140
145
  */
141
- private warnOfSyncPins;
146
+ private logSyncTargets;
142
147
  /**
143
148
  * Boot: sync the registered ontologies into their graphs in app data and run the shape
144
149
  * check, before the shape sync (see utils/ontologySync). The sync follows the shape sync's
145
- * switch (`syncShapesOnBoot`, `LINKED_SYNC_SHAPES_ON_BOOT`), plus the opt-out
146
- * `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`; the in-memory class hierarchy is loaded either way.
150
+ * switch ({@link bootSyncs}); the in-memory class hierarchy is loaded either way.
147
151
  * Never stops the boot: failures and findings are logged.
148
152
  */
149
153
  private syncOntologiesOnBoot;
150
154
  /**
151
155
  * Every discovered linked package and every workspace package of the app registers its
152
156
  * ontologies (in dev, an unbuilt one through Vite), then the ontology sync (when
153
- * `sync` is set) into the default dataset, then the shape check.
157
+ * `sync` is set), following the routing, then the shape check.
154
158
  * Orphaned ontology graphs (and earlier sync nodes) are pruned under the shape sync's orphan policy
155
159
  * (`LINKED_SYNC_SHAPES_PRUNE_ORPHANS`).
156
160
  */
157
161
  private runOntologyPhase;
158
- /** Tail of the {@link resyncShapes} queue: one re-sync at a time. */
162
+ /** Tail of the graph content's queue: one request at a time. */
159
163
  private shapeSyncQueue;
160
164
  /**
161
- * Re-sync the registered ontologies and shapes into app data, as the boot does,
162
- * and report what was written: the ontology sync (every registered package,
163
- * hash-skipped), the shape check's counts, then the shape sync. A failed ontology
164
- * sync does not stop the shape sync; the run then fails with the ontology error. A shape
165
- * check that throws fails nothing: the answer reports it (`shapeCheck.failed`). The host calls it (`POST /admin/sync-shapes`) after it
166
- * changed the app's shape code, so the app — the only process that loads that
167
- * code — writes its own shapes and their hashes.
165
+ * What the app's `'package'`-mode syncs would write, writing nothing (`POST /admin/graph-content`):
166
+ * per package its shape graph's Turtle and its ontology graph's N-Triples with the hash each
167
+ * record gets and its sources, and the app's own shape graph (`null` when it has no single-owner
168
+ * shapes; left out without `DATA_ROOT`). A host that writes the app's graphs itself
169
+ * (`LINKED_GRAPH_WRITER=host`) reads them here, after it changed the app's shape code: the app
170
+ * is the only process that loads that code.
168
171
  *
169
- * `packages` names the packages whose source changed: the app itself or its
170
- * direct dependencies (its `linked.unpublishedPackages` among them), never `@_linked/*` ({@link assertReloadablePackages}). In
171
- * dev (Vite) their modules are invalidated and their backends reloaded first
172
- * ({@link onSourceChange}), so a shape added since the boot is registered before
173
- * the sync; without Vite (a built app) nothing is reloaded. The answer names the
174
- * packages reloaded and the node shapes registered after it, so the host can check
175
- * that the shape it wrote is there. Runs as the system, never as the caller of a
176
- * request, one at a time; a run that takes longer than
172
+ * `reload` names the packages whose source changed: the app itself or its direct dependencies
173
+ * (its `linked.unpublishedPackages` among them), never `@_linked/*`
174
+ * ({@link assertReloadablePackages}). In dev (Vite) their modules are invalidated and their
175
+ * backends reloaded first ({@link onSourceChange}), then the ontologies are registered again and
176
+ * the shape check runs, so a shape added since the boot is in the answer; without Vite (a built
177
+ * app) nothing is reloaded. `reloaded` names the packages reloaded. `packages` limits the
178
+ * answer. Each package's Turtle comes from where the shape sync takes it (an installed package
179
+ * without `shapes.ttl` has none: `withoutTurtle`).
180
+ * Refused with {@link AdminConflictError} unless both graph targets are `'package'`. Runs one at
181
+ * a time, as the system, never as the caller of a request; a run that takes longer than
177
182
  * `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120 s) fails and releases the queue.
178
183
  */
179
- resyncShapes({ packages }?: {
184
+ graphContent({ packages, reload }?: {
180
185
  packages?: string[];
181
- }): Promise<ShapeSyncResult>;
186
+ reload?: string[];
187
+ }): Promise<GraphContentAnswer>;
188
+ /** Reload each of `packages` from disk (dev only); the ones actually reloaded. */
189
+ private reloadPackages;
190
+ /**
191
+ * Run `fn` in the graph content's queue (one at a time), as the system, failing it after
192
+ * `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120 s) and releasing the queue.
193
+ */
194
+ private inShapeSyncQueue;
182
195
  /**
183
196
  * The packages a host may ask to reload: the app itself and its direct dependencies, which
184
197
  * include every package in its `linked.unpublishedPackages`, transitively
@@ -187,7 +200,7 @@ export declare class LinkedServer extends Shape {
187
200
  */
188
201
  private reloadablePackages;
189
202
  /**
190
- * Re-read one package's source from disk (dev only); see {@link resyncShapes}.
203
+ * Re-read one package's source from disk (dev only); see {@link graphContent}.
191
204
  * Resolves whether it reloaded anything.
192
205
  */
193
206
  private reloadPackageFromDisk;
@@ -298,7 +311,7 @@ export declare class LinkedServer extends Shape {
298
311
  * Reload one package's backend providers from source.
299
312
  *
300
313
  * Called by the CLI orchestrator's Vite watcher whenever a `.ts`/`.tsx`
301
- * file inside a workspace package changes, and by {@link resyncShapes}.
314
+ * file inside a workspace package changes, and by {@link graphContent}.
302
315
  * Disposes the package's existing providers (so routes, listeners, timers
303
316
  * don't accumulate), drops them from the registry, and re-runs
304
317
  * indexPackageBackendProviders to pick up the freshly-imported module.
@@ -36,7 +36,7 @@ import { paint } from '../utils/paint.js';
36
36
  import { nativeImport } from '../utils/nativeImport.js';
37
37
  import { createJsonBodyParser } from '../utils/jsonBodyParser.js';
38
38
  import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
39
- import { getAllShapeClasses, getShapeClass, getSuperShapesClasses, } from '@_linked/core/utils/ShapeClass';
39
+ import { getShapeClass, getSuperShapesClasses, } from '@_linked/core/utils/ShapeClass';
40
40
  import path from 'path';
41
41
  import { pathToFileURL } from 'node:url';
42
42
  import { FatalStartupError, isFatalError } from '../utils/fatalError.js';
@@ -137,10 +137,11 @@ import { resolveStaticAccessURL, staticAssetURL, } from '../utils/releaseManifes
137
137
  import { resolveBootstrapEntry, resolveViteMainEntry, resolveViteServingMode, } from '../utils/bootstrapEntry.js';
138
138
  import { resolveRouteAssets } from '../utils/routeAssets.js';
139
139
  import { resolveRouteComponent, ssrCssInlineUrls, ssrCssScope, } from '../utils/ssrCss.js';
140
- import { appShapesGraph, bootOrphanScope, loadShapeGraphCore, syncShapeGraphsInto, viteShapesTurtleGenerator, } from '../utils/syncShapes.js';
141
- import { bootSyncsOntologies, loadOntologyCore, runOntologyPhase, ontologyPackageDirs, sourceModuleImporter, shapeSyncOn, syncOntologiesThenShapes, } from '../utils/ontologySync.js';
142
- import { applyGraphTargetsFromEnv, syncPinWarning } from '../utils/graphTargets.js';
143
- import { AdminRequestError, assertReloadablePackages, registerAdminRoutes, resolveAdminSecret, } from '../utils/adminChannel.js';
140
+ import { appShapesGraph, bootOrphanScope, loadShapeGraphCore, graphContentOf, syncRoutedShapeGraphs, viteShapesTurtleGenerator, } from '../utils/syncShapes.js';
141
+ import { loadOntologyCore, runOntologyPhase, ontologyPackageDirs, sourceModuleImporter, shapeSyncOn, } from '../utils/ontologySync.js';
142
+ import { getGraphTargets } from '@_linked/core/utils/graphTargets';
143
+ import { applyGraphTargetsFromEnv, appWritesGraphs, GRAPH_WRITER_ENV, graphWriterFromEnv, syncTargetsLine } from '../utils/graphTargets.js';
144
+ import { AdminRequestError, assertReloadablePackages, AdminConflictError, registerAdminRoutes, resolveAdminSecret, } from '../utils/adminChannel.js';
144
145
  import { envMs, withTimeout } from '../utils/withTimeout.js';
145
146
  import { invalidateSourceUnder } from '../utils/reloadSource.js';
146
147
  import { installSpaFallback, repinSpaFallback, } from '../utils/spaFallback.js';
@@ -298,7 +299,7 @@ let LinkedServer = class LinkedServer extends Shape {
298
299
  * error is logged and contained.
299
300
  */
300
301
  this.booting = false;
301
- /** Tail of the {@link resyncShapes} queue: one re-sync at a time. */
302
+ /** Tail of the graph content's queue: one request at a time. */
302
303
  this.shapeSyncQueue = Promise.resolve();
303
304
  /** Tail of the {@link onSourceChange} queue: reloads never interleave. */
304
305
  this.sourceReloadQueue = Promise.resolve();
@@ -364,19 +365,21 @@ let LinkedServer = class LinkedServer extends Shape {
364
365
  this.initPackage();
365
366
  this.server = express();
366
367
  await this.initBackendProviders();
367
- this.warnOfSyncPins();
368
+ await this.logSyncTargets();
368
369
  await this.syncOntologiesOnBoot();
369
370
  await this.materializeShapesIntoStore();
370
371
  return this;
371
372
  }
372
373
  /**
373
374
  * Sync this server's registered shapes into app data, one named graph per package (see
374
- * utils/syncShapes): each package's shapes in `{package IRI}/graph/shapes`, from its built
375
+ * utils/syncShapes) in every dataset the storage routing needs it in (core's
376
+ * `syncShapeGraphsRouted`; the default dataset when nothing is pinned, and a graph routing no
377
+ * longer sends to the default dataset is dropped there): each package's shapes in
378
+ * `{package IRI}/graph/shapes`, from its built
375
379
  * `shapes.ttl` (an installed package without one gets no shape graph), the app's own packages
376
380
  * generated in process in dev (through Vite's loader), and the app's generated single-owner
377
- * shapes in `{DATA_ROOT}/graph/shapes`. Runs at boot, after the ontology sync, and again when
378
- * the host asks for it over the control channel ({@link resyncShapes}). A source reload (HMR)
379
- * does not sync shapes by itself.
381
+ * shapes in `{DATA_ROOT}/graph/shapes`. Runs at boot, after the ontology sync. A source reload
382
+ * (HMR) does not sync shapes.
380
383
  *
381
384
  * A graph whose stored content hash matches is not written, so a sync of unchanged shapes
382
385
  * writes nothing. Shapes an earlier sync wrote into the default graph are removed there once
@@ -386,10 +389,9 @@ let LinkedServer = class LinkedServer extends Shape {
386
389
  * Shape graphs of packages no longer registered stay unless the operator sets
387
390
  * `LINKED_SYNC_SHAPES_PRUNE_ORPHANS=true` — see `bootOrphanScope` in utils/syncShapes.
388
391
  *
389
- * Opt-out via `linked.config` `syncShapesOnBoot` (default true); env
390
- * LINKED_SYNC_SHAPES_ON_BOOT overrides for deployment. A host whose default
391
- * dataset is a context router (one that can't write context-free) sets
392
- * `syncShapesOnBoot: false` and syncs its shapes itself.
392
+ * Runs when {@link bootSyncs}: the app writes its own graphs and `linked.config` does not set
393
+ * `syncShapesOnBoot: false`. A host that writes the app's graphs (`LINKED_GRAPH_WRITER=host`)
394
+ * reads their content through `POST /admin/graph-content` ({@link graphContent}).
393
395
  */
394
396
  async materializeShapesIntoStore() {
395
397
  try {
@@ -399,22 +401,28 @@ let LinkedServer = class LinkedServer extends Shape {
399
401
  console.warn('[LinkedServer] shape sync failed (non-fatal):', err);
400
402
  }
401
403
  }
402
- /** One shape sync into the default dataset; throws when it fails. */
404
+ /**
405
+ * Whether the boot syncs the shapes and the ontologies: the app writes its own graphs
406
+ * (`LINKED_GRAPH_WRITER` is `app`) and `linked.config` does not set `syncShapesOnBoot: false`.
407
+ */
408
+ bootSyncs() {
409
+ var _a;
410
+ return appWritesGraphs(process.env) && shapeSyncOn((_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot);
411
+ }
412
+ /** One shape sync, following the routing; throws when it fails in any dataset. */
403
413
  async syncRegisteredShapes() {
404
- var _a, _b, _c, _d, _e;
405
- if (!shapeSyncOn(process.env, (_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot)) {
406
- return { written: 0, skipped: 'disabled' };
407
- }
408
- const appData = LinkedStorage.getDefaultDataset();
409
- if (!appData)
410
- return { written: 0, skipped: 'no-default-dataset' };
414
+ var _a, _b;
415
+ if (!this.bootSyncs())
416
+ return;
417
+ if (!LinkedStorage.getDefaultDataset())
418
+ return;
411
419
  const orphanScope = bootOrphanScope();
412
- const appName = (_b = this.package) === null || _b === void 0 ? void 0 : _b.name;
413
- const vite = (_c = this.config.server) === null || _c === void 0 ? void 0 : _c.vite;
420
+ const appName = (_a = this.package) === null || _a === void 0 ? void 0 : _a.name;
421
+ const vite = (_b = this.config.server) === null || _b === void 0 ? void 0 : _b.vite;
414
422
  const generate = viteShapesTurtleGenerator(vite);
415
- // Explicit target: every registered shape goes into the app's own data store, regardless of
416
- // per-shape routing or pins.
417
- const shapeGraphs = await syncShapeGraphsInto(appData, {
423
+ // Follows the routing: each package's shape graph goes to every dataset its shapes (and the
424
+ // shape shapes) route to; the default dataset is one of them unless routing sends all elsewhere.
425
+ await syncRoutedShapeGraphs({
418
426
  core: await loadShapeGraphCore(),
419
427
  orphanScope,
420
428
  isOwn: (name) => name === appName || this.workspacePackageNames.has(name),
@@ -424,8 +432,6 @@ let LinkedServer = class LinkedServer extends Shape {
424
432
  appGraph: appShapesGraph(),
425
433
  appPackage: appName,
426
434
  });
427
- const written = (_d = shapeGraphs.defaultGraph) !== null && _d !== void 0 ? _d : shapeGraphs.written.length + (((_e = shapeGraphs.appGraph) === null || _e === void 0 ? void 0 : _e.status) === 'written' ? 1 : 0);
428
- return { written, orphanScope, shapeGraphs };
429
435
  }
430
436
  /** The root directory of a package with shapes: the app, a discovered linked package, or one the app resolves. */
431
437
  shapePackageDir(name) {
@@ -439,30 +445,34 @@ let LinkedServer = class LinkedServer extends Shape {
439
445
  return installed === null || installed === void 0 ? void 0 : installed.root;
440
446
  }
441
447
  /**
442
- * Boot: one warning when routing pins the shape shapes or the ontology term shapes to a dataset
443
- * other than the default one, since the syncs write into the default dataset only (see
444
- * {@link syncPinWarning}). Only for the syncs this boot runs.
448
+ * Boot: one line naming the datasets the boot syncs write into, when the routing sends any graph
449
+ * beyond the default dataset (see {@link syncTargetsLine}). Only for the syncs this boot runs.
445
450
  */
446
- warnOfSyncPins() {
447
- var _a;
448
- const flag = (_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot;
449
- const warning = syncPinWarning({
450
- shapeSync: shapeSyncOn(process.env, flag),
451
- ontologySync: bootSyncsOntologies(process.env, flag),
452
- });
453
- if (warning)
454
- console.warn(warning);
451
+ async logSyncTargets() {
452
+ if (!appWritesGraphs(process.env)) {
453
+ console.log(`[LinkedServer] ${GRAPH_WRITER_ENV}=host: the host writes this app's shape and ontology graphs; ` +
454
+ 'the boot writes none (the host reads them through POST /admin/graph-content).');
455
+ return;
456
+ }
457
+ try {
458
+ const on = this.bootSyncs();
459
+ const line = syncTargetsLine({ shapeSync: on, ontologySync: on }, await import('@_linked/core/shapes/syncTargets'));
460
+ if (line)
461
+ console.log(line);
462
+ }
463
+ catch (err) {
464
+ console.warn('[LinkedServer] could not name the datasets the boot syncs write into:', err);
465
+ }
455
466
  }
456
467
  /**
457
468
  * Boot: sync the registered ontologies into their graphs in app data and run the shape
458
469
  * check, before the shape sync (see utils/ontologySync). The sync follows the shape sync's
459
- * switch (`syncShapesOnBoot`, `LINKED_SYNC_SHAPES_ON_BOOT`), plus the opt-out
460
- * `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`; the in-memory class hierarchy is loaded either way.
470
+ * switch ({@link bootSyncs}); the in-memory class hierarchy is loaded either way.
461
471
  * Never stops the boot: failures and findings are logged.
462
472
  */
463
473
  async syncOntologiesOnBoot() {
464
474
  try {
465
- await runAsSystem(() => { var _a; return this.runOntologyPhase(bootSyncsOntologies(process.env, (_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot)); }, 'ontology sync at boot');
475
+ await runAsSystem(() => this.runOntologyPhase(this.bootSyncs()), 'ontology sync at boot');
466
476
  }
467
477
  catch (err) {
468
478
  console.warn('[LinkedServer] ontology sync failed (non-fatal):', err);
@@ -471,7 +481,7 @@ let LinkedServer = class LinkedServer extends Shape {
471
481
  /**
472
482
  * Every discovered linked package and every workspace package of the app registers its
473
483
  * ontologies (in dev, an unbuilt one through Vite), then the ontology sync (when
474
- * `sync` is set) into the default dataset, then the shape check.
484
+ * `sync` is set), following the routing, then the shape check.
475
485
  * Orphaned ontology graphs (and earlier sync nodes) are pruned under the shape sync's orphan policy
476
486
  * (`LINKED_SYNC_SHAPES_PRUNE_ORPHANS`).
477
487
  */
@@ -480,6 +490,7 @@ let LinkedServer = class LinkedServer extends Shape {
480
490
  return runOntologyPhase(LinkedStorage.getDefaultDataset(), {
481
491
  core: await loadOntologyCore(),
482
492
  sync,
493
+ routed: true,
483
494
  orphanScope: bootOrphanScope(),
484
495
  dependencies: readDependencyLists((_a = this.linkedPackageDirs) !== null && _a !== void 0 ? _a : new Map(), this.package),
485
496
  packageDirs: ontologyPackageDirs(((_b = this.linkedPackageDirs) !== null && _b !== void 0 ? _b : new Map()).values(), (_c = this.workspacePackageDirs) !== null && _c !== void 0 ? _c : []),
@@ -487,36 +498,68 @@ let LinkedServer = class LinkedServer extends Shape {
487
498
  });
488
499
  }
489
500
  /**
490
- * Re-sync the registered ontologies and shapes into app data, as the boot does,
491
- * and report what was written: the ontology sync (every registered package,
492
- * hash-skipped), the shape check's counts, then the shape sync. A failed ontology
493
- * sync does not stop the shape sync; the run then fails with the ontology error. A shape
494
- * check that throws fails nothing: the answer reports it (`shapeCheck.failed`). The host calls it (`POST /admin/sync-shapes`) after it
495
- * changed the app's shape code, so the app — the only process that loads that
496
- * code — writes its own shapes and their hashes.
501
+ * What the app's `'package'`-mode syncs would write, writing nothing (`POST /admin/graph-content`):
502
+ * per package its shape graph's Turtle and its ontology graph's N-Triples with the hash each
503
+ * record gets and its sources, and the app's own shape graph (`null` when it has no single-owner
504
+ * shapes; left out without `DATA_ROOT`). A host that writes the app's graphs itself
505
+ * (`LINKED_GRAPH_WRITER=host`) reads them here, after it changed the app's shape code: the app
506
+ * is the only process that loads that code.
497
507
  *
498
- * `packages` names the packages whose source changed: the app itself or its
499
- * direct dependencies (its `linked.unpublishedPackages` among them), never `@_linked/*` ({@link assertReloadablePackages}). In
500
- * dev (Vite) their modules are invalidated and their backends reloaded first
501
- * ({@link onSourceChange}), so a shape added since the boot is registered before
502
- * the sync; without Vite (a built app) nothing is reloaded. The answer names the
503
- * packages reloaded and the node shapes registered after it, so the host can check
504
- * that the shape it wrote is there. Runs as the system, never as the caller of a
505
- * request, one at a time; a run that takes longer than
508
+ * `reload` names the packages whose source changed: the app itself or its direct dependencies
509
+ * (its `linked.unpublishedPackages` among them), never `@_linked/*`
510
+ * ({@link assertReloadablePackages}). In dev (Vite) their modules are invalidated and their
511
+ * backends reloaded first ({@link onSourceChange}), then the ontologies are registered again and
512
+ * the shape check runs, so a shape added since the boot is in the answer; without Vite (a built
513
+ * app) nothing is reloaded. `reloaded` names the packages reloaded. `packages` limits the
514
+ * answer. Each package's Turtle comes from where the shape sync takes it (an installed package
515
+ * without `shapes.ttl` has none: `withoutTurtle`).
516
+ * Refused with {@link AdminConflictError} unless both graph targets are `'package'`. Runs one at
517
+ * a time, as the system, never as the caller of a request; a run that takes longer than
506
518
  * `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120 s) fails and releases the queue.
507
519
  */
508
- async resyncShapes({ packages = [] } = {}) {
509
- assertReloadablePackages(packages, this.reloadablePackages());
510
- const timeoutMs = envMs('LINKED_SHAPE_SYNC_TIMEOUT_MS', 120000);
511
- const run = this.shapeSyncQueue.then(() => withTimeout(runAsSystem(async () => {
512
- const reloaded = [];
513
- for (const pkg of packages) {
514
- if (await this.reloadPackageFromDisk(pkg))
515
- reloaded.push(pkg);
520
+ async graphContent({ packages, reload = [] } = {}) {
521
+ assertReloadablePackages(reload, this.reloadablePackages());
522
+ const graphTargets = getGraphTargets();
523
+ for (const [setting, env] of [['ontologyGraph', 'LINKED_ONTOLOGY_GRAPH'], ['shapeGraph', 'LINKED_SHAPE_GRAPH']]) {
524
+ if (graphTargets[setting] !== 'package') {
525
+ throw new AdminConflictError(`${setting} is ${JSON.stringify(graphTargets[setting])} (${env}); the graph content is what the 'package' mode writes, one graph per package`);
516
526
  }
517
- const result = await syncOntologiesThenShapes(() => { var _a; return this.runOntologyPhase(shapeSyncOn(process.env, (_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot)); }, () => this.syncRegisteredShapes());
518
- return { ...result, reloaded, shapes: [...getAllShapeClasses().keys()].sort() };
519
- }, 'shape sync requested by the host'), timeoutMs, `the shape sync did not finish within ${timeoutMs} ms`));
527
+ }
528
+ return this.inShapeSyncQueue('the graph content', 'graph content requested by the host', async () => {
529
+ var _a, _b;
530
+ const reloaded = await this.reloadPackages(reload);
531
+ if (reloaded.length)
532
+ await this.runOntologyPhase(false);
533
+ const appName = (_a = this.package) === null || _a === void 0 ? void 0 : _a.name;
534
+ const generate = viteShapesTurtleGenerator((_b = this.config.server) === null || _b === void 0 ? void 0 : _b.vite);
535
+ const content = await graphContentOf({
536
+ core: await loadShapeGraphCore(),
537
+ isOwn: (name) => name === appName || this.workspacePackageNames.has(name),
538
+ dev: !!generate,
539
+ generate,
540
+ packageDirOf: (name) => this.shapePackageDir(name),
541
+ appGraph: appShapesGraph(),
542
+ ...(packages ? { packages } : {}),
543
+ });
544
+ return { graphTargets, ...content, reloaded };
545
+ });
546
+ }
547
+ /** Reload each of `packages` from disk (dev only); the ones actually reloaded. */
548
+ async reloadPackages(packages) {
549
+ const reloaded = [];
550
+ for (const pkg of packages) {
551
+ if (await this.reloadPackageFromDisk(pkg))
552
+ reloaded.push(pkg);
553
+ }
554
+ return reloaded;
555
+ }
556
+ /**
557
+ * Run `fn` in the graph content's queue (one at a time), as the system, failing it after
558
+ * `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120 s) and releasing the queue.
559
+ */
560
+ inShapeSyncQueue(what, why, fn) {
561
+ const timeoutMs = envMs('LINKED_SHAPE_SYNC_TIMEOUT_MS', 120000);
562
+ const run = this.shapeSyncQueue.then(() => withTimeout(runAsSystem(fn, why), timeoutMs, `${what} did not finish within ${timeoutMs} ms`));
520
563
  this.shapeSyncQueue = run.catch(() => undefined);
521
564
  return run;
522
565
  }
@@ -549,7 +592,7 @@ let LinkedServer = class LinkedServer extends Shape {
549
592
  return names;
550
593
  }
551
594
  /**
552
- * Re-read one package's source from disk (dev only); see {@link resyncShapes}.
595
+ * Re-read one package's source from disk (dev only); see {@link graphContent}.
553
596
  * Resolves whether it reloaded anything.
554
597
  */
555
598
  async reloadPackageFromDisk(pkg) {
@@ -711,7 +754,7 @@ let LinkedServer = class LinkedServer extends Shape {
711
754
  // });
712
755
  // before controllers
713
756
  await this.initBackendProviders();
714
- this.warnOfSyncPins();
757
+ await this.logSyncTargets();
715
758
  await this.syncOntologiesOnBoot();
716
759
  await this.materializeShapesIntoStore();
717
760
  //START OF EXPRESS ROUTES AND MIDDLEWARE
@@ -785,7 +828,7 @@ let LinkedServer = class LinkedServer extends Shape {
785
828
  registerAdminRoutes(this.server, resolveAdminSecret(), {
786
829
  // The host's provisioner starts the app again after it exits.
787
830
  exit: () => process.exit(0),
788
- syncShapes: (request) => this.resyncShapes(request),
831
+ graphContent: (request) => this.graphContent(request),
789
832
  });
790
833
  // after controller
791
834
  await this.callGenericBackendProvidersMethod('setupAfterControllers');
@@ -839,8 +882,10 @@ let LinkedServer = class LinkedServer extends Shape {
839
882
  // LINKED_ONTOLOGY_GRAPH / LINKED_SHAPE_GRAPH: checked (an invalid value stops the boot) and set
840
883
  // once as core's global graph targets, before any store is initialised and before the syncs.
841
884
  // A rejected boot does not end the process by itself, so the error is fatal.
885
+ // LINKED_GRAPH_WRITER: checked here too, so an invalid value stops the boot before anything runs.
842
886
  try {
843
887
  applyGraphTargetsFromEnv(process.env);
888
+ graphWriterFromEnv(process.env);
844
889
  }
845
890
  catch (err) {
846
891
  throw new FatalStartupError(`[LinkedServer] ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`, { cause: err });
@@ -1558,7 +1603,7 @@ let LinkedServer = class LinkedServer extends Shape {
1558
1603
  * Reload one package's backend providers from source.
1559
1604
  *
1560
1605
  * Called by the CLI orchestrator's Vite watcher whenever a `.ts`/`.tsx`
1561
- * file inside a workspace package changes, and by {@link resyncShapes}.
1606
+ * file inside a workspace package changes, and by {@link graphContent}.
1562
1607
  * Disposes the package's existing providers (so routes, listeners, timers
1563
1608
  * don't accumulate), drops them from the registry, and re-runs
1564
1609
  * indexPackageBackendProviders to pick up the freshly-imported module.