@_linked/server 3.12.1 → 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 (44) hide show
  1. package/CHANGELOG.md +72 -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 +59 -39
  9. package/lib/esm/shapes/LinkedServer.js +158 -93
  10. package/lib/esm/shapes/LinkedServer.js.map +1 -1
  11. package/lib/esm/shapes/quadstores/RemoteDataset.d.ts +52 -20
  12. package/lib/esm/shapes/quadstores/RemoteDataset.js +134 -32
  13. package/lib/esm/shapes/quadstores/RemoteDataset.js.map +1 -1
  14. package/lib/esm/utils/adminChannel.d.ts +23 -51
  15. package/lib/esm/utils/adminChannel.js +50 -38
  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 +42 -17
  24. package/lib/esm/utils/graphTargets.js +62 -69
  25. package/lib/esm/utils/graphTargets.js.map +1 -1
  26. package/lib/esm/utils/linkedDependencies.d.ts +53 -7
  27. package/lib/esm/utils/linkedDependencies.js +130 -12
  28. package/lib/esm/utils/linkedDependencies.js.map +1 -1
  29. package/lib/esm/utils/ontologySync.d.ts +55 -31
  30. package/lib/esm/utils/ontologySync.js +94 -42
  31. package/lib/esm/utils/ontologySync.js.map +1 -1
  32. package/lib/esm/utils/queryPlane.d.ts +13 -0
  33. package/lib/esm/utils/queryPlane.js +49 -2
  34. package/lib/esm/utils/queryPlane.js.map +1 -1
  35. package/lib/esm/utils/reloadSource.js +1 -1
  36. package/lib/esm/utils/reloadSource.js.map +1 -1
  37. package/lib/esm/utils/serverTransactions.d.ts +22 -0
  38. package/lib/esm/utils/serverTransactions.js +81 -0
  39. package/lib/esm/utils/serverTransactions.js.map +1 -0
  40. package/lib/esm/utils/syncShapes.d.ts +123 -6
  41. package/lib/esm/utils/syncShapes.js +166 -35
  42. package/lib/esm/utils/syncShapes.js.map +1 -1
  43. package/package.json +4 -4
  44. package/readme.md +158 -38
@@ -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.
@@ -82,7 +82,7 @@ export declare class LinkedServer extends Shape {
82
82
  * could not be loaded at all.
83
83
  */
84
84
  private linkedPackageDirs;
85
- /** The app's own workspace packages (`linkedPackage: true`), by real path. */
85
+ /** The app's own workspace packages (`linked.kind` of a package), by real path. */
86
86
  private workspacePackageDirs;
87
87
  /** The names of the app's own workspace packages. */
88
88
  private workspacePackageNames;
@@ -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,65 +125,82 @@ 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}).
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. The host calls it (`POST /admin/sync-shapes`) after it
165
- * changed the app's shape code, so the app — the only process that loads that
166
- * 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.
167
171
  *
168
- * `packages` names the packages whose source changed: the app itself or its
169
- * direct dependencies, never `@_linked/*` ({@link assertReloadablePackages}). In
170
- * dev (Vite) their modules are invalidated and their backends reloaded first
171
- * ({@link onSourceChange}), so a shape added since the boot is registered before
172
- * the sync; without Vite (a built app) nothing is reloaded. The answer names the
173
- * packages reloaded and the node shapes registered after it, so the host can check
174
- * that the shape it wrote is there. Runs as the system, never as the caller of a
175
- * 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
176
182
  * `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120 s) fails and releases the queue.
177
183
  */
178
- resyncShapes({ packages }?: {
184
+ graphContent({ packages, reload }?: {
179
185
  packages?: string[];
180
- }): Promise<ShapeSyncResult>;
181
- /** The packages a host may ask to reload: the app itself and its direct dependencies. */
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;
195
+ /**
196
+ * The packages a host may ask to reload: the app itself and its direct dependencies, which
197
+ * include every package in its `linked.unpublishedPackages`, transitively
198
+ * ({@link listedPackageNames}). Read from disk on each call: a list written after the boot
199
+ * counts. A malformed list throws.
200
+ */
182
201
  private reloadablePackages;
183
202
  /**
184
- * 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}.
185
204
  * Resolves whether it reloaded anything.
186
205
  */
187
206
  private reloadPackageFromDisk;
@@ -270,7 +289,8 @@ export declare class LinkedServer extends Shape {
270
289
  protected importModule(specifier: string): Promise<any>;
271
290
  /**
272
291
  * Filters local workspace packages to only those reachable from the app's
273
- * dependency tree. Mirrors the logic used by `buildAll` in lincd-cli.
292
+ * dependency tree: `dependencies` and `linked.unpublishedPackages`, of the app
293
+ * and of each package reached. Mirrors the logic used by `buildAll` in the cli.
274
294
  */
275
295
  private filterPackagesByDependencyTree;
276
296
  resizeImage(req: any, res: any): Promise<void>;
@@ -291,7 +311,7 @@ export declare class LinkedServer extends Shape {
291
311
  * Reload one package's backend providers from source.
292
312
  *
293
313
  * Called by the CLI orchestrator's Vite watcher whenever a `.ts`/`.tsx`
294
- * file inside a workspace package changes, and by {@link resyncShapes}.
314
+ * file inside a workspace package changes, and by {@link graphContent}.
295
315
  * Disposes the package's existing providers (so routes, listeners, timers
296
316
  * don't accumulate), drops them from the registry, and re-runs
297
317
  * indexPackageBackendProviders to pick up the freshly-imported module.
@@ -36,11 +36,11 @@ 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';
43
- import { discoverLinkedDependencies, hasModuleEntry, isBundledBySsr, NODE_IMPORT_CONDITIONS, readDependencyLists, readInstalledPackage, resolveBackendEntry, resolveDevBackendEntry, ssrNoExternalOf, } from '../utils/linkedDependencies.js';
43
+ import { discoverLinkedDependencies, listedPackageNames, unpublishedNames, hasModuleEntry, isBundledBySsr, NODE_IMPORT_CONDITIONS, readDependencyLists, readInstalledPackage, resolveBackendEntry, resolveDevBackendEntry, ssrNoExternalOf, } from '../utils/linkedDependencies.js';
44
44
  /**
45
45
  * `instanceof ShapeProvider`, tolerating a second copy of server-utils.
46
46
  *
@@ -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';
@@ -285,7 +286,7 @@ let LinkedServer = class LinkedServer extends Shape {
285
286
  * could not be loaded at all.
286
287
  */
287
288
  this.linkedPackageDirs = new Map();
288
- /** The app's own workspace packages (`linkedPackage: true`), by real path. */
289
+ /** The app's own workspace packages (`linked.kind` of a package), by real path. */
289
290
  this.workspacePackageDirs = [];
290
291
  /** The names of the app's own workspace packages. */
291
292
  this.workspacePackageNames = new Set();
@@ -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,25 +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}).
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
- const warning = syncPinWarning();
448
- if (warning)
449
- 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
+ }
450
466
  }
451
467
  /**
452
468
  * Boot: sync the registered ontologies into their graphs in app data and run the shape
453
469
  * check, before the shape sync (see utils/ontologySync). The sync follows the shape sync's
454
- * switch (`syncShapesOnBoot`, `LINKED_SYNC_SHAPES_ON_BOOT`), plus the opt-out
455
- * `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.
456
471
  * Never stops the boot: failures and findings are logged.
457
472
  */
458
473
  async syncOntologiesOnBoot() {
459
474
  try {
460
- 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');
461
476
  }
462
477
  catch (err) {
463
478
  console.warn('[LinkedServer] ontology sync failed (non-fatal):', err);
@@ -466,7 +481,7 @@ let LinkedServer = class LinkedServer extends Shape {
466
481
  /**
467
482
  * Every discovered linked package and every workspace package of the app registers its
468
483
  * ontologies (in dev, an unbuilt one through Vite), then the ontology sync (when
469
- * `sync` is set) into the default dataset, then the shape check.
484
+ * `sync` is set), following the routing, then the shape check.
470
485
  * Orphaned ontology graphs (and earlier sync nodes) are pruned under the shape sync's orphan policy
471
486
  * (`LINKED_SYNC_SHAPES_PRUNE_ORPHANS`).
472
487
  */
@@ -475,6 +490,7 @@ let LinkedServer = class LinkedServer extends Shape {
475
490
  return runOntologyPhase(LinkedStorage.getDefaultDataset(), {
476
491
  core: await loadOntologyCore(),
477
492
  sync,
493
+ routed: true,
478
494
  orphanScope: bootOrphanScope(),
479
495
  dependencies: readDependencyLists((_a = this.linkedPackageDirs) !== null && _a !== void 0 ? _a : new Map(), this.package),
480
496
  packageDirs: ontologyPackageDirs(((_b = this.linkedPackageDirs) !== null && _b !== void 0 ? _b : new Map()).values(), (_c = this.workspacePackageDirs) !== null && _c !== void 0 ? _c : []),
@@ -482,58 +498,101 @@ let LinkedServer = class LinkedServer extends Shape {
482
498
  });
483
499
  }
484
500
  /**
485
- * Re-sync the registered ontologies and shapes into app data, as the boot does,
486
- * and report what was written: the ontology sync (every registered package,
487
- * hash-skipped), the shape check's counts, then the shape sync. A failed ontology
488
- * sync does not stop the shape sync; the run then fails with the ontology error. The host calls it (`POST /admin/sync-shapes`) after it
489
- * changed the app's shape code, so the app — the only process that loads that
490
- * 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.
491
507
  *
492
- * `packages` names the packages whose source changed: the app itself or its
493
- * direct dependencies, never `@_linked/*` ({@link assertReloadablePackages}). In
494
- * dev (Vite) their modules are invalidated and their backends reloaded first
495
- * ({@link onSourceChange}), so a shape added since the boot is registered before
496
- * the sync; without Vite (a built app) nothing is reloaded. The answer names the
497
- * packages reloaded and the node shapes registered after it, so the host can check
498
- * that the shape it wrote is there. Runs as the system, never as the caller of a
499
- * 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
500
518
  * `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120 s) fails and releases the queue.
501
519
  */
502
- async resyncShapes({ packages = [] } = {}) {
503
- assertReloadablePackages(packages, this.reloadablePackages());
504
- const timeoutMs = envMs('LINKED_SHAPE_SYNC_TIMEOUT_MS', 120000);
505
- const run = this.shapeSyncQueue.then(() => withTimeout(runAsSystem(async () => {
506
- const reloaded = [];
507
- for (const pkg of packages) {
508
- if (await this.reloadPackageFromDisk(pkg))
509
- 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`);
510
526
  }
511
- 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());
512
- return { ...result, reloaded, shapes: [...getAllShapeClasses().keys()].sort() };
513
- }, '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`));
514
563
  this.shapeSyncQueue = run.catch(() => undefined);
515
564
  return run;
516
565
  }
517
- /** The packages a host may ask to reload: the app itself and its direct dependencies. */
566
+ /**
567
+ * The packages a host may ask to reload: the app itself and its direct dependencies, which
568
+ * include every package in its `linked.unpublishedPackages`, transitively
569
+ * ({@link listedPackageNames}). Read from disk on each call: a list written after the boot
570
+ * counts. A malformed list throws.
571
+ */
518
572
  reloadablePackages() {
519
573
  var _a, _b;
520
574
  const names = new Set();
521
575
  if ((_a = this.package) === null || _a === void 0 ? void 0 : _a.name)
522
576
  names.add(this.package.name);
577
+ let json;
523
578
  try {
524
- const json = JSON.parse(fsNative.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
525
- if (json.name)
526
- names.add(json.name);
527
- for (const field of ['dependencies', 'devDependencies']) {
528
- for (const name of Object.keys((_b = json[field]) !== null && _b !== void 0 ? _b : {}))
529
- names.add(name);
530
- }
579
+ json = JSON.parse(fsNative.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
580
+ }
581
+ catch (_c) {
582
+ return names;
531
583
  }
532
- catch (_c) { }
584
+ if (json.name)
585
+ names.add(json.name);
586
+ for (const field of ['dependencies', 'devDependencies']) {
587
+ for (const name of Object.keys((_b = json[field]) !== null && _b !== void 0 ? _b : {}))
588
+ names.add(name);
589
+ }
590
+ for (const name of listedPackageNames(process.cwd(), json))
591
+ names.add(name);
533
592
  return names;
534
593
  }
535
594
  /**
536
- * 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}.
537
596
  * Resolves whether it reloaded anything.
538
597
  */
539
598
  async reloadPackageFromDisk(pkg) {
@@ -695,7 +754,7 @@ let LinkedServer = class LinkedServer extends Shape {
695
754
  // });
696
755
  // before controllers
697
756
  await this.initBackendProviders();
698
- this.warnOfSyncPins();
757
+ await this.logSyncTargets();
699
758
  await this.syncOntologiesOnBoot();
700
759
  await this.materializeShapesIntoStore();
701
760
  //START OF EXPRESS ROUTES AND MIDDLEWARE
@@ -769,7 +828,7 @@ let LinkedServer = class LinkedServer extends Shape {
769
828
  registerAdminRoutes(this.server, resolveAdminSecret(), {
770
829
  // The host's provisioner starts the app again after it exits.
771
830
  exit: () => process.exit(0),
772
- syncShapes: (request) => this.resyncShapes(request),
831
+ graphContent: (request) => this.graphContent(request),
773
832
  });
774
833
  // after controller
775
834
  await this.callGenericBackendProvidersMethod('setupAfterControllers');
@@ -823,8 +882,10 @@ let LinkedServer = class LinkedServer extends Shape {
823
882
  // LINKED_ONTOLOGY_GRAPH / LINKED_SHAPE_GRAPH: checked (an invalid value stops the boot) and set
824
883
  // once as core's global graph targets, before any store is initialised and before the syncs.
825
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.
826
886
  try {
827
887
  applyGraphTargetsFromEnv(process.env);
888
+ graphWriterFromEnv(process.env);
828
889
  }
829
890
  catch (err) {
830
891
  throw new FatalStartupError(`[LinkedServer] ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`, { cause: err });
@@ -1047,18 +1108,21 @@ let LinkedServer = class LinkedServer extends Shape {
1047
1108
  }
1048
1109
  /**
1049
1110
  * Filters local workspace packages to only those reachable from the app's
1050
- * dependency tree. Mirrors the logic used by `buildAll` in lincd-cli.
1111
+ * dependency tree: `dependencies` and `linked.unpublishedPackages`, of the app
1112
+ * and of each package reached. Mirrors the logic used by `buildAll` in the cli.
1051
1113
  */
1052
1114
  filterPackagesByDependencyTree(allPackages, appPackageJson) {
1115
+ var _a, _b;
1053
1116
  const relevantPackages = new Map();
1054
1117
  const packagesToCheck = new Set();
1055
1118
  const processedPackages = new Set();
1056
- // Start with direct dependencies from the app
1057
- if (appPackageJson.dependencies) {
1058
- for (const dep of Object.keys(appPackageJson.dependencies)) {
1059
- if (allPackages.has(dep)) {
1060
- packagesToCheck.add(dep);
1061
- }
1119
+ // Start with direct dependencies from the app, its linked.unpublishedPackages among them
1120
+ for (const dep of [
1121
+ ...Object.keys((_a = appPackageJson.dependencies) !== null && _a !== void 0 ? _a : {}),
1122
+ ...unpublishedNames(appPackageJson),
1123
+ ]) {
1124
+ if (allPackages.has(dep)) {
1125
+ packagesToCheck.add(dep);
1062
1126
  }
1063
1127
  }
1064
1128
  // Recursively follow each package's dependencies
@@ -1073,11 +1137,12 @@ let LinkedServer = class LinkedServer extends Shape {
1073
1137
  if (packageDetails) {
1074
1138
  relevantPackages.set(packageName, packageDetails);
1075
1139
  const pkg = getPackageJSON(packageDetails.path);
1076
- if (pkg === null || pkg === void 0 ? void 0 : pkg.dependencies) {
1077
- for (const dep of Object.keys(pkg.dependencies)) {
1078
- if (allPackages.has(dep) && !processedPackages.has(dep)) {
1079
- packagesToCheck.add(dep);
1080
- }
1140
+ for (const dep of [
1141
+ ...Object.keys((_b = pkg === null || pkg === void 0 ? void 0 : pkg.dependencies) !== null && _b !== void 0 ? _b : {}),
1142
+ ...unpublishedNames(pkg),
1143
+ ]) {
1144
+ if (allPackages.has(dep) && !processedPackages.has(dep)) {
1145
+ packagesToCheck.add(dep);
1081
1146
  }
1082
1147
  }
1083
1148
  }
@@ -1538,7 +1603,7 @@ let LinkedServer = class LinkedServer extends Shape {
1538
1603
  * Reload one package's backend providers from source.
1539
1604
  *
1540
1605
  * Called by the CLI orchestrator's Vite watcher whenever a `.ts`/`.tsx`
1541
- * file inside a workspace package changes, and by {@link resyncShapes}.
1606
+ * file inside a workspace package changes, and by {@link graphContent}.
1542
1607
  * Disposes the package's existing providers (so routes, listeners, timers
1543
1608
  * don't accumulate), drops them from the registry, and re-runs
1544
1609
  * indexPackageBackendProviders to pick up the freshly-imported module.