@_linked/server 3.6.0 → 3.7.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/CHANGELOG.md +33 -0
- package/lib/esm/data/server.ttl +75 -0
- package/lib/esm/ontologies/server.d.ts +4 -22
- package/lib/esm/ontologies/server.js +5 -11
- package/lib/esm/ontologies/server.js.map +1 -1
- package/lib/esm/ontologies/server.register.js +3 -3
- package/lib/esm/ontologies/server.register.js.map +1 -1
- package/lib/esm/package.d.ts +1 -1
- package/lib/esm/shapes/LinkedServer.d.ts +39 -9
- package/lib/esm/shapes/LinkedServer.js +104 -25
- package/lib/esm/shapes/LinkedServer.js.map +1 -1
- package/lib/esm/utils/adminChannel.d.ts +28 -0
- package/lib/esm/utils/adminChannel.js +35 -4
- package/lib/esm/utils/adminChannel.js.map +1 -1
- package/lib/esm/utils/linkedDependencies.d.ts +7 -0
- package/lib/esm/utils/linkedDependencies.js +29 -0
- package/lib/esm/utils/linkedDependencies.js.map +1 -1
- package/lib/esm/utils/ontologySync.d.ts +126 -0
- package/lib/esm/utils/ontologySync.js +124 -0
- package/lib/esm/utils/ontologySync.js.map +1 -0
- package/lib/esm/utils/withTimeout.d.ts +7 -0
- package/lib/esm/utils/withTimeout.js +17 -0
- package/lib/esm/utils/withTimeout.js.map +1 -0
- package/package.json +4 -4
- package/readme.md +29 -0
- package/lib/esm/data/server.json +0 -47
|
@@ -7,7 +7,35 @@ export interface ShapeSyncResult {
|
|
|
7
7
|
orphanScope?: 'all' | 'none';
|
|
8
8
|
/** Set when no sync ran: shape sync is turned off, or there is no default dataset. */
|
|
9
9
|
skipped?: 'disabled' | 'no-default-dataset';
|
|
10
|
+
/**
|
|
11
|
+
* The requested packages whose source was reloaded before the sync (dev only: a built
|
|
12
|
+
* app reloads nothing). Set by `POST /admin/sync-shapes`.
|
|
13
|
+
*/
|
|
14
|
+
reloaded?: string[];
|
|
15
|
+
/** The node shape IRIs registered in the process after the reload. Set by `POST /admin/sync-shapes`. */
|
|
16
|
+
shapes?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* The ontology sync: packages whose class hierarchy was written, and packages left as
|
|
19
|
+
* they were (unchanged, or their data could not be read). Set by `POST /admin/sync-shapes`.
|
|
20
|
+
*/
|
|
21
|
+
ontologies?: {
|
|
22
|
+
written: string[];
|
|
23
|
+
skipped: string[];
|
|
24
|
+
};
|
|
25
|
+
/** The shape check's findings, counted by severity (each is logged). Set by `POST /admin/sync-shapes`. */
|
|
26
|
+
shapeCheck?: {
|
|
27
|
+
errors: number;
|
|
28
|
+
warnings: number;
|
|
29
|
+
};
|
|
10
30
|
}
|
|
31
|
+
/** Whether `name` is a valid npm package name (and so can never be a path like `..`). */
|
|
32
|
+
export declare function isValidPackageName(name: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Refuse a package a host may not ask to reload: not a valid npm name, a framework
|
|
35
|
+
* (`@_linked/*`) package, or not in `allowed` (the app's own name and its direct
|
|
36
|
+
* dependencies). Throws {@link AdminRequestError}.
|
|
37
|
+
*/
|
|
38
|
+
export declare function assertReloadablePackages(packages: string[], allowed: ReadonlySet<string>): void;
|
|
11
39
|
/** Thrown by a handler for a request the channel refuses with 400. */
|
|
12
40
|
export declare class AdminRequestError extends Error {
|
|
13
41
|
}
|
|
@@ -8,13 +8,40 @@
|
|
|
8
8
|
* - `GET /admin/health` → 200 `{status: 'ok'}` once the server listens (the boot
|
|
9
9
|
* shape sync has finished by then).
|
|
10
10
|
* - `POST /admin/restart` → 202, then the process exits; the host starts it again.
|
|
11
|
-
* - `POST /admin/sync-shapes` → re-syncs the app's
|
|
12
|
-
*
|
|
11
|
+
* - `POST /admin/sync-shapes` → re-syncs the app's ontologies (the class hierarchy
|
|
12
|
+
* of every registered package, skipped when unchanged), runs the shape check and
|
|
13
|
+
* re-syncs its shapes into its app data, as the boot does, and answers 200 with
|
|
14
|
+
* {@link ShapeSyncResult}. Body (optional):
|
|
13
15
|
* `{packages: string[]}`, packages whose source changed on disk: in dev they are
|
|
14
|
-
* reloaded first, so the sync sees shapes added or changed since the boot.
|
|
15
|
-
*
|
|
16
|
+
* reloaded first, so the sync sees shapes added or changed since the boot. Each
|
|
17
|
+
* name must be a valid npm package name and the app itself or one of its direct
|
|
18
|
+
* dependencies, never an `@_linked/*` framework package; anything else answers
|
|
19
|
+
* 400 `{error}`. The list never filters the syncs. A failed or timed-out sync
|
|
20
|
+
* answers 500 `{error}` (a failed ontology sync too, after the shape sync ran).
|
|
16
21
|
*/
|
|
17
22
|
import { timingSafeEqual } from 'crypto';
|
|
23
|
+
/** npm's package-name rule: optional `@scope/`, lower case, URL-safe, no leading `.` or `_` (a scope may start with `_`, as `@_linked` does). */
|
|
24
|
+
const NPM_NAME = /^(?:@[a-z0-9-~_][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
25
|
+
/** Whether `name` is a valid npm package name (and so can never be a path like `..`). */
|
|
26
|
+
export function isValidPackageName(name) {
|
|
27
|
+
return name.length <= 214 && NPM_NAME.test(name) && !name.split('/').some((part) => part === '.' || part === '..');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Refuse a package a host may not ask to reload: not a valid npm name, a framework
|
|
31
|
+
* (`@_linked/*`) package, or not in `allowed` (the app's own name and its direct
|
|
32
|
+
* dependencies). Throws {@link AdminRequestError}.
|
|
33
|
+
*/
|
|
34
|
+
export function assertReloadablePackages(packages, allowed) {
|
|
35
|
+
for (const pkg of packages) {
|
|
36
|
+
if (!isValidPackageName(pkg))
|
|
37
|
+
throw new AdminRequestError(`not a package name: ${JSON.stringify(pkg)}`);
|
|
38
|
+
if (pkg.startsWith('@_linked/'))
|
|
39
|
+
throw new AdminRequestError(`framework packages are not reloaded: ${pkg}`);
|
|
40
|
+
if (!allowed.has(pkg)) {
|
|
41
|
+
throw new AdminRequestError(`unknown package: ${pkg} is neither the app nor one of its direct dependencies`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
18
45
|
/** Thrown by a handler for a request the channel refuses with 400. */
|
|
19
46
|
export class AdminRequestError extends Error {
|
|
20
47
|
}
|
|
@@ -39,6 +66,10 @@ export function parseSyncShapesBody(body) {
|
|
|
39
66
|
if (!Array.isArray(packages) || packages.some((p) => typeof p !== 'string' || !p)) {
|
|
40
67
|
throw new AdminRequestError('packages must be a list of package names');
|
|
41
68
|
}
|
|
69
|
+
for (const p of packages) {
|
|
70
|
+
if (!isValidPackageName(p))
|
|
71
|
+
throw new AdminRequestError(`not a package name: ${JSON.stringify(p)}`);
|
|
72
|
+
}
|
|
42
73
|
return { packages: [...new Set(packages)] };
|
|
43
74
|
}
|
|
44
75
|
/** Mount the control channel's routes on `app`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adminChannel.js","sourceRoot":"","sources":["../../../src/utils/adminChannel.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The host→app control channel: `/admin/*` routes a host (Create Now) uses on an\n * app process it started. Present only when the process was started with a\n * shared secret in `CN_APP_ADMIN_SECRET`; without it every route answers 404, so\n * a standalone app has no control channel. Every request carries the secret in\n * the `x-cn-admin-secret` header.\n *\n * - `GET /admin/health` → 200 `{status: 'ok'}` once the server listens (the boot\n * shape sync has finished by then).\n * - `POST /admin/restart` → 202, then the process exits; the host starts it again.\n * - `POST /admin/sync-shapes` → re-syncs the app's shapes into its app data, as\n * the boot does, and answers 200 with {@link ShapeSyncResult}. Body (optional):\n * `{packages: string[]}`, packages whose source changed on disk: in dev they are\n * reloaded first, so the sync sees shapes added or changed since the boot. A\n * failed sync answers 500 `{error}`; an unknown package 400 `{error}`.\n */\nimport { timingSafeEqual } from 'crypto';\nimport type express from 'express';\n\n/** What one shape sync did. */\nexport interface ShapeSyncResult {\n /** Shapes written: those whose stored hash differed from the registered shape. */\n written: number;\n /** The orphan policy the sync ran with (see utils/syncShapes). */\n orphanScope?: 'all' | 'none';\n /** Set when no sync ran: shape sync is turned off, or there is no default dataset. */\n skipped?: 'disabled' | 'no-default-dataset';\n}\n\n/** Thrown by a handler for a request the channel refuses with 400. */\nexport class AdminRequestError extends Error {}\n\nexport interface AdminChannelHandlers {\n /** Exit the process (the host restarts it). Called after the 202 is sent. */\n exit: () => void;\n /** Reload `packages` (when given) and sync the registered shapes into app data. */\n syncShapes: (request: { packages: string[] }) => Promise<ShapeSyncResult>;\n}\n\n/** Compare the provided secret with the configured one in constant time. */\nexport function adminSecretMatches(\n secret: string | undefined,\n provided: string | undefined\n): boolean {\n if (!secret || !provided) return false;\n const a = Buffer.from(provided);\n const b = Buffer.from(secret);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\n/** The `packages` of a sync-shapes body: a list of package names, or none. */\nexport function parseSyncShapesBody(body: unknown): { packages: string[] } {\n if (body === undefined || body === null || body === '') return { packages: [] };\n if (typeof body !== 'object') {\n throw new AdminRequestError('the body must be a JSON object');\n }\n const packages = (body as { packages?: unknown }).packages;\n if (packages === undefined) return { packages: [] };\n if (!Array.isArray(packages) || packages.some((p) => typeof p !== 'string' || !p)) {\n throw new AdminRequestError('packages must be a list of package names');\n }\n return { packages: [...new Set(packages as string[])] };\n}\n\n/** Mount the control channel's routes on `app`. */\nexport function registerAdminRoutes(\n app: express.Express,\n secret: string | undefined,\n handlers: AdminChannelHandlers\n): void {\n const requireAdmin = (req: express.Request, res: express.Response): boolean => {\n if (!secret) {\n res.sendStatus(404); // no control channel in this mode\n return false;\n }\n if (!adminSecretMatches(secret, req.get('x-cn-admin-secret'))) {\n res.sendStatus(401);\n return false;\n }\n return true;\n };\n\n app.get('/admin/health', (req, res) => {\n if (!requireAdmin(req, res)) return;\n res.status(200).json({ status: 'ok' });\n });\n\n app.post('/admin/restart', (req, res) => {\n if (!requireAdmin(req, res)) return;\n res.sendStatus(202);\n setTimeout(handlers.exit, 50);\n });\n\n app.post('/admin/sync-shapes', async (req, res) => {\n if (!requireAdmin(req, res)) return;\n let request: { packages: string[] };\n try {\n request = parseSyncShapesBody(req.body);\n } catch (err) {\n res.status(400).json({ error: (err as Error).message });\n return;\n }\n try {\n res.status(200).json(await handlers.syncShapes(request));\n } catch (err) {\n const status = err instanceof AdminRequestError ? 400 : 500;\n res.status(status).json({ error: err instanceof Error ? err.message : String(err) });\n }\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"adminChannel.js","sourceRoot":"","sources":["../../../src/utils/adminChannel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AA2BzC,iJAAiJ;AACjJ,MAAM,QAAQ,GAAG,2DAA2D,CAAC;AAE7E,yFAAyF;AACzF,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC;AACrH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAAkB,EAAE,OAA4B;IACvF,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxG,IAAI,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAAC;QAC5G,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,iBAAiB,CAAC,oBAAoB,GAAG,wDAAwD,CAAC,CAAC;QAC/G,CAAC;IACH,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAS/C,4EAA4E;AAC5E,MAAM,UAAU,kBAAkB,CAChC,MAA0B,EAC1B,QAA4B;IAE5B,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAChF,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,IAAI,iBAAiB,CAAC,gCAAgC,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,QAAQ,GAAI,IAA+B,CAAC,QAAQ,CAAC;IAC3D,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACpD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,MAAM,IAAI,iBAAiB,CAAC,0CAA0C,CAAC,CAAC;IAC1E,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,QAAoB,EAAE,CAAC;QACrC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtG,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,QAAoB,CAAC,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,mBAAmB,CACjC,GAAoB,EACpB,MAA0B,EAC1B,QAA8B;IAE9B,MAAM,YAAY,GAAG,CAAC,GAAoB,EAAE,GAAqB,EAAW,EAAE;QAC5E,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,kCAAkC;YACvD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC;YAC9D,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACpB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACpC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QACpC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACtC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QACpC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACpB,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAChD,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QACpC,IAAI,OAA+B,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACxD,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAC5D,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The host→app control channel: `/admin/*` routes a host (Create Now) uses on an\n * app process it started. Present only when the process was started with a\n * shared secret in `CN_APP_ADMIN_SECRET`; without it every route answers 404, so\n * a standalone app has no control channel. Every request carries the secret in\n * the `x-cn-admin-secret` header.\n *\n * - `GET /admin/health` → 200 `{status: 'ok'}` once the server listens (the boot\n * shape sync has finished by then).\n * - `POST /admin/restart` → 202, then the process exits; the host starts it again.\n * - `POST /admin/sync-shapes` → re-syncs the app's ontologies (the class hierarchy\n * of every registered package, skipped when unchanged), runs the shape check and\n * re-syncs its shapes into its app data, as the boot does, and answers 200 with\n * {@link ShapeSyncResult}. Body (optional):\n * `{packages: string[]}`, packages whose source changed on disk: in dev they are\n * reloaded first, so the sync sees shapes added or changed since the boot. Each\n * name must be a valid npm package name and the app itself or one of its direct\n * dependencies, never an `@_linked/*` framework package; anything else answers\n * 400 `{error}`. The list never filters the syncs. A failed or timed-out sync\n * answers 500 `{error}` (a failed ontology sync too, after the shape sync ran).\n */\nimport { timingSafeEqual } from 'crypto';\nimport type express from 'express';\n\n/** What one shape sync did. */\nexport interface ShapeSyncResult {\n /** Shapes written: those whose stored hash differed from the registered shape. */\n written: number;\n /** The orphan policy the sync ran with (see utils/syncShapes). */\n orphanScope?: 'all' | 'none';\n /** Set when no sync ran: shape sync is turned off, or there is no default dataset. */\n skipped?: 'disabled' | 'no-default-dataset';\n /**\n * The requested packages whose source was reloaded before the sync (dev only: a built\n * app reloads nothing). Set by `POST /admin/sync-shapes`.\n */\n reloaded?: string[];\n /** The node shape IRIs registered in the process after the reload. Set by `POST /admin/sync-shapes`. */\n shapes?: string[];\n /**\n * The ontology sync: packages whose class hierarchy was written, and packages left as\n * they were (unchanged, or their data could not be read). Set by `POST /admin/sync-shapes`.\n */\n ontologies?: { written: string[]; skipped: string[] };\n /** The shape check's findings, counted by severity (each is logged). Set by `POST /admin/sync-shapes`. */\n shapeCheck?: { errors: number; warnings: number };\n}\n\n/** npm's package-name rule: optional `@scope/`, lower case, URL-safe, no leading `.` or `_` (a scope may start with `_`, as `@_linked` does). */\nconst NPM_NAME = /^(?:@[a-z0-9-~_][a-z0-9-._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*$/;\n\n/** Whether `name` is a valid npm package name (and so can never be a path like `..`). */\nexport function isValidPackageName(name: string): boolean {\n return name.length <= 214 && NPM_NAME.test(name) && !name.split('/').some((part) => part === '.' || part === '..');\n}\n\n/**\n * Refuse a package a host may not ask to reload: not a valid npm name, a framework\n * (`@_linked/*`) package, or not in `allowed` (the app's own name and its direct\n * dependencies). Throws {@link AdminRequestError}.\n */\nexport function assertReloadablePackages(packages: string[], allowed: ReadonlySet<string>): void {\n for (const pkg of packages) {\n if (!isValidPackageName(pkg)) throw new AdminRequestError(`not a package name: ${JSON.stringify(pkg)}`);\n if (pkg.startsWith('@_linked/')) throw new AdminRequestError(`framework packages are not reloaded: ${pkg}`);\n if (!allowed.has(pkg)) {\n throw new AdminRequestError(`unknown package: ${pkg} is neither the app nor one of its direct dependencies`);\n }\n }\n}\n\n/** Thrown by a handler for a request the channel refuses with 400. */\nexport class AdminRequestError extends Error {}\n\nexport interface AdminChannelHandlers {\n /** Exit the process (the host restarts it). Called after the 202 is sent. */\n exit: () => void;\n /** Reload `packages` (when given) and sync the registered shapes into app data. */\n syncShapes: (request: { packages: string[] }) => Promise<ShapeSyncResult>;\n}\n\n/** Compare the provided secret with the configured one in constant time. */\nexport function adminSecretMatches(\n secret: string | undefined,\n provided: string | undefined\n): boolean {\n if (!secret || !provided) return false;\n const a = Buffer.from(provided);\n const b = Buffer.from(secret);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\n/** The `packages` of a sync-shapes body: a list of package names, or none. */\nexport function parseSyncShapesBody(body: unknown): { packages: string[] } {\n if (body === undefined || body === null || body === '') return { packages: [] };\n if (typeof body !== 'object') {\n throw new AdminRequestError('the body must be a JSON object');\n }\n const packages = (body as { packages?: unknown }).packages;\n if (packages === undefined) return { packages: [] };\n if (!Array.isArray(packages) || packages.some((p) => typeof p !== 'string' || !p)) {\n throw new AdminRequestError('packages must be a list of package names');\n }\n for (const p of packages as string[]) {\n if (!isValidPackageName(p)) throw new AdminRequestError(`not a package name: ${JSON.stringify(p)}`);\n }\n return { packages: [...new Set(packages as string[])] };\n}\n\n/** Mount the control channel's routes on `app`. */\nexport function registerAdminRoutes(\n app: express.Express,\n secret: string | undefined,\n handlers: AdminChannelHandlers\n): void {\n const requireAdmin = (req: express.Request, res: express.Response): boolean => {\n if (!secret) {\n res.sendStatus(404); // no control channel in this mode\n return false;\n }\n if (!adminSecretMatches(secret, req.get('x-cn-admin-secret'))) {\n res.sendStatus(401);\n return false;\n }\n return true;\n };\n\n app.get('/admin/health', (req, res) => {\n if (!requireAdmin(req, res)) return;\n res.status(200).json({ status: 'ok' });\n });\n\n app.post('/admin/restart', (req, res) => {\n if (!requireAdmin(req, res)) return;\n res.sendStatus(202);\n setTimeout(handlers.exit, 50);\n });\n\n app.post('/admin/sync-shapes', async (req, res) => {\n if (!requireAdmin(req, res)) return;\n let request: { packages: string[] };\n try {\n request = parseSyncShapesBody(req.body);\n } catch (err) {\n res.status(400).json({ error: (err as Error).message });\n return;\n }\n try {\n res.status(200).json(await handlers.syncShapes(request));\n } catch (err) {\n const status = err instanceof AdminRequestError ? 400 : 500;\n res.status(status).json({ error: err instanceof Error ? err.message : String(err) });\n }\n });\n}\n"]}
|
|
@@ -70,6 +70,13 @@ export declare function readInstalledPackage(name: string, fromDir: string): {
|
|
|
70
70
|
* (it is only installed nested) it is the first copy found.
|
|
71
71
|
*/
|
|
72
72
|
export declare function discoverLinkedDependencies(appDir: string, appPackageJson: any): LinkedDependency[];
|
|
73
|
+
/**
|
|
74
|
+
* Package name → the names of its direct `dependencies` and `peerDependencies`, for the app
|
|
75
|
+
* (`appPackageJson`) and every package in `packageDirs` (name → package root, as discovery
|
|
76
|
+
* found it). The shape check scopes its direction-I errors with it. A package whose
|
|
77
|
+
* `package.json` cannot be read is left out.
|
|
78
|
+
*/
|
|
79
|
+
export declare function readDependencyLists(packageDirs: Iterable<[string, string]>, appPackageJson?: any): Record<string, string[]>;
|
|
73
80
|
/**
|
|
74
81
|
* The conditions a backend entry is resolved with on the Node path. These are
|
|
75
82
|
* the ones Node's ESM loader applies to `import()` — so the file found is the
|
|
@@ -131,6 +131,35 @@ export function discoverLinkedDependencies(appDir, appPackageJson) {
|
|
|
131
131
|
}
|
|
132
132
|
return out;
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Package name → the names of its direct `dependencies` and `peerDependencies`, for the app
|
|
136
|
+
* (`appPackageJson`) and every package in `packageDirs` (name → package root, as discovery
|
|
137
|
+
* found it). The shape check scopes its direction-I errors with it. A package whose
|
|
138
|
+
* `package.json` cannot be read is left out.
|
|
139
|
+
*/
|
|
140
|
+
export function readDependencyLists(packageDirs, appPackageJson) {
|
|
141
|
+
const lists = {};
|
|
142
|
+
const add = (name, json) => {
|
|
143
|
+
var _a, _b;
|
|
144
|
+
if (typeof name !== 'string' || !name)
|
|
145
|
+
return;
|
|
146
|
+
lists[name] = [
|
|
147
|
+
...new Set([
|
|
148
|
+
...Object.keys((_a = json === null || json === void 0 ? void 0 : json.dependencies) !== null && _a !== void 0 ? _a : {}),
|
|
149
|
+
...Object.keys((_b = json === null || json === void 0 ? void 0 : json.peerDependencies) !== null && _b !== void 0 ? _b : {}),
|
|
150
|
+
]),
|
|
151
|
+
].sort();
|
|
152
|
+
};
|
|
153
|
+
if (appPackageJson)
|
|
154
|
+
add(appPackageJson.name, appPackageJson);
|
|
155
|
+
for (const [name, dir] of packageDirs) {
|
|
156
|
+
try {
|
|
157
|
+
add(name, JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8')));
|
|
158
|
+
}
|
|
159
|
+
catch (_a) { }
|
|
160
|
+
}
|
|
161
|
+
return lists;
|
|
162
|
+
}
|
|
134
163
|
function realpathOr(p) {
|
|
135
164
|
try {
|
|
136
165
|
return fs.realpathSync(p);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linkedDependencies.js","sourceRoot":"","sources":["../../../src/utils/linkedDependencies.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\n/**\n * A linked package found in the app's dependency tree, wherever it is\n * installed: a workspace member, a localized checkout, or a registry install.\n */\nexport interface LinkedDependency {\n packageName: string;\n /**\n * Realpath of the package root whose backend the server loads: the copy the\n * app itself resolves when that copy is a linked package, otherwise the\n * first copy the walk found.\n */\n path: string;\n /**\n * Every distinct installed copy of this package in the tree (realpaths),\n * in the order the walk found them. More than one means the install is not\n * deduplicated; only `path`'s backend is ever loaded.\n */\n copies: { path: string; version?: string }[];\n}\n\n/**\n * A package whose backend providers the server loads at boot: one whose\n * package.json declares `\"linkedPackage\": true`. That flag is the only test —\n * not the package's scope. `@_linked/localize` is in the `@_linked` scope and\n * is a CLI tool, not a linked package; a custom-scope package with the flag is\n * a linked package. The CLI's workspace discovery uses the same flag.\n *\n * The legacy `\"lincd\": true` flag is deliberately NOT enough. Those packages\n * import the old `lincd` core, and loading them at boot is what produced\n * \"Multiple versions of LINCD are loaded\" before boot loading was narrowed to\n * workspace members.\n */\nexport function isLinkedPackageJson(json: any): boolean {\n if (!json || typeof json.name !== 'string') return false;\n return json.linkedPackage === true;\n}\n\n/**\n * Find an installed package the way Node does: `<dir>/node_modules/<name>`,\n * then each parent directory's `node_modules`. Returns null when it is not\n * installed (an optional dependency, say) rather than throwing.\n */\n/**\n * Whether a package has a JavaScript entry the server can import: `main`,\n * `module`, or an `exports` map with a root (`.`) entry or a single string.\n * A linked package can be pure CSS (`@_linked/css`); importing that whole\n * fails with \"Failed to resolve entry\", so the server skips it.\n */\nexport function hasModuleEntry(json: any): boolean {\n if (!json) return false;\n if (typeof json.main === 'string' || typeof json.module === 'string') {\n return true;\n }\n const exp = json.exports;\n if (typeof exp === 'string') return true;\n if (exp && typeof exp === 'object') {\n const keys = Object.keys(exp);\n // Conditions only ({import, require, ...}) describe the root entry.\n if (keys.length > 0 && keys.every((k) => !k.startsWith('.'))) return true;\n return '.' in exp;\n }\n return false;\n}\n\nexport function readInstalledPackage(\n name: string,\n fromDir: string\n): { root: string; json: any } | null {\n let dir = path.resolve(fromDir);\n while (true) {\n const root = path.join(dir, 'node_modules', name);\n const pkgJsonPath = path.join(root, 'package.json');\n if (fs.existsSync(pkgJsonPath)) {\n try {\n return { root, json: JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')) };\n } catch {\n return null;\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Every linked package reachable from the app's `dependencies`, each one AFTER\n * the linked packages it depends on. Providers register middleware in the\n * order they are indexed, so a package's middleware must not run before that\n * of a package it builds on (e.g. anything reading `request.linkedAuth` after\n * @_linked/auth's JWT middleware).\n *\n * Only linked packages are recursed into — the same rule the workspace-only\n * filter used — so the walk reads a few dozen package.json files, not the\n * whole of node_modules. Each package's dependencies are resolved from its\n * REAL location, so a symlinked workspace or localized checkout resolves its\n * own dependencies the way Node will when it loads it — which is how a\n * package installed only nested (`owl/node_modules/@_linked/rdfs`) is found.\n *\n * The walk is keyed by real path, not name, so every installed copy of a\n * package is recorded (`copies`). The copy whose backend is loaded (`path`)\n * is the one the app itself resolves, so it is the same module instance the\n * app's own imports reach; when the app cannot resolve the package at all\n * (it is only installed nested) it is the first copy found.\n */\nexport function discoverLinkedDependencies(\n appDir: string,\n appPackageJson: any\n): LinkedDependency[] {\n const out: LinkedDependency[] = [];\n const byName = new Map<string, LinkedDependency>();\n const visitedRoots = new Set<string>();\n const appName = appPackageJson?.name;\n\n const visit = (name: string, fromDir: string) => {\n if (name === appName) return;\n const installed = readInstalledPackage(name, fromDir);\n if (!installed || !isLinkedPackageJson(installed.json)) return;\n const realRoot = realpathOr(installed.root);\n if (visitedRoots.has(realRoot)) return;\n visitedRoots.add(realRoot);\n let entry = byName.get(name);\n if (!entry) {\n entry = { packageName: name, path: realRoot, copies: [] };\n byName.set(name, entry);\n }\n entry.copies.push({ path: realRoot, version: installed.json.version });\n for (const dep of Object.keys(installed.json.dependencies ?? {})) {\n visit(dep, realRoot);\n }\n // Pushed after its dependencies, once per name: the first copy found\n // fixes the package's place in the order.\n if (!out.includes(entry)) out.push(entry);\n };\n\n for (const name of Object.keys(appPackageJson?.dependencies ?? {})) {\n visit(name, appDir);\n }\n\n for (const entry of out) {\n if (entry.copies.length < 2) continue;\n const fromApp = readInstalledPackage(entry.packageName, appDir);\n if (fromApp && isLinkedPackageJson(fromApp.json)) {\n const appCopy = realpathOr(fromApp.root);\n if (entry.copies.some((c) => c.path === appCopy)) entry.path = appCopy;\n }\n }\n return out;\n}\n\nfunction realpathOr(p: string): string {\n try {\n return fs.realpathSync(p);\n } catch {\n return p;\n }\n}\n\n/**\n * The conditions a backend entry is resolved with on the Node path. These are\n * the ones Node's ESM loader applies to `import()` — so the file found is the\n * file a bare `import('<pkg>/backend')` from the app would load, and Node's\n * module cache (keyed by real file URL) hands both the same instance.\n * `development` is deliberately absent: it is a Vite/bundler condition, and a\n * production Node runtime never applies it.\n */\nexport const NODE_IMPORT_CONDITIONS = ['import', 'node', 'default'] as const;\n\n/**\n * The absolute path of a package's backend entry file, or null when the\n * package has no backend entry or the file it names does not exist.\n *\n * With `exports`, this follows Node's PACKAGE_EXPORTS_RESOLVE for the\n * `./backend` subpath: an exact `./backend` key wins; otherwise the `./*`-style\n * pattern with the longest matching prefix; condition objects (nested or not)\n * pick the first key that is one of `conditions` or `default`; arrays take the\n * first target that resolves. A package that has `exports` but nothing\n * matching `./backend` has no backend — exactly as Node would refuse to\n * import it.\n *\n * Without `exports`, the legacy layout: `<root>/backend.js`,\n * `<root>/backend/index.js`, and the layout linked packages build to — a\n * `backend.js` next to `main` (`lib/esm/backend.js`).\n *\n * Hand-written rather than via `resolve.exports`: that package also stops at\n * a path and leaves the existence check to the caller, the subset needed is\n * ~50 lines, and the server keeps one less dependency in every app's install.\n */\nexport function resolveBackendEntry(\n pkgRoot: string,\n pkgJson: any,\n conditions: readonly string[] = NODE_IMPORT_CONDITIONS\n): string | null {\n const exists = (rel: string) => packageFile(pkgRoot, rel);\n const exportsField = pkgJson?.exports;\n if (exportsField === undefined || exportsField === null) {\n const candidates = ['backend.js', 'backend/index.js'];\n if (typeof pkgJson?.main === 'string') {\n candidates.push(path.join(path.dirname(pkgJson.main), 'backend.js'));\n }\n candidates.push('lib/esm/backend.js');\n for (const rel of candidates) {\n const file = exists(rel);\n if (file) return file;\n }\n return null;\n }\n return resolveBackendExport(exportsField, conditions, exists);\n}\n\n/** `<pkgRoot>/<rel>` when it is a file inside the package, else null. */\nfunction packageFile(pkgRoot: string, rel: string): string | null {\n const file = path.resolve(pkgRoot, rel);\n // Never let an export target escape the package.\n if (!file.startsWith(path.resolve(pkgRoot) + path.sep)) return null;\n try {\n return fs.statSync(file).isFile() ? file : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The `./backend` subpath of an `exports` map, resolved per Node's\n * PACKAGE_EXPORTS_RESOLVE (see resolveBackendEntry). `probe` turns a matched\n * `./`-relative target into a file, or null when it does not resolve.\n */\nfunction resolveBackendExport(\n exportsField: any,\n conditions: readonly string[],\n probe: (rel: string) => string | null\n): string | null {\n // `\"exports\": \"./x.js\"`, an array, or a condition object at the top level\n // are all sugar for the \".\" subpath only.\n if (\n typeof exportsField !== 'object' ||\n Array.isArray(exportsField) ||\n !Object.keys(exportsField).some((k) => k.startsWith('.'))\n ) {\n return null;\n }\n\n const subpath = './backend';\n let target: any;\n let patternMatch: string | null = null;\n if (Object.prototype.hasOwnProperty.call(exportsField, subpath)) {\n target = exportsField[subpath];\n } else {\n let bestKey: string | null = null;\n for (const key of Object.keys(exportsField)) {\n const star = key.indexOf('*');\n if (star === -1 || key.indexOf('*', star + 1) !== -1) continue;\n const prefix = key.slice(0, star);\n const suffix = key.slice(star + 1);\n if (\n subpath !== prefix &&\n subpath.length >= key.length &&\n subpath.startsWith(prefix) &&\n subpath.endsWith(suffix) &&\n (bestKey === null || patternKeyCompare(bestKey, key) > 0)\n ) {\n bestKey = key;\n patternMatch = subpath.slice(prefix.length, subpath.length - suffix.length);\n }\n }\n if (bestKey === null) return null;\n target = exportsField[bestKey];\n }\n\n const resolveTarget = (t: any): string | null => {\n if (typeof t === 'string') {\n if (!t.startsWith('./')) return null;\n const rel = patternMatch === null ? t : t.split('*').join(patternMatch);\n return probe(rel);\n }\n if (Array.isArray(t)) {\n for (const item of t) {\n const file = resolveTarget(item);\n if (file) return file;\n }\n return null;\n }\n if (t && typeof t === 'object') {\n for (const key of Object.keys(t)) {\n if (key === 'default' || conditions.includes(key)) {\n // Node takes the first matching condition even if its target fails\n // to resolve; it does not fall through to later keys.\n return resolveTarget(t[key]);\n }\n }\n }\n return null;\n };\n return resolveTarget(target);\n}\n\n/**\n * The SOURCE file a dev Vite runner loads for `<pkg>/backend` when the\n * package is a source package — its real root has a `src/` directory, which is\n * what makes `linked`'s Vite config register it as a workspace (cli\n * `discoverWorkspaces` / `readSourcePackage`). Null when the package has no\n * `src/` or no source backend; the caller then falls back to\n * resolveBackendEntry (a built `lib/` copy, or nothing).\n *\n * Mirrors the cli's `resolveWorkspaceSpecifier` for the `./backend` subpath,\n * which runs BEFORE `exports`: `src/backend.tsx`, `src/backend.ts`, then the\n * package's own `exports` under `import`/`default` with a `./lib/esm/X.js`\n * target mapped to `src/X.tsx` / `src/X.ts` (any other `./lib/esm/` target to\n * the same path under src). So an unbuilt workspace, whose `exports` name only\n * `lib/esm`, still has its backend found, and a built one reports the src file\n * Vite actually evaluates rather than its stale `lib/`.\n *\n * Only meaningful when the dev Vite runner owns the package; Node (production,\n * external packages) loads `lib/` and never sees src.\n */\nexport function resolveSourceBackendEntry(\n pkgRoot: string,\n pkgJson: any\n): string | null {\n try {\n if (!fs.statSync(path.join(pkgRoot, 'src')).isDirectory()) return null;\n } catch {\n return null;\n }\n for (const rel of ['src/backend.tsx', 'src/backend.ts']) {\n const file = packageFile(pkgRoot, rel);\n if (file) return file;\n }\n const exportsField = pkgJson?.exports;\n if (exportsField === undefined || exportsField === null) return null;\n const libPrefix = './lib/esm/';\n return resolveBackendExport(exportsField, ['import'], (rel) => {\n if (!rel.startsWith(libPrefix)) return null;\n const sub = rel.slice(libPrefix.length);\n if (/\\.jsx?$/.test(sub)) {\n const base = 'src/' + sub.replace(/\\.jsx?$/, '');\n return packageFile(pkgRoot, base + '.tsx') ?? packageFile(pkgRoot, base + '.ts');\n }\n return packageFile(pkgRoot, 'src/' + sub);\n });\n}\n\n/** Node's PATTERN_KEY_COMPARE: < 0 means `a` is the more specific pattern. */\nfunction patternKeyCompare(a: string, b: string): number {\n const aBase = a.indexOf('*');\n const bBase = b.indexOf('*');\n const baseA = aBase === -1 ? a.length : aBase + 1;\n const baseB = bBase === -1 ? b.length : bBase + 1;\n if (baseA > baseB) return -1;\n if (baseB > baseA) return 1;\n if (aBase === -1) return 1;\n if (bBase === -1) return -1;\n if (a.length > b.length) return -1;\n if (b.length > a.length) return 1;\n return 0;\n}\n\n/**\n * Whether the dev SSR runner BUNDLES `pkgName` (it is in Vite's\n * `ssr.noExternal`) rather than handing it to Node.\n *\n * This decides which loader a package's backend must be loaded with. A module\n * has to be evaluated by the same loader that evaluates the package everywhere\n * else in the SSR graph, or it runs twice:\n *\n * - `vite.ssrLoadModule('<external pkg>/backend')` transforms that entry and\n * its relative imports even though the package is external, while the app's\n * bare imports of the same package are evaluated by Node — two copies of the\n * package's module state.\n * - a plain `import()` of a BUNDLED package goes to Node, while the app's\n * imports of it are evaluated by Vite — the same split the other way round.\n *\n * Vite matches `noExternal` entries against the bare package name, so a\n * subpath (`pkg/backend`) is covered by an entry for `pkg`.\n */\nexport function isBundledBySsr(pkgName: string, noExternal: unknown): boolean {\n if (noExternal === true) return true;\n const entries = Array.isArray(noExternal)\n ? noExternal\n : noExternal\n ? [noExternal]\n : [];\n return entries.some((entry) =>\n typeof entry === 'string'\n ? entry === pkgName\n : entry instanceof RegExp\n ? entry.test(pkgName)\n : false\n );\n}\n\n/** The dev SSR runner's resolved `noExternal`, wherever this Vite keeps it. */\nexport function ssrNoExternalOf(vite: any): unknown {\n return (\n vite?.config?.ssr?.noExternal ??\n vite?.config?.environments?.ssr?.resolve?.noExternal\n );\n}\n\n/**\n * The backend entry of a package the dev Vite runner owns (see isBundledBySsr):\n * its source backend when it is a source package (resolveSourceBackendEntry),\n * otherwise what `exports` names under the `development` condition and Node's\n * import conditions. A registry install has no src/, so it resolves exactly\n * as before.\n */\nexport function resolveDevBackendEntry(\n pkgRoot: string,\n pkgJson: any\n): string | null {\n return (\n resolveSourceBackendEntry(pkgRoot, pkgJson) ??\n resolveBackendEntry(pkgRoot, pkgJson, [\n 'development',\n ...NODE_IMPORT_CONDITIONS,\n ])\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"linkedDependencies.js","sourceRoot":"","sources":["../../../src/utils/linkedDependencies.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\n/**\n * A linked package found in the app's dependency tree, wherever it is\n * installed: a workspace member, a localized checkout, or a registry install.\n */\nexport interface LinkedDependency {\n packageName: string;\n /**\n * Realpath of the package root whose backend the server loads: the copy the\n * app itself resolves when that copy is a linked package, otherwise the\n * first copy the walk found.\n */\n path: string;\n /**\n * Every distinct installed copy of this package in the tree (realpaths),\n * in the order the walk found them. More than one means the install is not\n * deduplicated; only `path`'s backend is ever loaded.\n */\n copies: { path: string; version?: string }[];\n}\n\n/**\n * A package whose backend providers the server loads at boot: one whose\n * package.json declares `\"linkedPackage\": true`. That flag is the only test —\n * not the package's scope. `@_linked/localize` is in the `@_linked` scope and\n * is a CLI tool, not a linked package; a custom-scope package with the flag is\n * a linked package. The CLI's workspace discovery uses the same flag.\n *\n * The legacy `\"lincd\": true` flag is deliberately NOT enough. Those packages\n * import the old `lincd` core, and loading them at boot is what produced\n * \"Multiple versions of LINCD are loaded\" before boot loading was narrowed to\n * workspace members.\n */\nexport function isLinkedPackageJson(json: any): boolean {\n if (!json || typeof json.name !== 'string') return false;\n return json.linkedPackage === true;\n}\n\n/**\n * Find an installed package the way Node does: `<dir>/node_modules/<name>`,\n * then each parent directory's `node_modules`. Returns null when it is not\n * installed (an optional dependency, say) rather than throwing.\n */\n/**\n * Whether a package has a JavaScript entry the server can import: `main`,\n * `module`, or an `exports` map with a root (`.`) entry or a single string.\n * A linked package can be pure CSS (`@_linked/css`); importing that whole\n * fails with \"Failed to resolve entry\", so the server skips it.\n */\nexport function hasModuleEntry(json: any): boolean {\n if (!json) return false;\n if (typeof json.main === 'string' || typeof json.module === 'string') {\n return true;\n }\n const exp = json.exports;\n if (typeof exp === 'string') return true;\n if (exp && typeof exp === 'object') {\n const keys = Object.keys(exp);\n // Conditions only ({import, require, ...}) describe the root entry.\n if (keys.length > 0 && keys.every((k) => !k.startsWith('.'))) return true;\n return '.' in exp;\n }\n return false;\n}\n\nexport function readInstalledPackage(\n name: string,\n fromDir: string\n): { root: string; json: any } | null {\n let dir = path.resolve(fromDir);\n while (true) {\n const root = path.join(dir, 'node_modules', name);\n const pkgJsonPath = path.join(root, 'package.json');\n if (fs.existsSync(pkgJsonPath)) {\n try {\n return { root, json: JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')) };\n } catch {\n return null;\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Every linked package reachable from the app's `dependencies`, each one AFTER\n * the linked packages it depends on. Providers register middleware in the\n * order they are indexed, so a package's middleware must not run before that\n * of a package it builds on (e.g. anything reading `request.linkedAuth` after\n * @_linked/auth's JWT middleware).\n *\n * Only linked packages are recursed into — the same rule the workspace-only\n * filter used — so the walk reads a few dozen package.json files, not the\n * whole of node_modules. Each package's dependencies are resolved from its\n * REAL location, so a symlinked workspace or localized checkout resolves its\n * own dependencies the way Node will when it loads it — which is how a\n * package installed only nested (`owl/node_modules/@_linked/rdfs`) is found.\n *\n * The walk is keyed by real path, not name, so every installed copy of a\n * package is recorded (`copies`). The copy whose backend is loaded (`path`)\n * is the one the app itself resolves, so it is the same module instance the\n * app's own imports reach; when the app cannot resolve the package at all\n * (it is only installed nested) it is the first copy found.\n */\nexport function discoverLinkedDependencies(\n appDir: string,\n appPackageJson: any\n): LinkedDependency[] {\n const out: LinkedDependency[] = [];\n const byName = new Map<string, LinkedDependency>();\n const visitedRoots = new Set<string>();\n const appName = appPackageJson?.name;\n\n const visit = (name: string, fromDir: string) => {\n if (name === appName) return;\n const installed = readInstalledPackage(name, fromDir);\n if (!installed || !isLinkedPackageJson(installed.json)) return;\n const realRoot = realpathOr(installed.root);\n if (visitedRoots.has(realRoot)) return;\n visitedRoots.add(realRoot);\n let entry = byName.get(name);\n if (!entry) {\n entry = { packageName: name, path: realRoot, copies: [] };\n byName.set(name, entry);\n }\n entry.copies.push({ path: realRoot, version: installed.json.version });\n for (const dep of Object.keys(installed.json.dependencies ?? {})) {\n visit(dep, realRoot);\n }\n // Pushed after its dependencies, once per name: the first copy found\n // fixes the package's place in the order.\n if (!out.includes(entry)) out.push(entry);\n };\n\n for (const name of Object.keys(appPackageJson?.dependencies ?? {})) {\n visit(name, appDir);\n }\n\n for (const entry of out) {\n if (entry.copies.length < 2) continue;\n const fromApp = readInstalledPackage(entry.packageName, appDir);\n if (fromApp && isLinkedPackageJson(fromApp.json)) {\n const appCopy = realpathOr(fromApp.root);\n if (entry.copies.some((c) => c.path === appCopy)) entry.path = appCopy;\n }\n }\n return out;\n}\n\n/**\n * Package name → the names of its direct `dependencies` and `peerDependencies`, for the app\n * (`appPackageJson`) and every package in `packageDirs` (name → package root, as discovery\n * found it). The shape check scopes its direction-I errors with it. A package whose\n * `package.json` cannot be read is left out.\n */\nexport function readDependencyLists(\n packageDirs: Iterable<[string, string]>,\n appPackageJson?: any\n): Record<string, string[]> {\n const lists: Record<string, string[]> = {};\n const add = (name: unknown, json: any) => {\n if (typeof name !== 'string' || !name) return;\n lists[name] = [\n ...new Set([\n ...Object.keys(json?.dependencies ?? {}),\n ...Object.keys(json?.peerDependencies ?? {}),\n ]),\n ].sort();\n };\n if (appPackageJson) add(appPackageJson.name, appPackageJson);\n for (const [name, dir] of packageDirs) {\n try {\n add(name, JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8')));\n } catch {}\n }\n return lists;\n}\n\nfunction realpathOr(p: string): string {\n try {\n return fs.realpathSync(p);\n } catch {\n return p;\n }\n}\n\n/**\n * The conditions a backend entry is resolved with on the Node path. These are\n * the ones Node's ESM loader applies to `import()` — so the file found is the\n * file a bare `import('<pkg>/backend')` from the app would load, and Node's\n * module cache (keyed by real file URL) hands both the same instance.\n * `development` is deliberately absent: it is a Vite/bundler condition, and a\n * production Node runtime never applies it.\n */\nexport const NODE_IMPORT_CONDITIONS = ['import', 'node', 'default'] as const;\n\n/**\n * The absolute path of a package's backend entry file, or null when the\n * package has no backend entry or the file it names does not exist.\n *\n * With `exports`, this follows Node's PACKAGE_EXPORTS_RESOLVE for the\n * `./backend` subpath: an exact `./backend` key wins; otherwise the `./*`-style\n * pattern with the longest matching prefix; condition objects (nested or not)\n * pick the first key that is one of `conditions` or `default`; arrays take the\n * first target that resolves. A package that has `exports` but nothing\n * matching `./backend` has no backend — exactly as Node would refuse to\n * import it.\n *\n * Without `exports`, the legacy layout: `<root>/backend.js`,\n * `<root>/backend/index.js`, and the layout linked packages build to — a\n * `backend.js` next to `main` (`lib/esm/backend.js`).\n *\n * Hand-written rather than via `resolve.exports`: that package also stops at\n * a path and leaves the existence check to the caller, the subset needed is\n * ~50 lines, and the server keeps one less dependency in every app's install.\n */\nexport function resolveBackendEntry(\n pkgRoot: string,\n pkgJson: any,\n conditions: readonly string[] = NODE_IMPORT_CONDITIONS\n): string | null {\n const exists = (rel: string) => packageFile(pkgRoot, rel);\n const exportsField = pkgJson?.exports;\n if (exportsField === undefined || exportsField === null) {\n const candidates = ['backend.js', 'backend/index.js'];\n if (typeof pkgJson?.main === 'string') {\n candidates.push(path.join(path.dirname(pkgJson.main), 'backend.js'));\n }\n candidates.push('lib/esm/backend.js');\n for (const rel of candidates) {\n const file = exists(rel);\n if (file) return file;\n }\n return null;\n }\n return resolveBackendExport(exportsField, conditions, exists);\n}\n\n/** `<pkgRoot>/<rel>` when it is a file inside the package, else null. */\nfunction packageFile(pkgRoot: string, rel: string): string | null {\n const file = path.resolve(pkgRoot, rel);\n // Never let an export target escape the package.\n if (!file.startsWith(path.resolve(pkgRoot) + path.sep)) return null;\n try {\n return fs.statSync(file).isFile() ? file : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The `./backend` subpath of an `exports` map, resolved per Node's\n * PACKAGE_EXPORTS_RESOLVE (see resolveBackendEntry). `probe` turns a matched\n * `./`-relative target into a file, or null when it does not resolve.\n */\nfunction resolveBackendExport(\n exportsField: any,\n conditions: readonly string[],\n probe: (rel: string) => string | null\n): string | null {\n // `\"exports\": \"./x.js\"`, an array, or a condition object at the top level\n // are all sugar for the \".\" subpath only.\n if (\n typeof exportsField !== 'object' ||\n Array.isArray(exportsField) ||\n !Object.keys(exportsField).some((k) => k.startsWith('.'))\n ) {\n return null;\n }\n\n const subpath = './backend';\n let target: any;\n let patternMatch: string | null = null;\n if (Object.prototype.hasOwnProperty.call(exportsField, subpath)) {\n target = exportsField[subpath];\n } else {\n let bestKey: string | null = null;\n for (const key of Object.keys(exportsField)) {\n const star = key.indexOf('*');\n if (star === -1 || key.indexOf('*', star + 1) !== -1) continue;\n const prefix = key.slice(0, star);\n const suffix = key.slice(star + 1);\n if (\n subpath !== prefix &&\n subpath.length >= key.length &&\n subpath.startsWith(prefix) &&\n subpath.endsWith(suffix) &&\n (bestKey === null || patternKeyCompare(bestKey, key) > 0)\n ) {\n bestKey = key;\n patternMatch = subpath.slice(prefix.length, subpath.length - suffix.length);\n }\n }\n if (bestKey === null) return null;\n target = exportsField[bestKey];\n }\n\n const resolveTarget = (t: any): string | null => {\n if (typeof t === 'string') {\n if (!t.startsWith('./')) return null;\n const rel = patternMatch === null ? t : t.split('*').join(patternMatch);\n return probe(rel);\n }\n if (Array.isArray(t)) {\n for (const item of t) {\n const file = resolveTarget(item);\n if (file) return file;\n }\n return null;\n }\n if (t && typeof t === 'object') {\n for (const key of Object.keys(t)) {\n if (key === 'default' || conditions.includes(key)) {\n // Node takes the first matching condition even if its target fails\n // to resolve; it does not fall through to later keys.\n return resolveTarget(t[key]);\n }\n }\n }\n return null;\n };\n return resolveTarget(target);\n}\n\n/**\n * The SOURCE file a dev Vite runner loads for `<pkg>/backend` when the\n * package is a source package — its real root has a `src/` directory, which is\n * what makes `linked`'s Vite config register it as a workspace (cli\n * `discoverWorkspaces` / `readSourcePackage`). Null when the package has no\n * `src/` or no source backend; the caller then falls back to\n * resolveBackendEntry (a built `lib/` copy, or nothing).\n *\n * Mirrors the cli's `resolveWorkspaceSpecifier` for the `./backend` subpath,\n * which runs BEFORE `exports`: `src/backend.tsx`, `src/backend.ts`, then the\n * package's own `exports` under `import`/`default` with a `./lib/esm/X.js`\n * target mapped to `src/X.tsx` / `src/X.ts` (any other `./lib/esm/` target to\n * the same path under src). So an unbuilt workspace, whose `exports` name only\n * `lib/esm`, still has its backend found, and a built one reports the src file\n * Vite actually evaluates rather than its stale `lib/`.\n *\n * Only meaningful when the dev Vite runner owns the package; Node (production,\n * external packages) loads `lib/` and never sees src.\n */\nexport function resolveSourceBackendEntry(\n pkgRoot: string,\n pkgJson: any\n): string | null {\n try {\n if (!fs.statSync(path.join(pkgRoot, 'src')).isDirectory()) return null;\n } catch {\n return null;\n }\n for (const rel of ['src/backend.tsx', 'src/backend.ts']) {\n const file = packageFile(pkgRoot, rel);\n if (file) return file;\n }\n const exportsField = pkgJson?.exports;\n if (exportsField === undefined || exportsField === null) return null;\n const libPrefix = './lib/esm/';\n return resolveBackendExport(exportsField, ['import'], (rel) => {\n if (!rel.startsWith(libPrefix)) return null;\n const sub = rel.slice(libPrefix.length);\n if (/\\.jsx?$/.test(sub)) {\n const base = 'src/' + sub.replace(/\\.jsx?$/, '');\n return packageFile(pkgRoot, base + '.tsx') ?? packageFile(pkgRoot, base + '.ts');\n }\n return packageFile(pkgRoot, 'src/' + sub);\n });\n}\n\n/** Node's PATTERN_KEY_COMPARE: < 0 means `a` is the more specific pattern. */\nfunction patternKeyCompare(a: string, b: string): number {\n const aBase = a.indexOf('*');\n const bBase = b.indexOf('*');\n const baseA = aBase === -1 ? a.length : aBase + 1;\n const baseB = bBase === -1 ? b.length : bBase + 1;\n if (baseA > baseB) return -1;\n if (baseB > baseA) return 1;\n if (aBase === -1) return 1;\n if (bBase === -1) return -1;\n if (a.length > b.length) return -1;\n if (b.length > a.length) return 1;\n return 0;\n}\n\n/**\n * Whether the dev SSR runner BUNDLES `pkgName` (it is in Vite's\n * `ssr.noExternal`) rather than handing it to Node.\n *\n * This decides which loader a package's backend must be loaded with. A module\n * has to be evaluated by the same loader that evaluates the package everywhere\n * else in the SSR graph, or it runs twice:\n *\n * - `vite.ssrLoadModule('<external pkg>/backend')` transforms that entry and\n * its relative imports even though the package is external, while the app's\n * bare imports of the same package are evaluated by Node — two copies of the\n * package's module state.\n * - a plain `import()` of a BUNDLED package goes to Node, while the app's\n * imports of it are evaluated by Vite — the same split the other way round.\n *\n * Vite matches `noExternal` entries against the bare package name, so a\n * subpath (`pkg/backend`) is covered by an entry for `pkg`.\n */\nexport function isBundledBySsr(pkgName: string, noExternal: unknown): boolean {\n if (noExternal === true) return true;\n const entries = Array.isArray(noExternal)\n ? noExternal\n : noExternal\n ? [noExternal]\n : [];\n return entries.some((entry) =>\n typeof entry === 'string'\n ? entry === pkgName\n : entry instanceof RegExp\n ? entry.test(pkgName)\n : false\n );\n}\n\n/** The dev SSR runner's resolved `noExternal`, wherever this Vite keeps it. */\nexport function ssrNoExternalOf(vite: any): unknown {\n return (\n vite?.config?.ssr?.noExternal ??\n vite?.config?.environments?.ssr?.resolve?.noExternal\n );\n}\n\n/**\n * The backend entry of a package the dev Vite runner owns (see isBundledBySsr):\n * its source backend when it is a source package (resolveSourceBackendEntry),\n * otherwise what `exports` names under the `development` condition and Node's\n * import conditions. A registry install has no src/, so it resolves exactly\n * as before.\n */\nexport function resolveDevBackendEntry(\n pkgRoot: string,\n pkgJson: any\n): string | null {\n return (\n resolveSourceBackendEntry(pkgRoot, pkgJson) ??\n resolveBackendEntry(pkgRoot, pkgJson, [\n 'development',\n ...NODE_IMPORT_CONDITIONS,\n ])\n );\n}\n"]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ontology sync and the shape check, as the server runs them before its shape sync: at boot and
|
|
3
|
+
* when the host asks for a re-sync (`POST /admin/sync-shapes`).
|
|
4
|
+
*
|
|
5
|
+
* - The ontology sync (core `syncOntologies`) writes each registered package's
|
|
6
|
+
* `rdfs:subClassOf` triples into the app's default dataset, skipping a package whose stored
|
|
7
|
+
* hash matches, so the store's RDFS view infers the class hierarchy.
|
|
8
|
+
* - The shape check (core `checkShapeHierarchy`) compares the registered shapes with that
|
|
9
|
+
* hierarchy. A running app is never stopped by it: its findings are logged and counted.
|
|
10
|
+
* - The in-memory class hierarchy (core `loadOntologyHierarchy`) is loaded also when the sync
|
|
11
|
+
* is off; the sync loads it itself when it runs.
|
|
12
|
+
* - Before either, every linked package directory the server discovered registers its ontologies
|
|
13
|
+
* (core `registerInstalledOntologies`): a package the app only deep-imports shape files from
|
|
14
|
+
* never evaluates its register modules, so its data would be missing from both. A source
|
|
15
|
+
* package without built register modules (a workspace package in dev) registers through
|
|
16
|
+
* `importModule` (Vite's `ssrLoadModule`), when the caller passes one.
|
|
17
|
+
*
|
|
18
|
+
* The four core modules (`ontology/syncOntologies`, `shapes/checkShapeHierarchy`,
|
|
19
|
+
* `ontology/hierarchy`, `ontology/registerInstalled`) are server only and not in core's index, so
|
|
20
|
+
* {@link loadOntologyCore} imports them by their fixed subpaths when the phase runs; the core
|
|
21
|
+
* this server depends on has all of them. Kept apart from `LinkedServer` so the order and the failure handling can be tested without
|
|
22
|
+
* booting a server.
|
|
23
|
+
*/
|
|
24
|
+
import type { BootOrphanScope } from './syncShapes.js';
|
|
25
|
+
/** The subset of core's ontology and shape-check API this module calls. */
|
|
26
|
+
export interface OntologyCore {
|
|
27
|
+
syncOntologies(ds: unknown, options: {
|
|
28
|
+
orphanScope: BootOrphanScope;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
written: string[];
|
|
31
|
+
skipped: string[];
|
|
32
|
+
pruned: string[];
|
|
33
|
+
}>;
|
|
34
|
+
checkShapeHierarchy(options: {
|
|
35
|
+
dependencies?: Record<string, string[]>;
|
|
36
|
+
}): Promise<Array<{
|
|
37
|
+
kind: string;
|
|
38
|
+
severity: 'error' | 'warning';
|
|
39
|
+
shape?: string;
|
|
40
|
+
iri?: string;
|
|
41
|
+
message: string;
|
|
42
|
+
}>>;
|
|
43
|
+
/** Loads the in-memory class hierarchy. Optional so a test double can leave it out. */
|
|
44
|
+
loadOntologyHierarchy?: () => Promise<unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* Registers the ontologies of the installed packages in the given directories. Optional so a
|
|
47
|
+
* test double can leave it out.
|
|
48
|
+
*/
|
|
49
|
+
registerInstalledOntologies?: (packageDirs: string[], options?: {
|
|
50
|
+
importModule?: (file: string) => Promise<unknown>;
|
|
51
|
+
}) => Promise<string[]>;
|
|
52
|
+
}
|
|
53
|
+
/** What the ontology sync and the shape check did, as `POST /admin/sync-shapes` reports it. */
|
|
54
|
+
export interface OntologyPhaseResult {
|
|
55
|
+
/** Packages whose hierarchy was written, and packages left as they were (unchanged or unreadable). */
|
|
56
|
+
ontologies: {
|
|
57
|
+
written: string[];
|
|
58
|
+
skipped: string[];
|
|
59
|
+
};
|
|
60
|
+
/** The shape check's findings, counted by severity. */
|
|
61
|
+
shapeCheck: {
|
|
62
|
+
errors: number;
|
|
63
|
+
warnings: number;
|
|
64
|
+
};
|
|
65
|
+
/** The first error the sync or the check threw; the phase went on without it. */
|
|
66
|
+
failure?: Error;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether the server syncs shapes (and so ontologies) into its app data: off when `linked.config`
|
|
70
|
+
* sets `syncShapesOnBoot: false` or the environment sets `LINKED_SYNC_SHAPES_ON_BOOT=false`. One
|
|
71
|
+
* switch for both syncs, at boot and on the host's request.
|
|
72
|
+
*/
|
|
73
|
+
export declare function shapeSyncOn(env: Record<string, string | undefined>, configFlag: unknown): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Whether the boot syncs ontologies: when shape sync is on ({@link shapeSyncOn}) and the
|
|
76
|
+
* environment does not opt out with `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`.
|
|
77
|
+
*/
|
|
78
|
+
export declare function bootSyncsOntologies(env: Record<string, string | undefined>, configFlag?: unknown): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Import core's ontology sync, shape check, hierarchy loader and installed packages'
|
|
81
|
+
* registration.
|
|
82
|
+
*/
|
|
83
|
+
export declare function loadOntologyCore(): Promise<OntologyCore>;
|
|
84
|
+
/**
|
|
85
|
+
* The package directories whose ontologies register before the sync: every linked package
|
|
86
|
+
* discovered in the app's dependency tree, then the app's own workspace packages (a CN clone's
|
|
87
|
+
* `packages/<slug>-ont` is one no package imports, so the dependency walk never reaches it).
|
|
88
|
+
* Order kept, duplicates dropped.
|
|
89
|
+
*/
|
|
90
|
+
export declare function ontologyPackageDirs(linkedPackageDirs: Iterable<string>, workspacePackageDirs: Iterable<string>): string[];
|
|
91
|
+
/**
|
|
92
|
+
* In dev, the loader for source register modules: Vite's `ssrLoadModule`, so a workspace package
|
|
93
|
+
* that was never built registers on the core the SSR graph uses. Undefined without Vite (a built
|
|
94
|
+
* app), where source packages are skipped.
|
|
95
|
+
*/
|
|
96
|
+
export declare function sourceModuleImporter(vite: {
|
|
97
|
+
ssrLoadModule?: (url: string) => Promise<unknown>;
|
|
98
|
+
} | undefined | null): ((file: string) => Promise<unknown>) | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Register the ontologies of the installed linked packages in `packageDirs` (package roots), sync
|
|
101
|
+
* the registered ontologies into `ds` (when `sync` is set and there is a dataset), then run the
|
|
102
|
+
* shape check. Never throws: a failed registration is logged and the phase goes on; a failed sync is logged and returned as `failure`, and the
|
|
103
|
+
* check still runs; a failed check likewise. Logs one line for the sync, one summary line for
|
|
104
|
+
* the check and one line per finding.
|
|
105
|
+
*/
|
|
106
|
+
export declare function runOntologyPhase(ds: unknown, { core, sync, orphanScope, dependencies, packageDirs, importModule, log, warn, }: {
|
|
107
|
+
core: OntologyCore;
|
|
108
|
+
sync: boolean;
|
|
109
|
+
orphanScope: BootOrphanScope;
|
|
110
|
+
dependencies?: Record<string, string[]>;
|
|
111
|
+
/** Every linked package directory the server discovered (transitively). */
|
|
112
|
+
packageDirs?: string[];
|
|
113
|
+
/**
|
|
114
|
+
* Imports a source register module (`src/ontologies/*.register.ts`) of a package directory
|
|
115
|
+
* without built ones; in dev, Vite's `ssrLoadModule`.
|
|
116
|
+
*/
|
|
117
|
+
importModule?: (file: string) => Promise<unknown>;
|
|
118
|
+
log?: (message: string) => void;
|
|
119
|
+
warn?: (message: string, err?: unknown) => void;
|
|
120
|
+
}): Promise<OntologyPhaseResult>;
|
|
121
|
+
/**
|
|
122
|
+
* The host's re-sync: the ontology phase, then the shape sync, which runs also when the ontology
|
|
123
|
+
* phase failed. The ontology phase's failure is then thrown, so the route answers with it, as
|
|
124
|
+
* it does for a failed shape sync.
|
|
125
|
+
*/
|
|
126
|
+
export declare function syncOntologiesThenShapes<T extends object>(ontologyPhase: () => Promise<OntologyPhaseResult>, shapeSync: () => Promise<T>): Promise<T & Pick<OntologyPhaseResult, 'ontologies' | 'shapeCheck'>>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the server syncs shapes (and so ontologies) into its app data: off when `linked.config`
|
|
3
|
+
* sets `syncShapesOnBoot: false` or the environment sets `LINKED_SYNC_SHAPES_ON_BOOT=false`. One
|
|
4
|
+
* switch for both syncs, at boot and on the host's request.
|
|
5
|
+
*/
|
|
6
|
+
export function shapeSyncOn(env, configFlag) {
|
|
7
|
+
return configFlag !== false && env.LINKED_SYNC_SHAPES_ON_BOOT !== 'false';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Whether the boot syncs ontologies: when shape sync is on ({@link shapeSyncOn}) and the
|
|
11
|
+
* environment does not opt out with `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`.
|
|
12
|
+
*/
|
|
13
|
+
export function bootSyncsOntologies(env, configFlag) {
|
|
14
|
+
return shapeSyncOn(env, configFlag) && env.LINKED_SYNC_ONTOLOGIES_ON_BOOT !== 'false';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Import core's ontology sync, shape check, hierarchy loader and installed packages'
|
|
18
|
+
* registration.
|
|
19
|
+
*/
|
|
20
|
+
export async function loadOntologyCore() {
|
|
21
|
+
const sync = await import('@_linked/core/ontology/syncOntologies');
|
|
22
|
+
const check = await import('@_linked/core/shapes/checkShapeHierarchy');
|
|
23
|
+
const hierarchy = await import('@_linked/core/ontology/hierarchy');
|
|
24
|
+
const registerInstalled = await import('@_linked/core/ontology/registerInstalled');
|
|
25
|
+
return {
|
|
26
|
+
syncOntologies: (ds, options) => sync.syncOntologies(ds, options),
|
|
27
|
+
checkShapeHierarchy: (options) => check.checkShapeHierarchy(options),
|
|
28
|
+
loadOntologyHierarchy: () => hierarchy.loadOntologyHierarchy(),
|
|
29
|
+
registerInstalledOntologies: (packageDirs, options) => registerInstalled.registerInstalledOntologies(packageDirs, options),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The package directories whose ontologies register before the sync: every linked package
|
|
34
|
+
* discovered in the app's dependency tree, then the app's own workspace packages (a CN clone's
|
|
35
|
+
* `packages/<slug>-ont` is one no package imports, so the dependency walk never reaches it).
|
|
36
|
+
* Order kept, duplicates dropped.
|
|
37
|
+
*/
|
|
38
|
+
export function ontologyPackageDirs(linkedPackageDirs, workspacePackageDirs) {
|
|
39
|
+
return [...new Set([...linkedPackageDirs, ...workspacePackageDirs])];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* In dev, the loader for source register modules: Vite's `ssrLoadModule`, so a workspace package
|
|
43
|
+
* that was never built registers on the core the SSR graph uses. Undefined without Vite (a built
|
|
44
|
+
* app), where source packages are skipped.
|
|
45
|
+
*/
|
|
46
|
+
export function sourceModuleImporter(vite) {
|
|
47
|
+
const ssrLoadModule = vite === null || vite === void 0 ? void 0 : vite.ssrLoadModule;
|
|
48
|
+
return typeof ssrLoadModule === 'function' ? (file) => ssrLoadModule.call(vite, file) : undefined;
|
|
49
|
+
}
|
|
50
|
+
const asError = (err) => (err instanceof Error ? err : new Error(String(err)));
|
|
51
|
+
const list = (names) => (names.length ? names.join(', ') : 'none');
|
|
52
|
+
/**
|
|
53
|
+
* Register the ontologies of the installed linked packages in `packageDirs` (package roots), sync
|
|
54
|
+
* the registered ontologies into `ds` (when `sync` is set and there is a dataset), then run the
|
|
55
|
+
* shape check. Never throws: a failed registration is logged and the phase goes on; a failed sync is logged and returned as `failure`, and the
|
|
56
|
+
* check still runs; a failed check likewise. Logs one line for the sync, one summary line for
|
|
57
|
+
* the check and one line per finding.
|
|
58
|
+
*/
|
|
59
|
+
export async function runOntologyPhase(ds, { core, sync, orphanScope, dependencies, packageDirs = [], importModule, log = console.log, warn = console.warn, }) {
|
|
60
|
+
var _a;
|
|
61
|
+
const result = {
|
|
62
|
+
ontologies: { written: [], skipped: [] },
|
|
63
|
+
shapeCheck: { errors: 0, warnings: 0 },
|
|
64
|
+
};
|
|
65
|
+
if (core.registerInstalledOntologies && packageDirs.length) {
|
|
66
|
+
try {
|
|
67
|
+
if (importModule)
|
|
68
|
+
await core.registerInstalledOntologies(packageDirs, { importModule });
|
|
69
|
+
else
|
|
70
|
+
await core.registerInstalledOntologies(packageDirs);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
warn("[LinkedServer] registering the installed packages' ontologies failed:", err);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
let hierarchyLoaded = false;
|
|
77
|
+
if (sync && ds) {
|
|
78
|
+
try {
|
|
79
|
+
const { written, skipped, pruned } = await core.syncOntologies(ds, { orphanScope });
|
|
80
|
+
result.ontologies = { written, skipped };
|
|
81
|
+
hierarchyLoaded = true;
|
|
82
|
+
log(`[LinkedServer] ontology sync: written ${list(written)}; skipped ${list(skipped)}` +
|
|
83
|
+
(orphanScope === 'all' ? `; pruned ${list(pruned)}` : ''));
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
result.failure = asError(err);
|
|
87
|
+
warn('[LinkedServer] ontology sync failed:', err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!hierarchyLoaded && core.loadOntologyHierarchy) {
|
|
91
|
+
try {
|
|
92
|
+
await core.loadOntologyHierarchy();
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
warn('[LinkedServer] loading the class hierarchy failed:', err);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const findings = await core.checkShapeHierarchy({ dependencies });
|
|
100
|
+
const errors = findings.filter((f) => f.severity === 'error').length;
|
|
101
|
+
result.shapeCheck = { errors, warnings: findings.length - errors };
|
|
102
|
+
log(`[LinkedServer] shape check: ${errors} error(s), ${findings.length - errors} warning(s)`);
|
|
103
|
+
for (const f of findings)
|
|
104
|
+
warn(`[LinkedServer] shape check ${f.severity} (${f.kind}): ${f.message}`);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
(_a = result.failure) !== null && _a !== void 0 ? _a : (result.failure = asError(err));
|
|
108
|
+
warn('[LinkedServer] shape check failed:', err);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The host's re-sync: the ontology phase, then the shape sync, which runs also when the ontology
|
|
114
|
+
* phase failed. The ontology phase's failure is then thrown, so the route answers with it, as
|
|
115
|
+
* it does for a failed shape sync.
|
|
116
|
+
*/
|
|
117
|
+
export async function syncOntologiesThenShapes(ontologyPhase, shapeSync) {
|
|
118
|
+
const { ontologies, shapeCheck, failure } = await ontologyPhase();
|
|
119
|
+
const shapes = await shapeSync();
|
|
120
|
+
if (failure)
|
|
121
|
+
throw failure;
|
|
122
|
+
return { ...shapes, ontologies, shapeCheck };
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=ontologySync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ontologySync.js","sourceRoot":"","sources":["../../../src/utils/ontologySync.ts"],"names":[],"mappings":"AAwDA;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,GAAuC,EACvC,UAAmB;IAEnB,OAAO,UAAU,KAAK,KAAK,IAAI,GAAG,CAAC,0BAA0B,KAAK,OAAO,CAAC;AAC5E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAuC,EACvC,UAAoB;IAEpB,OAAO,WAAW,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,GAAG,CAAC,8BAA8B,KAAK,OAAO,CAAC;AACxF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,uCAAuC,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,0CAA0C,CAAC,CAAC;IACvE,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,kCAAkC,CAAC,CAAC;IACnE,MAAM,iBAAiB,GAAG,MAAM,MAAM,CAAC,0CAA0C,CAAC,CAAC;IACnF,OAAO;QACL,cAAc,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,CAC9B,IAAI,CAAC,cAAc,CAAC,EAA+C,EAAE,OAAO,CAAC;QAC/E,mBAAmB,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,CAAC;QACpE,qBAAqB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,qBAAqB,EAAE;QAC9D,2BAA2B,EAAE,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,CACpD,iBAAiB,CAAC,2BAA2B,CAAC,WAAW,EAAE,OAAO,CAAC;KACtE,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,iBAAmC,EACnC,oBAAsC;IAEtC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,iBAAiB,EAAE,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC;AACvE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAA4E;IAE5E,MAAM,aAAa,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,aAAa,CAAC;IAC1C,OAAO,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACpG,CAAC;AAED,MAAM,OAAO,GAAG,CAAC,GAAY,EAAS,EAAE,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAC/F,MAAM,IAAI,GAAG,CAAC,KAAe,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7E;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,EAAW,EACX,EACE,IAAI,EACJ,IAAI,EACJ,WAAW,EACX,YAAY,EACZ,WAAW,GAAG,EAAE,EAChB,YAAY,EACZ,GAAG,GAAG,OAAO,CAAC,GAAG,EACjB,IAAI,GAAG,OAAO,CAAC,IAAI,GAepB;;IAED,MAAM,MAAM,GAAwB;QAClC,UAAU,EAAE,EAAC,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAC;QACtC,UAAU,EAAE,EAAC,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAC;KACrC,CAAC;IAEF,IAAI,IAAI,CAAC,2BAA2B,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;QAC3D,IAAI,CAAC;YACH,IAAI,YAAY;gBAAE,MAAM,IAAI,CAAC,2BAA2B,CAAC,WAAW,EAAE,EAAC,YAAY,EAAC,CAAC,CAAC;;gBACjF,MAAM,IAAI,CAAC,2BAA2B,CAAC,WAAW,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,uEAAuE,EAAE,GAAG,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IAED,IAAI,eAAe,GAAG,KAAK,CAAC;IAC5B,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QACf,IAAI,CAAC;YACH,MAAM,EAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAC,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,EAAC,WAAW,EAAC,CAAC,CAAC;YAChF,MAAM,CAAC,UAAU,GAAG,EAAC,OAAO,EAAE,OAAO,EAAC,CAAC;YACvC,eAAe,GAAG,IAAI,CAAC;YACvB,GAAG,CACD,yCAAyC,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,CAAC,OAAO,CAAC,EAAE;gBAChF,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC5D,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IACD,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACnD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACrC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,oDAAoD,EAAE,GAAG,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,EAAC,YAAY,EAAC,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;QACrE,MAAM,CAAC,UAAU,GAAG,EAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,GAAG,MAAM,EAAC,CAAC;QACjE,GAAG,CAAC,+BAA+B,MAAM,cAAc,QAAQ,CAAC,MAAM,GAAG,MAAM,aAAa,CAAC,CAAC;QAC9F,KAAK,MAAM,CAAC,IAAI,QAAQ;YAAE,IAAI,CAAC,8BAA8B,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACvG,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAA,MAAM,CAAC,OAAO,oCAAd,MAAM,CAAC,OAAO,GAAK,OAAO,CAAC,GAAG,CAAC,EAAC;QAChC,IAAI,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,aAAiD,EACjD,SAA2B;IAE3B,MAAM,EAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAC,GAAG,MAAM,aAAa,EAAE,CAAC;IAChE,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;IACjC,IAAI,OAAO;QAAE,MAAM,OAAO,CAAC;IAC3B,OAAO,EAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAAU,EAAC,CAAC;AAC7C,CAAC","sourcesContent":["/**\n * Ontology sync and the shape check, as the server runs them before its shape sync: at boot and\n * when the host asks for a re-sync (`POST /admin/sync-shapes`).\n *\n * - The ontology sync (core `syncOntologies`) writes each registered package's\n * `rdfs:subClassOf` triples into the app's default dataset, skipping a package whose stored\n * hash matches, so the store's RDFS view infers the class hierarchy.\n * - The shape check (core `checkShapeHierarchy`) compares the registered shapes with that\n * hierarchy. A running app is never stopped by it: its findings are logged and counted.\n * - The in-memory class hierarchy (core `loadOntologyHierarchy`) is loaded also when the sync\n * is off; the sync loads it itself when it runs.\n * - Before either, every linked package directory the server discovered registers its ontologies\n * (core `registerInstalledOntologies`): a package the app only deep-imports shape files from\n * never evaluates its register modules, so its data would be missing from both. A source\n * package without built register modules (a workspace package in dev) registers through\n * `importModule` (Vite's `ssrLoadModule`), when the caller passes one.\n *\n * The four core modules (`ontology/syncOntologies`, `shapes/checkShapeHierarchy`,\n * `ontology/hierarchy`, `ontology/registerInstalled`) are server only and not in core's index, so\n * {@link loadOntologyCore} imports them by their fixed subpaths when the phase runs; the core\n * this server depends on has all of them. Kept apart from `LinkedServer` so the order and the failure handling can be tested without\n * booting a server.\n */\nimport type {BootOrphanScope} from './syncShapes.js';\n\n/** The subset of core's ontology and shape-check API this module calls. */\nexport interface OntologyCore {\n syncOntologies(\n ds: unknown,\n options: {orphanScope: BootOrphanScope},\n ): Promise<{written: string[]; skipped: string[]; pruned: string[]}>;\n checkShapeHierarchy(options: {dependencies?: Record<string, string[]>}): Promise<\n Array<{kind: string; severity: 'error' | 'warning'; shape?: string; iri?: string; message: string}>\n >;\n /** Loads the in-memory class hierarchy. Optional so a test double can leave it out. */\n loadOntologyHierarchy?: () => Promise<unknown>;\n /**\n * Registers the ontologies of the installed packages in the given directories. Optional so a\n * test double can leave it out.\n */\n registerInstalledOntologies?: (\n packageDirs: string[],\n options?: {importModule?: (file: string) => Promise<unknown>},\n ) => Promise<string[]>;\n}\n\n/** What the ontology sync and the shape check did, as `POST /admin/sync-shapes` reports it. */\nexport interface OntologyPhaseResult {\n /** Packages whose hierarchy was written, and packages left as they were (unchanged or unreadable). */\n ontologies: {written: string[]; skipped: string[]};\n /** The shape check's findings, counted by severity. */\n shapeCheck: {errors: number; warnings: number};\n /** The first error the sync or the check threw; the phase went on without it. */\n failure?: Error;\n}\n\n/**\n * Whether the server syncs shapes (and so ontologies) into its app data: off when `linked.config`\n * sets `syncShapesOnBoot: false` or the environment sets `LINKED_SYNC_SHAPES_ON_BOOT=false`. One\n * switch for both syncs, at boot and on the host's request.\n */\nexport function shapeSyncOn(\n env: Record<string, string | undefined>,\n configFlag: unknown,\n): boolean {\n return configFlag !== false && env.LINKED_SYNC_SHAPES_ON_BOOT !== 'false';\n}\n\n/**\n * Whether the boot syncs ontologies: when shape sync is on ({@link shapeSyncOn}) and the\n * environment does not opt out with `LINKED_SYNC_ONTOLOGIES_ON_BOOT=false`.\n */\nexport function bootSyncsOntologies(\n env: Record<string, string | undefined>,\n configFlag?: unknown,\n): boolean {\n return shapeSyncOn(env, configFlag) && env.LINKED_SYNC_ONTOLOGIES_ON_BOOT !== 'false';\n}\n\n/**\n * Import core's ontology sync, shape check, hierarchy loader and installed packages'\n * registration.\n */\nexport async function loadOntologyCore(): Promise<OntologyCore> {\n const sync = await import('@_linked/core/ontology/syncOntologies');\n const check = await import('@_linked/core/shapes/checkShapeHierarchy');\n const hierarchy = await import('@_linked/core/ontology/hierarchy');\n const registerInstalled = await import('@_linked/core/ontology/registerInstalled');\n return {\n syncOntologies: (ds, options) =>\n sync.syncOntologies(ds as Parameters<typeof sync.syncOntologies>[0], options),\n checkShapeHierarchy: (options) => check.checkShapeHierarchy(options),\n loadOntologyHierarchy: () => hierarchy.loadOntologyHierarchy(),\n registerInstalledOntologies: (packageDirs, options) =>\n registerInstalled.registerInstalledOntologies(packageDirs, options),\n };\n}\n\n/**\n * The package directories whose ontologies register before the sync: every linked package\n * discovered in the app's dependency tree, then the app's own workspace packages (a CN clone's\n * `packages/<slug>-ont` is one no package imports, so the dependency walk never reaches it).\n * Order kept, duplicates dropped.\n */\nexport function ontologyPackageDirs(\n linkedPackageDirs: Iterable<string>,\n workspacePackageDirs: Iterable<string>,\n): string[] {\n return [...new Set([...linkedPackageDirs, ...workspacePackageDirs])];\n}\n\n/**\n * In dev, the loader for source register modules: Vite's `ssrLoadModule`, so a workspace package\n * that was never built registers on the core the SSR graph uses. Undefined without Vite (a built\n * app), where source packages are skipped.\n */\nexport function sourceModuleImporter(\n vite: {ssrLoadModule?: (url: string) => Promise<unknown>} | undefined | null,\n): ((file: string) => Promise<unknown>) | undefined {\n const ssrLoadModule = vite?.ssrLoadModule;\n return typeof ssrLoadModule === 'function' ? (file) => ssrLoadModule.call(vite, file) : undefined;\n}\n\nconst asError = (err: unknown): Error => (err instanceof Error ? err : new Error(String(err)));\nconst list = (names: string[]) => (names.length ? names.join(', ') : 'none');\n\n/**\n * Register the ontologies of the installed linked packages in `packageDirs` (package roots), sync\n * the registered ontologies into `ds` (when `sync` is set and there is a dataset), then run the\n * shape check. Never throws: a failed registration is logged and the phase goes on; a failed sync is logged and returned as `failure`, and the\n * check still runs; a failed check likewise. Logs one line for the sync, one summary line for\n * the check and one line per finding.\n */\nexport async function runOntologyPhase(\n ds: unknown,\n {\n core,\n sync,\n orphanScope,\n dependencies,\n packageDirs = [],\n importModule,\n log = console.log,\n warn = console.warn,\n }: {\n core: OntologyCore;\n sync: boolean;\n orphanScope: BootOrphanScope;\n dependencies?: Record<string, string[]>;\n /** Every linked package directory the server discovered (transitively). */\n packageDirs?: string[];\n /**\n * Imports a source register module (`src/ontologies/*.register.ts`) of a package directory\n * without built ones; in dev, Vite's `ssrLoadModule`.\n */\n importModule?: (file: string) => Promise<unknown>;\n log?: (message: string) => void;\n warn?: (message: string, err?: unknown) => void;\n },\n): Promise<OntologyPhaseResult> {\n const result: OntologyPhaseResult = {\n ontologies: {written: [], skipped: []},\n shapeCheck: {errors: 0, warnings: 0},\n };\n\n if (core.registerInstalledOntologies && packageDirs.length) {\n try {\n if (importModule) await core.registerInstalledOntologies(packageDirs, {importModule});\n else await core.registerInstalledOntologies(packageDirs);\n } catch (err) {\n warn(\"[LinkedServer] registering the installed packages' ontologies failed:\", err);\n }\n }\n\n let hierarchyLoaded = false;\n if (sync && ds) {\n try {\n const {written, skipped, pruned} = await core.syncOntologies(ds, {orphanScope});\n result.ontologies = {written, skipped};\n hierarchyLoaded = true;\n log(\n `[LinkedServer] ontology sync: written ${list(written)}; skipped ${list(skipped)}` +\n (orphanScope === 'all' ? `; pruned ${list(pruned)}` : ''),\n );\n } catch (err) {\n result.failure = asError(err);\n warn('[LinkedServer] ontology sync failed:', err);\n }\n }\n if (!hierarchyLoaded && core.loadOntologyHierarchy) {\n try {\n await core.loadOntologyHierarchy();\n } catch (err) {\n warn('[LinkedServer] loading the class hierarchy failed:', err);\n }\n }\n\n try {\n const findings = await core.checkShapeHierarchy({dependencies});\n const errors = findings.filter((f) => f.severity === 'error').length;\n result.shapeCheck = {errors, warnings: findings.length - errors};\n log(`[LinkedServer] shape check: ${errors} error(s), ${findings.length - errors} warning(s)`);\n for (const f of findings) warn(`[LinkedServer] shape check ${f.severity} (${f.kind}): ${f.message}`);\n } catch (err) {\n result.failure ??= asError(err);\n warn('[LinkedServer] shape check failed:', err);\n }\n return result;\n}\n\n/**\n * The host's re-sync: the ontology phase, then the shape sync, which runs also when the ontology\n * phase failed. The ontology phase's failure is then thrown, so the route answers with it, as\n * it does for a failed shape sync.\n */\nexport async function syncOntologiesThenShapes<T extends object>(\n ontologyPhase: () => Promise<OntologyPhaseResult>,\n shapeSync: () => Promise<T>,\n): Promise<T & Pick<OntologyPhaseResult, 'ontologies' | 'shapeCheck'>> {\n const {ontologies, shapeCheck, failure} = await ontologyPhase();\n const shapes = await shapeSync();\n if (failure) throw failure;\n return {...shapes, ontologies, shapeCheck};\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settle with `promise`, or reject with `message` after `ms`. The operation is not
|
|
3
|
+
* cancelled; the timeout only stops a caller (or a queue) from waiting on it forever.
|
|
4
|
+
*/
|
|
5
|
+
export declare function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T>;
|
|
6
|
+
/** A positive integer from the environment, else `fallback`. */
|
|
7
|
+
export declare function envMs(name: string, fallback: number, env?: Record<string, string | undefined>): number;
|