@_linked/server 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -27,6 +27,8 @@ If you want to further adjust the functionality of LincdServer yourself, either
27
27
  - [Shape catalog per request](#shape-catalog-per-request)
28
28
  - [API-only servers (`server.apiOnly`)](#api-only-servers-serverapionly)
29
29
  - [Host control channel (`/admin/*`)](#host-control-channel-admin)
30
+ - [Ontology sync and the shape check](#ontology-sync-and-the-shape-check)
31
+ - [Shape graphs](#shape-graphs)
30
32
  - [Provider lifecycle and HMR](#provider-lifecycle-and-hmr)
31
33
  - [When a provider throws, and refusing to start](#when-a-provider-throws-and-refusing-to-start)
32
34
  - [When to implement `dispose()`](#when-to-implement-dispose)
@@ -405,15 +407,32 @@ With `apiOnly` the server skips page rendering. It installs no SPA catch-all, so
405
407
 
406
408
  ## Host control channel (`/admin/*`)
407
409
 
408
- A host that starts the app process (Create Now) controls it over HTTP routes on the app's own port. They exist only when the process was started with `CN_APP_ADMIN_SECRET` in its environment; without it they answer 404. Every request carries the secret in `x-cn-admin-secret` (401 otherwise).
410
+ A host that starts the app process controls it over HTTP routes on the app's own port. They exist only when the process was started with `APP_ADMIN_SECRET` in its environment; without it they answer 404. Every request carries the secret in the `x-app-admin-secret` header (401 otherwise).
411
+
412
+ Deprecated names are still read: `CN_APP_ADMIN_SECRET` when `APP_ADMIN_SECRET` is unset, and the `x-cn-admin-secret` header when `x-app-admin-secret` is absent. Each logs one deprecation warning per process.
409
413
 
410
414
  | Route | Answer |
411
415
  |---|---|
412
416
  | `GET /admin/health` | 200 `{status: 'ok'}` once the server listens (the boot ontology sync, shape check and shape sync have finished by then) |
413
417
  | `POST /admin/restart` | 202, then the process exits; the host starts it again |
414
- | `POST /admin/sync-shapes` | 200 `{written, orphanScope, reloaded, shapes, ontologies: {written, skipped}, shapeCheck: {errors, warnings}}` (or `{written: 0, skipped, …}` when shape sync is off); 400 `{error}` for a malformed body or a package it may not reload; 500 `{error}` when the sync fails or times out |
418
+ | `POST /admin/graph-registry-changed` | body `{dataset}`: the host changed that dataset's named graphs; the process drops its cached graph registry and answers 200 `{invalidated: dataset}`; 400 without a dataset name |
419
+ | `GET /admin/routes` | 200 `{datasets: [{store, id?, default, routes, defaultGraphShapes}]}`: the effective graph routes (see below) |
420
+ | `POST /admin/sync-shapes` | 200 `{written, orphanScope, shapeGraphs, reloaded, shapes, ontologies: {written, skipped, graphs, droppedGraphs}, shapeCheck: {errors, warnings}}` (or `{written: 0, skipped, …}` when shape sync is off); 400 `{error}` for a malformed body or a package it may not reload; 500 `{error}` when the sync fails or times out |
421
+
422
+ `POST /admin/sync-shapes` runs the ontology sync and the shape check (see [Ontology sync and the shape check](#ontology-sync-and-the-shape-check)), then re-syncs the registered shapes into the app's data exactly as the boot does (see [Shape graphs](#shape-graphs): only graphs whose stored content hash differs are written, with the boot's orphan policy and `syncShapesOnBoot` / `LINKED_SYNC_SHAPES_ON_BOOT` switches). `written` counts the shape graphs written, and `shapeGraphs` reports them. The optional body `{packages: [...]}` names packages whose source the host changed; each must be a valid npm package name and the app itself or one of its direct dependencies, never an `@_linked/*` package; the list only says what to reload, it never filters the syncs. In dev their modules are invalidated in Vite and their backends reloaded before the sync, so a shape file written a moment ago is registered; a built app reloads nothing. `reloaded` lists the packages actually reloaded and `shapes` the node shape IRIs registered afterwards, so the host can check that the shape it wrote is there. Syncs run one at a time, as the system; one that takes longer than `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120000) fails and releases the queue. A source reload by itself (HMR) never syncs shapes. A failed ontology sync does not stop the shape sync; the route then answers 500 with the ontology error.
415
423
 
416
- `POST /admin/sync-shapes` runs the ontology sync and the shape check (see [Ontology sync and the shape check](#ontology-sync-and-the-shape-check)), then re-syncs the registered shapes into the app's data exactly as the boot does (core `syncShapes`: only shapes whose stored `core:shapeHash` differs are written, with the boot's orphan policy and `syncShapesOnBoot` / `LINKED_SYNC_SHAPES_ON_BOOT` switches). The optional body `{packages: [...]}` names packages whose source the host changed; each must be a valid npm package name and the app itself or one of its direct dependencies, never an `@_linked/*` package; the list only says what to reload, it never filters the syncs. In dev their modules are invalidated in Vite and their backends reloaded before the sync, so a shape file written a moment ago is registered; a built app reloads nothing. `reloaded` lists the packages actually reloaded and `shapes` the node shape IRIs registered afterwards, so the host can check that the shape it wrote is there. Syncs run one at a time, as the system; one that takes longer than `LINKED_SHAPE_SYNC_TIMEOUT_MS` (default 120000) fails and releases the queue. A source reload by itself (HMR) never syncs shapes. A failed ontology sync does not stop the shape sync; the route then answers 500 with the ontology error.
424
+ `GET /admin/routes` reports the graph routes the process runs with, as core's routing holds them once the app's storage file (`linked.backend.storage`) has run. Routes are written only there (`LinkedStorage.setDatasetForShapes(dataset, shapes, {graph})`); the app never pushes them anywhere, and this route lets a host check what actually runs. One entry per dataset the router knows, the default dataset first, then in the order they were pinned:
425
+
426
+ - `store`: the store's class name (`FusekiStore`, `RemoteDataset`, …), and `id` when the store has one. The server does not see the storage config's aliases, so these and `default` tell the datasets apart.
427
+ - `default`: whether it is the default dataset (the one for every shape without a pin).
428
+ - `routes`: `{<shape IRI>: <graph IRI>}` as registered. A sub-shape without a route of its own is written to its nearest routed super-shape's graph and is not listed.
429
+ - `defaultGraphShapes`: the registered shapes that resolve to this dataset and are written to its default graph (no route of their own, no routed super-shape), sorted.
430
+
431
+ ```json
432
+ {"datasets": [{"store": "FusekiStore", "default": true,
433
+ "routes": {"https://example.org/shape/Article": "https://example.org/data/graph/articles"},
434
+ "defaultGraphShapes": ["https://example.org/shape/Person"]}]}
435
+ ```
417
436
 
418
437
  Side effects to expect:
419
438
 
@@ -431,6 +450,24 @@ Before its shape sync, at boot and on `POST /admin/sync-shapes`, the server:
431
450
 
432
451
  The ontology sync follows the shape sync's switches (`syncShapesOnBoot`, `LINKED_SYNC_SHAPES_ON_BOOT`), at boot and on `POST /admin/sync-shapes` alike: with shape sync off, the route syncs neither ontologies nor shapes (it still registers the packages' ontologies and runs the shape check). `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false` turns off only the boot's ontology sync. The in-memory class hierarchy (core `loadOntologyHierarchy`) is loaded at boot either way. A failed ontology sync at boot is logged and the boot goes on.
433
452
 
453
+ ### Shape graphs
454
+
455
+ After the ontology sync and the shape check, at boot and on `POST /admin/sync-shapes`, the server syncs the registered shapes into the app's default dataset with core's `syncShapeGraphs`:
456
+
457
+ - **One graph per package.** Each package's shapes go in `{package IRI}/graph/shapes`, with a `ShapesGraph` record in the graph registry (never a hierarchy source). A graph whose stored content hash matches is not written, so an unchanged sync writes nothing; a changed one is replaced whole.
458
+ - **Where the Turtle comes from.** An installed package's built `lib/esm/data/shapes.ttl`, which `linked build` writes. An installed package without that file gets no shape graph: nothing is generated for it (a package with no shapes has none). The app's own packages (the app and its workspace packages) are generated in process: in dev through Vite's `ssrLoadModule` (core's `packageShapesTurtle` on the core the source shapes registered on, the same bytes `linked build` writes); in a built app from their `lib/esm/data/shapes.ttl` when they have one.
459
+ - **The app's own shape graph.** The single-owner shapes core generates from `contains` properties go in `{DATA_ROOT}/graph/shapes`, regenerated each sync (hash-skipped like the rest). Without `DATA_ROOT` there is none.
460
+ - **Earlier syncs.** Shapes an earlier server wrote into the default graph are removed there once their package's graph holds them. A store without graph queries gets that default-graph sync instead.
461
+ - **Orphans.** With `LINKED_SYNC_SHAPES_PRUNE_ORPHANS=true` the shape graphs of packages no longer registered are dropped (never the app's own graph); core prunes only when every registered package has Turtle, so a package without `shapes.ttl` skips the pruning with a warning.
462
+
463
+ The boot logs one line, for example:
464
+
465
+ ```
466
+ [LinkedServer] shape graph sync: written @acme/shop; skipped @_linked/schema; app graph written; graphs written https://linked.cm/pkg/acme-shop/graph/shapes, https://acme.example/data/graph/shapes; no shapes.ttl (no shape graph): @acme/legacy
467
+ ```
468
+
469
+ `POST /admin/sync-shapes` answers the same as `shapeGraphs`: `{written, skipped, pruned, graphs, appGraph: {graph, status}, withoutTurtle, cleaned}` (plus `defaultGraph`, the default-graph sync's writes, on a store without graph queries).
470
+
434
471
  ## Provider lifecycle and HMR
435
472
 
436
473
  Every provider — generic (`BackendProvider`) or shape-scoped (`ShapeProvider`) — has a lifecycle the framework drives: