@_linked/server 2.24.10 → 2.25.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 +12 -0
- package/lib/esm/shapes/LinkedServer.js +9 -8
- package/lib/esm/shapes/LinkedServer.js.map +1 -1
- package/lib/esm/shapes/quadstores/GatewayStore.d.ts +85 -0
- package/lib/esm/shapes/quadstores/GatewayStore.js +143 -0
- package/lib/esm/shapes/quadstores/GatewayStore.js.map +1 -0
- package/lib/esm/utils/linkedDependencies.d.ts +28 -0
- package/lib/esm/utils/linkedDependencies.js +85 -13
- package/lib/esm/utils/linkedDependencies.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +69 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { IDataset } from '@_linked/core/interfaces/IDataset';
|
|
2
|
+
import type { SelectQuery } from '@_linked/core/queries/SelectQuery';
|
|
3
|
+
import type { CountQuery } from '@_linked/core/queries/CountQuery';
|
|
4
|
+
import type { AskQuery } from '@_linked/core/queries/AskQuery';
|
|
5
|
+
import type { UpdateQuery } from '@_linked/core/queries/UpdateQuery';
|
|
6
|
+
import type { CreateQuery } from '@_linked/core/queries/CreateQuery';
|
|
7
|
+
import type { DeleteQuery, DeleteResponse } from '@_linked/core/queries/DeleteQuery';
|
|
8
|
+
import type { SelectResult, UpdateResult, CreateResult } from '@_linked/core/queries/IntermediateRepresentation';
|
|
9
|
+
/**
|
|
10
|
+
* Constructor argument for `new GatewayStore(config)` — the `config` object of a
|
|
11
|
+
* `linked.backend.datasets.json` entry, passed verbatim by `loadStores`.
|
|
12
|
+
*/
|
|
13
|
+
export interface GatewayStoreConfig {
|
|
14
|
+
/** Base URL of the gateway, e.g. `http://localhost:4040/gateway`. Routes are appended to it. */
|
|
15
|
+
endpoint: string;
|
|
16
|
+
/** The bearer credential the gateway issued to this process. */
|
|
17
|
+
credential: string;
|
|
18
|
+
/** Transport override; defaults to `globalThis.fetch`. */
|
|
19
|
+
fetch?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
/** The op names on the wire; each is the `IDataset` method the store was called on. */
|
|
22
|
+
export type GatewayOp = 'select' | 'ask' | 'update' | 'create' | 'delete';
|
|
23
|
+
/** The wire version this store speaks. The gateway refuses any other with `code: "wire-version"`. */
|
|
24
|
+
export declare const GATEWAY_WIRE_VERSION = 1;
|
|
25
|
+
/**
|
|
26
|
+
* A non-2xx answer from the gateway. `code` is the body's `code` when the gateway sent one
|
|
27
|
+
* (`bad-request`, `wire-version`, `unknown-shape`, `refused`, `fork-failed`, `query-failed`),
|
|
28
|
+
* else a generic one; `body` is the parsed response when it was JSON.
|
|
29
|
+
*/
|
|
30
|
+
export declare class GatewayStoreError extends Error {
|
|
31
|
+
readonly status: number;
|
|
32
|
+
readonly code: string;
|
|
33
|
+
readonly body?: unknown;
|
|
34
|
+
constructor(message: string, status: number, code: string, body?: unknown);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The gateway could not be used at all: the request never got an HTTP answer (network error,
|
|
38
|
+
* `status` 0), or the gateway refused the credential (401). Thrown by `init()` and by every
|
|
39
|
+
* query method; a process that gets this at boot should stop rather than carry on without data.
|
|
40
|
+
*/
|
|
41
|
+
export declare class GatewayUnreachableError extends GatewayStoreError {
|
|
42
|
+
}
|
|
43
|
+
/** What `GET {endpoint}/health` answers with a valid credential. */
|
|
44
|
+
export interface GatewayHealth {
|
|
45
|
+
projectId: string;
|
|
46
|
+
branchName: string;
|
|
47
|
+
dataset: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* An {@link IDataset} that forwards every Linked Query to a gateway over HTTP and returns
|
|
51
|
+
* the gateway's result as is.
|
|
52
|
+
*
|
|
53
|
+
* Each query method posts `{v, op, query: query.toJSON()}` to `POST {endpoint}/query` with
|
|
54
|
+
* `Authorization: Bearer <credential>` and resolves with the body's `result` — the same plain
|
|
55
|
+
* JSON rows `BackendAPIStore` returns. The gateway decides which dataset the query runs on
|
|
56
|
+
* from the credential; the store carries no project, branch or actor.
|
|
57
|
+
*
|
|
58
|
+
* Unlike `BackendAPIStore` it does not go through `Server.call` and has no dependency on
|
|
59
|
+
* the process's own HTTP server: it is meant for a backend whose data lives behind another
|
|
60
|
+
* service, selected from `linked.backend.datasets.json` by the host that started it.
|
|
61
|
+
*
|
|
62
|
+
* The store offers no raw SPARQL (`rawQuery`), no dataset administration and no change feed.
|
|
63
|
+
*/
|
|
64
|
+
export declare class GatewayStore implements IDataset {
|
|
65
|
+
readonly endpoint: string;
|
|
66
|
+
private readonly credential;
|
|
67
|
+
private readonly fetchImpl;
|
|
68
|
+
constructor(config: GatewayStoreConfig);
|
|
69
|
+
/**
|
|
70
|
+
* Checks the gateway answers for this credential: `GET {endpoint}/health`. Resolves with the
|
|
71
|
+
* gateway's answer; throws {@link GatewayUnreachableError} when the gateway cannot be reached
|
|
72
|
+
* or refuses the credential. Safe to call more than once; it changes nothing on either side.
|
|
73
|
+
*
|
|
74
|
+
* `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must
|
|
75
|
+
* fail loudly awaits `init()` itself before registering the store.
|
|
76
|
+
*/
|
|
77
|
+
init(): Promise<GatewayHealth>;
|
|
78
|
+
selectQuery(query: SelectQuery | CountQuery): Promise<SelectResult | number>;
|
|
79
|
+
askQuery(query: AskQuery): Promise<boolean>;
|
|
80
|
+
updateQuery(query: UpdateQuery): Promise<UpdateResult>;
|
|
81
|
+
createQuery(query: CreateQuery): Promise<CreateResult>;
|
|
82
|
+
deleteQuery(query: DeleteQuery): Promise<DeleteResponse>;
|
|
83
|
+
private send;
|
|
84
|
+
private request;
|
|
85
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/** The wire version this store speaks. The gateway refuses any other with `code: "wire-version"`. */
|
|
2
|
+
export const GATEWAY_WIRE_VERSION = 1;
|
|
3
|
+
/**
|
|
4
|
+
* A non-2xx answer from the gateway. `code` is the body's `code` when the gateway sent one
|
|
5
|
+
* (`bad-request`, `wire-version`, `unknown-shape`, `refused`, `fork-failed`, `query-failed`),
|
|
6
|
+
* else a generic one; `body` is the parsed response when it was JSON.
|
|
7
|
+
*/
|
|
8
|
+
export class GatewayStoreError extends Error {
|
|
9
|
+
constructor(message, status, code, body) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = new.target.name;
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.body = body;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The gateway could not be used at all: the request never got an HTTP answer (network error,
|
|
19
|
+
* `status` 0), or the gateway refused the credential (401). Thrown by `init()` and by every
|
|
20
|
+
* query method; a process that gets this at boot should stop rather than carry on without data.
|
|
21
|
+
*/
|
|
22
|
+
export class GatewayUnreachableError extends GatewayStoreError {
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* An {@link IDataset} that forwards every Linked Query to a gateway over HTTP and returns
|
|
26
|
+
* the gateway's result as is.
|
|
27
|
+
*
|
|
28
|
+
* Each query method posts `{v, op, query: query.toJSON()}` to `POST {endpoint}/query` with
|
|
29
|
+
* `Authorization: Bearer <credential>` and resolves with the body's `result` — the same plain
|
|
30
|
+
* JSON rows `BackendAPIStore` returns. The gateway decides which dataset the query runs on
|
|
31
|
+
* from the credential; the store carries no project, branch or actor.
|
|
32
|
+
*
|
|
33
|
+
* Unlike `BackendAPIStore` it does not go through `Server.call` and has no dependency on
|
|
34
|
+
* the process's own HTTP server: it is meant for a backend whose data lives behind another
|
|
35
|
+
* service, selected from `linked.backend.datasets.json` by the host that started it.
|
|
36
|
+
*
|
|
37
|
+
* The store offers no raw SPARQL (`rawQuery`), no dataset administration and no change feed.
|
|
38
|
+
*/
|
|
39
|
+
export class GatewayStore {
|
|
40
|
+
constructor(config) {
|
|
41
|
+
var _a;
|
|
42
|
+
const endpoint = typeof (config === null || config === void 0 ? void 0 : config.endpoint) === 'string' ? config.endpoint.trim() : '';
|
|
43
|
+
const credential = typeof (config === null || config === void 0 ? void 0 : config.credential) === 'string' ? config.credential.trim() : '';
|
|
44
|
+
if (!endpoint) {
|
|
45
|
+
throw new Error('GatewayStore: `endpoint` is empty. A gateway store needs the URL of the gateway ' +
|
|
46
|
+
'(the host that started this process sets it, e.g. through CN_EG_GATEWAY).');
|
|
47
|
+
}
|
|
48
|
+
if (!credential) {
|
|
49
|
+
throw new Error('GatewayStore: `credential` is empty. A gateway store needs the bearer credential ' +
|
|
50
|
+
'the gateway issued to this process (e.g. CN_APP_SERVICE_SECRET).');
|
|
51
|
+
}
|
|
52
|
+
const fetchImpl = (_a = config.fetch) !== null && _a !== void 0 ? _a : globalThis.fetch;
|
|
53
|
+
if (typeof fetchImpl !== 'function') {
|
|
54
|
+
throw new Error('GatewayStore: no `fetch` available; pass one in the config.');
|
|
55
|
+
}
|
|
56
|
+
this.endpoint = endpoint.replace(/\/+$/, '');
|
|
57
|
+
this.credential = credential;
|
|
58
|
+
this.fetchImpl = fetchImpl;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Checks the gateway answers for this credential: `GET {endpoint}/health`. Resolves with the
|
|
62
|
+
* gateway's answer; throws {@link GatewayUnreachableError} when the gateway cannot be reached
|
|
63
|
+
* or refuses the credential. Safe to call more than once; it changes nothing on either side.
|
|
64
|
+
*
|
|
65
|
+
* `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must
|
|
66
|
+
* fail loudly awaits `init()` itself before registering the store.
|
|
67
|
+
*/
|
|
68
|
+
async init() {
|
|
69
|
+
return (await this.request('GET', '/health'));
|
|
70
|
+
}
|
|
71
|
+
selectQuery(query) {
|
|
72
|
+
return this.send('select', query.toJSON());
|
|
73
|
+
}
|
|
74
|
+
askQuery(query) {
|
|
75
|
+
return this.send('ask', query.toJSON());
|
|
76
|
+
}
|
|
77
|
+
updateQuery(query) {
|
|
78
|
+
return this.send('update', query.toJSON());
|
|
79
|
+
}
|
|
80
|
+
createQuery(query) {
|
|
81
|
+
return this.send('create', query.toJSON());
|
|
82
|
+
}
|
|
83
|
+
deleteQuery(query) {
|
|
84
|
+
return this.send('delete', query.toJSON());
|
|
85
|
+
}
|
|
86
|
+
async send(op, query) {
|
|
87
|
+
const body = (await this.request('POST', '/query', {
|
|
88
|
+
v: GATEWAY_WIRE_VERSION,
|
|
89
|
+
op,
|
|
90
|
+
query,
|
|
91
|
+
}));
|
|
92
|
+
if (!body || typeof body !== 'object' || !('result' in body)) {
|
|
93
|
+
throw new GatewayStoreError(`GatewayStore: the gateway answered ${op} without a result`, 200, 'bad-response', body);
|
|
94
|
+
}
|
|
95
|
+
return body.result;
|
|
96
|
+
}
|
|
97
|
+
async request(method, route, json) {
|
|
98
|
+
const url = this.endpoint + route;
|
|
99
|
+
const headers = {
|
|
100
|
+
Authorization: `Bearer ${this.credential}`,
|
|
101
|
+
Accept: 'application/json',
|
|
102
|
+
};
|
|
103
|
+
const init = { method, headers };
|
|
104
|
+
if (json !== undefined) {
|
|
105
|
+
headers['Content-Type'] = 'application/json';
|
|
106
|
+
init.body = JSON.stringify(json);
|
|
107
|
+
}
|
|
108
|
+
let response;
|
|
109
|
+
try {
|
|
110
|
+
response = await this.fetchImpl(url, init);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
114
|
+
throw new GatewayUnreachableError(`GatewayStore: cannot reach the gateway at ${url}: ${cause}`, 0, 'unreachable');
|
|
115
|
+
}
|
|
116
|
+
const text = await response.text();
|
|
117
|
+
let body = undefined;
|
|
118
|
+
if (text) {
|
|
119
|
+
try {
|
|
120
|
+
body = JSON.parse(text);
|
|
121
|
+
}
|
|
122
|
+
catch (_a) {
|
|
123
|
+
body = text;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (response.ok)
|
|
127
|
+
return body;
|
|
128
|
+
const code = body && typeof body === 'object' && typeof body.code === 'string'
|
|
129
|
+
? body.code
|
|
130
|
+
: `http-${response.status}`;
|
|
131
|
+
const detail = body && typeof body === 'object' && typeof body.error === 'string'
|
|
132
|
+
? body.error
|
|
133
|
+
: typeof body === 'string' && body
|
|
134
|
+
? body
|
|
135
|
+
: response.statusText;
|
|
136
|
+
const message = `GatewayStore: ${method} ${url} answered ${response.status} (${code})${detail ? `: ${detail}` : ''}`;
|
|
137
|
+
if (response.status === 401) {
|
|
138
|
+
throw new GatewayUnreachableError(message, 401, code, body);
|
|
139
|
+
}
|
|
140
|
+
throw new GatewayStoreError(message, response.status, code, body);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=GatewayStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GatewayStore.js","sourceRoot":"","sources":["../../../../src/shapes/quadstores/GatewayStore.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IDataset } from '@_linked/core/interfaces/IDataset';\nimport type { SelectQuery } from '@_linked/core/queries/SelectQuery';\nimport type { CountQuery } from '@_linked/core/queries/CountQuery';\nimport type { AskQuery } from '@_linked/core/queries/AskQuery';\nimport type { UpdateQuery } from '@_linked/core/queries/UpdateQuery';\nimport type { CreateQuery } from '@_linked/core/queries/CreateQuery';\nimport type {\n DeleteQuery,\n DeleteResponse,\n} from '@_linked/core/queries/DeleteQuery';\nimport type {\n SelectResult,\n UpdateResult,\n CreateResult,\n} from '@_linked/core/queries/IntermediateRepresentation';\n\n/**\n * Constructor argument for `new GatewayStore(config)` — the `config` object of a\n * `linked.backend.datasets.json` entry, passed verbatim by `loadStores`.\n */\nexport interface GatewayStoreConfig {\n /** Base URL of the gateway, e.g. `http://localhost:4040/gateway`. Routes are appended to it. */\n endpoint: string;\n /** The bearer credential the gateway issued to this process. */\n credential: string;\n /** Transport override; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n}\n\n/** The op names on the wire; each is the `IDataset` method the store was called on. */\nexport type GatewayOp = 'select' | 'ask' | 'update' | 'create' | 'delete';\n\n/** The wire version this store speaks. The gateway refuses any other with `code: \"wire-version\"`. */\nexport const GATEWAY_WIRE_VERSION = 1;\n\n/**\n * A non-2xx answer from the gateway. `code` is the body's `code` when the gateway sent one\n * (`bad-request`, `wire-version`, `unknown-shape`, `refused`, `fork-failed`, `query-failed`),\n * else a generic one; `body` is the parsed response when it was JSON.\n */\nexport class GatewayStoreError extends Error {\n readonly status: number;\n readonly code: string;\n readonly body?: unknown;\n\n constructor(message: string, status: number, code: string, body?: unknown) {\n super(message);\n this.name = new.target.name;\n this.status = status;\n this.code = code;\n this.body = body;\n }\n}\n\n/**\n * The gateway could not be used at all: the request never got an HTTP answer (network error,\n * `status` 0), or the gateway refused the credential (401). Thrown by `init()` and by every\n * query method; a process that gets this at boot should stop rather than carry on without data.\n */\nexport class GatewayUnreachableError extends GatewayStoreError {}\n\n/** What `GET {endpoint}/health` answers with a valid credential. */\nexport interface GatewayHealth {\n projectId: string;\n branchName: string;\n dataset: string;\n}\n\n/**\n * An {@link IDataset} that forwards every Linked Query to a gateway over HTTP and returns\n * the gateway's result as is.\n *\n * Each query method posts `{v, op, query: query.toJSON()}` to `POST {endpoint}/query` with\n * `Authorization: Bearer <credential>` and resolves with the body's `result` — the same plain\n * JSON rows `BackendAPIStore` returns. The gateway decides which dataset the query runs on\n * from the credential; the store carries no project, branch or actor.\n *\n * Unlike `BackendAPIStore` it does not go through `Server.call` and has no dependency on\n * the process's own HTTP server: it is meant for a backend whose data lives behind another\n * service, selected from `linked.backend.datasets.json` by the host that started it.\n *\n * The store offers no raw SPARQL (`rawQuery`), no dataset administration and no change feed.\n */\nexport class GatewayStore implements IDataset {\n readonly endpoint: string;\n private readonly credential: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(config: GatewayStoreConfig) {\n const endpoint = typeof config?.endpoint === 'string' ? config.endpoint.trim() : '';\n const credential = typeof config?.credential === 'string' ? config.credential.trim() : '';\n if (!endpoint) {\n throw new Error(\n 'GatewayStore: `endpoint` is empty. A gateway store needs the URL of the gateway ' +\n '(the host that started this process sets it, e.g. through CN_EG_GATEWAY).',\n );\n }\n if (!credential) {\n throw new Error(\n 'GatewayStore: `credential` is empty. A gateway store needs the bearer credential ' +\n 'the gateway issued to this process (e.g. CN_APP_SERVICE_SECRET).',\n );\n }\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new Error('GatewayStore: no `fetch` available; pass one in the config.');\n }\n this.endpoint = endpoint.replace(/\\/+$/, '');\n this.credential = credential;\n this.fetchImpl = fetchImpl;\n }\n\n /**\n * Checks the gateway answers for this credential: `GET {endpoint}/health`. Resolves with the\n * gateway's answer; throws {@link GatewayUnreachableError} when the gateway cannot be reached\n * or refuses the credential. Safe to call more than once; it changes nothing on either side.\n *\n * `LinkedStorage.setDefaultDataset` calls `init()` without awaiting it, so a boot that must\n * fail loudly awaits `init()` itself before registering the store.\n */\n async init(): Promise<GatewayHealth> {\n return (await this.request('GET', '/health')) as GatewayHealth;\n }\n\n selectQuery(query: SelectQuery | CountQuery): Promise<SelectResult | number> {\n return this.send('select', query.toJSON());\n }\n\n askQuery(query: AskQuery): Promise<boolean> {\n return this.send('ask', query.toJSON());\n }\n\n updateQuery(query: UpdateQuery): Promise<UpdateResult> {\n return this.send('update', query.toJSON());\n }\n\n createQuery(query: CreateQuery): Promise<CreateResult> {\n return this.send('create', query.toJSON());\n }\n\n deleteQuery(query: DeleteQuery): Promise<DeleteResponse> {\n return this.send('delete', query.toJSON());\n }\n\n private async send<T>(op: GatewayOp, query: unknown): Promise<T> {\n const body = (await this.request('POST', '/query', {\n v: GATEWAY_WIRE_VERSION,\n op,\n query,\n })) as { result?: T } | null;\n if (!body || typeof body !== 'object' || !('result' in body)) {\n throw new GatewayStoreError(\n `GatewayStore: the gateway answered ${op} without a result`,\n 200,\n 'bad-response',\n body,\n );\n }\n return body.result as T;\n }\n\n private async request(method: 'GET' | 'POST', route: string, json?: unknown): Promise<unknown> {\n const url = this.endpoint + route;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.credential}`,\n Accept: 'application/json',\n };\n const init: RequestInit = { method, headers };\n if (json !== undefined) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(json);\n }\n\n let response: Response;\n try {\n response = await this.fetchImpl(url, init);\n } catch (err) {\n const cause = err instanceof Error ? err.message : String(err);\n throw new GatewayUnreachableError(\n `GatewayStore: cannot reach the gateway at ${url}: ${cause}`,\n 0,\n 'unreachable',\n );\n }\n\n const text = await response.text();\n let body: unknown = undefined;\n if (text) {\n try {\n body = JSON.parse(text);\n } catch {\n body = text;\n }\n }\n\n if (response.ok) return body;\n\n const code =\n body && typeof body === 'object' && typeof (body as any).code === 'string'\n ? ((body as any).code as string)\n : `http-${response.status}`;\n const detail =\n body && typeof body === 'object' && typeof (body as any).error === 'string'\n ? (body as any).error\n : typeof body === 'string' && body\n ? body\n : response.statusText;\n const message = `GatewayStore: ${method} ${url} answered ${response.status} (${code})${\n detail ? `: ${detail}` : ''\n }`;\n\n if (response.status === 401) {\n throw new GatewayUnreachableError(message, 401, code, body);\n }\n throw new GatewayStoreError(message, response.status, code, body);\n }\n}\n"]}
|
|
@@ -100,6 +100,26 @@ export declare const NODE_IMPORT_CONDITIONS: readonly ["import", "node", "defaul
|
|
|
100
100
|
* ~50 lines, and the server keeps one less dependency in every app's install.
|
|
101
101
|
*/
|
|
102
102
|
export declare function resolveBackendEntry(pkgRoot: string, pkgJson: any, conditions?: readonly string[]): string | null;
|
|
103
|
+
/**
|
|
104
|
+
* The SOURCE file a dev Vite runner loads for `<pkg>/backend` when the
|
|
105
|
+
* package is a source package — its real root has a `src/` directory, which is
|
|
106
|
+
* what makes `linked`'s Vite config register it as a workspace (cli
|
|
107
|
+
* `discoverWorkspaces` / `readSourcePackage`). Null when the package has no
|
|
108
|
+
* `src/` or no source backend; the caller then falls back to
|
|
109
|
+
* resolveBackendEntry (a built `lib/` copy, or nothing).
|
|
110
|
+
*
|
|
111
|
+
* Mirrors the cli's `resolveWorkspaceSpecifier` for the `./backend` subpath,
|
|
112
|
+
* which runs BEFORE `exports`: `src/backend.tsx`, `src/backend.ts`, then the
|
|
113
|
+
* package's own `exports` under `import`/`default` with a `./lib/esm/X.js`
|
|
114
|
+
* target mapped to `src/X.tsx` / `src/X.ts` (any other `./lib/esm/` target to
|
|
115
|
+
* the same path under src). So an unbuilt workspace, whose `exports` name only
|
|
116
|
+
* `lib/esm`, still has its backend found, and a built one reports the src file
|
|
117
|
+
* Vite actually evaluates rather than its stale `lib/`.
|
|
118
|
+
*
|
|
119
|
+
* Only meaningful when the dev Vite runner owns the package; Node (production,
|
|
120
|
+
* external packages) loads `lib/` and never sees src.
|
|
121
|
+
*/
|
|
122
|
+
export declare function resolveSourceBackendEntry(pkgRoot: string, pkgJson: any): string | null;
|
|
103
123
|
/**
|
|
104
124
|
* Whether the dev SSR runner BUNDLES `pkgName` (it is in Vite's
|
|
105
125
|
* `ssr.noExternal`) rather than handing it to Node.
|
|
@@ -121,3 +141,11 @@ export declare function resolveBackendEntry(pkgRoot: string, pkgJson: any, condi
|
|
|
121
141
|
export declare function isBundledBySsr(pkgName: string, noExternal: unknown): boolean;
|
|
122
142
|
/** The dev SSR runner's resolved `noExternal`, wherever this Vite keeps it. */
|
|
123
143
|
export declare function ssrNoExternalOf(vite: any): unknown;
|
|
144
|
+
/**
|
|
145
|
+
* The backend entry of a package the dev Vite runner owns (see isBundledBySsr):
|
|
146
|
+
* its source backend when it is a source package (resolveSourceBackendEntry),
|
|
147
|
+
* otherwise what `exports` names under the `development` condition and Node's
|
|
148
|
+
* import conditions. A registry install has no src/, so it resolves exactly
|
|
149
|
+
* as before.
|
|
150
|
+
*/
|
|
151
|
+
export declare function resolveDevBackendEntry(pkgRoot: string, pkgJson: any): string | null;
|
|
@@ -169,18 +169,7 @@ export const NODE_IMPORT_CONDITIONS = ['import', 'node', 'default'];
|
|
|
169
169
|
* ~50 lines, and the server keeps one less dependency in every app's install.
|
|
170
170
|
*/
|
|
171
171
|
export function resolveBackendEntry(pkgRoot, pkgJson, conditions = NODE_IMPORT_CONDITIONS) {
|
|
172
|
-
const exists = (rel) =>
|
|
173
|
-
const file = path.resolve(pkgRoot, rel);
|
|
174
|
-
// Never let an export target escape the package.
|
|
175
|
-
if (!file.startsWith(path.resolve(pkgRoot) + path.sep))
|
|
176
|
-
return null;
|
|
177
|
-
try {
|
|
178
|
-
return fs.statSync(file).isFile() ? file : null;
|
|
179
|
-
}
|
|
180
|
-
catch (_a) {
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
};
|
|
172
|
+
const exists = (rel) => packageFile(pkgRoot, rel);
|
|
184
173
|
const exportsField = pkgJson === null || pkgJson === void 0 ? void 0 : pkgJson.exports;
|
|
185
174
|
if (exportsField === undefined || exportsField === null) {
|
|
186
175
|
const candidates = ['backend.js', 'backend/index.js'];
|
|
@@ -195,6 +184,27 @@ export function resolveBackendEntry(pkgRoot, pkgJson, conditions = NODE_IMPORT_C
|
|
|
195
184
|
}
|
|
196
185
|
return null;
|
|
197
186
|
}
|
|
187
|
+
return resolveBackendExport(exportsField, conditions, exists);
|
|
188
|
+
}
|
|
189
|
+
/** `<pkgRoot>/<rel>` when it is a file inside the package, else null. */
|
|
190
|
+
function packageFile(pkgRoot, rel) {
|
|
191
|
+
const file = path.resolve(pkgRoot, rel);
|
|
192
|
+
// Never let an export target escape the package.
|
|
193
|
+
if (!file.startsWith(path.resolve(pkgRoot) + path.sep))
|
|
194
|
+
return null;
|
|
195
|
+
try {
|
|
196
|
+
return fs.statSync(file).isFile() ? file : null;
|
|
197
|
+
}
|
|
198
|
+
catch (_a) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The `./backend` subpath of an `exports` map, resolved per Node's
|
|
204
|
+
* PACKAGE_EXPORTS_RESOLVE (see resolveBackendEntry). `probe` turns a matched
|
|
205
|
+
* `./`-relative target into a file, or null when it does not resolve.
|
|
206
|
+
*/
|
|
207
|
+
function resolveBackendExport(exportsField, conditions, probe) {
|
|
198
208
|
// `"exports": "./x.js"`, an array, or a condition object at the top level
|
|
199
209
|
// are all sugar for the "." subpath only.
|
|
200
210
|
if (typeof exportsField !== 'object' ||
|
|
@@ -234,7 +244,7 @@ export function resolveBackendEntry(pkgRoot, pkgJson, conditions = NODE_IMPORT_C
|
|
|
234
244
|
if (!t.startsWith('./'))
|
|
235
245
|
return null;
|
|
236
246
|
const rel = patternMatch === null ? t : t.split('*').join(patternMatch);
|
|
237
|
-
return
|
|
247
|
+
return probe(rel);
|
|
238
248
|
}
|
|
239
249
|
if (Array.isArray(t)) {
|
|
240
250
|
for (const item of t) {
|
|
@@ -257,6 +267,54 @@ export function resolveBackendEntry(pkgRoot, pkgJson, conditions = NODE_IMPORT_C
|
|
|
257
267
|
};
|
|
258
268
|
return resolveTarget(target);
|
|
259
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* The SOURCE file a dev Vite runner loads for `<pkg>/backend` when the
|
|
272
|
+
* package is a source package — its real root has a `src/` directory, which is
|
|
273
|
+
* what makes `linked`'s Vite config register it as a workspace (cli
|
|
274
|
+
* `discoverWorkspaces` / `readSourcePackage`). Null when the package has no
|
|
275
|
+
* `src/` or no source backend; the caller then falls back to
|
|
276
|
+
* resolveBackendEntry (a built `lib/` copy, or nothing).
|
|
277
|
+
*
|
|
278
|
+
* Mirrors the cli's `resolveWorkspaceSpecifier` for the `./backend` subpath,
|
|
279
|
+
* which runs BEFORE `exports`: `src/backend.tsx`, `src/backend.ts`, then the
|
|
280
|
+
* package's own `exports` under `import`/`default` with a `./lib/esm/X.js`
|
|
281
|
+
* target mapped to `src/X.tsx` / `src/X.ts` (any other `./lib/esm/` target to
|
|
282
|
+
* the same path under src). So an unbuilt workspace, whose `exports` name only
|
|
283
|
+
* `lib/esm`, still has its backend found, and a built one reports the src file
|
|
284
|
+
* Vite actually evaluates rather than its stale `lib/`.
|
|
285
|
+
*
|
|
286
|
+
* Only meaningful when the dev Vite runner owns the package; Node (production,
|
|
287
|
+
* external packages) loads `lib/` and never sees src.
|
|
288
|
+
*/
|
|
289
|
+
export function resolveSourceBackendEntry(pkgRoot, pkgJson) {
|
|
290
|
+
try {
|
|
291
|
+
if (!fs.statSync(path.join(pkgRoot, 'src')).isDirectory())
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
catch (_a) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
for (const rel of ['src/backend.tsx', 'src/backend.ts']) {
|
|
298
|
+
const file = packageFile(pkgRoot, rel);
|
|
299
|
+
if (file)
|
|
300
|
+
return file;
|
|
301
|
+
}
|
|
302
|
+
const exportsField = pkgJson === null || pkgJson === void 0 ? void 0 : pkgJson.exports;
|
|
303
|
+
if (exportsField === undefined || exportsField === null)
|
|
304
|
+
return null;
|
|
305
|
+
const libPrefix = './lib/esm/';
|
|
306
|
+
return resolveBackendExport(exportsField, ['import'], (rel) => {
|
|
307
|
+
var _a;
|
|
308
|
+
if (!rel.startsWith(libPrefix))
|
|
309
|
+
return null;
|
|
310
|
+
const sub = rel.slice(libPrefix.length);
|
|
311
|
+
if (/\.jsx?$/.test(sub)) {
|
|
312
|
+
const base = 'src/' + sub.replace(/\.jsx?$/, '');
|
|
313
|
+
return (_a = packageFile(pkgRoot, base + '.tsx')) !== null && _a !== void 0 ? _a : packageFile(pkgRoot, base + '.ts');
|
|
314
|
+
}
|
|
315
|
+
return packageFile(pkgRoot, 'src/' + sub);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
260
318
|
/** Node's PATTERN_KEY_COMPARE: < 0 means `a` is the more specific pattern. */
|
|
261
319
|
function patternKeyCompare(a, b) {
|
|
262
320
|
const aBase = a.indexOf('*');
|
|
@@ -314,4 +372,18 @@ export function ssrNoExternalOf(vite) {
|
|
|
314
372
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
315
373
|
return ((_c = (_b = (_a = vite === null || vite === void 0 ? void 0 : vite.config) === null || _a === void 0 ? void 0 : _a.ssr) === null || _b === void 0 ? void 0 : _b.noExternal) !== null && _c !== void 0 ? _c : (_g = (_f = (_e = (_d = vite === null || vite === void 0 ? void 0 : vite.config) === null || _d === void 0 ? void 0 : _d.environments) === null || _e === void 0 ? void 0 : _e.ssr) === null || _f === void 0 ? void 0 : _f.resolve) === null || _g === void 0 ? void 0 : _g.noExternal);
|
|
316
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* The backend entry of a package the dev Vite runner owns (see isBundledBySsr):
|
|
377
|
+
* its source backend when it is a source package (resolveSourceBackendEntry),
|
|
378
|
+
* otherwise what `exports` names under the `development` condition and Node's
|
|
379
|
+
* import conditions. A registry install has no src/, so it resolves exactly
|
|
380
|
+
* as before.
|
|
381
|
+
*/
|
|
382
|
+
export function resolveDevBackendEntry(pkgRoot, pkgJson) {
|
|
383
|
+
var _a;
|
|
384
|
+
return ((_a = resolveSourceBackendEntry(pkgRoot, pkgJson)) !== null && _a !== void 0 ? _a : resolveBackendEntry(pkgRoot, pkgJson, [
|
|
385
|
+
'development',
|
|
386
|
+
...NODE_IMPORT_CONDITIONS,
|
|
387
|
+
]));
|
|
388
|
+
}
|
|
317
389
|
//# sourceMappingURL=linkedDependencies.js.map
|
|
@@ -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) => {\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 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\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 exists(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/** 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"]}
|
|
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"]}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -19,6 +19,9 @@ If you want to further adjust the functionality of LincdServer yourself, either
|
|
|
19
19
|
- [Generic backend providers](#generic-backend-providers)
|
|
20
20
|
- [Gotchas](#gotchas)
|
|
21
21
|
- [Error semantics](#error-semantics)
|
|
22
|
+
- [Query stores](#query-stores)
|
|
23
|
+
- [BackendAPIStore](#backendapistore)
|
|
24
|
+
- [GatewayStore](#gatewaystore)
|
|
22
25
|
- [API-only servers (`server.apiOnly`)](#api-only-servers-serverapionly)
|
|
23
26
|
- [Provider lifecycle and HMR](#provider-lifecycle-and-hmr)
|
|
24
27
|
- [When a provider throws, and refusing to start](#when-a-provider-throws-and-refusing-to-start)
|
|
@@ -232,6 +235,72 @@ What the caller sees depends on how it calls:
|
|
|
232
235
|
- An unmatched call resolves `undefined` by default and rejects with a 501 `ServerCallError` when `rejectOnError` is set.
|
|
233
236
|
- **`BackendAPIStore`.** It always uses `rejectOnError`, so a failed query rejects with the status and message. A successful call resolves with whatever the provider returned, `undefined` included.
|
|
234
237
|
|
|
238
|
+
## Query stores
|
|
239
|
+
|
|
240
|
+
Two `IDataset` implementations in this package forward Linked Queries over HTTP instead of
|
|
241
|
+
answering them. Both ship `query.toJSON()` and resolve with the plain JSON rows the other side
|
|
242
|
+
returns; neither offers raw SPARQL (`rawQuery`), dataset administration or a change feed.
|
|
243
|
+
|
|
244
|
+
| | `BackendAPIStore` | `GatewayStore` |
|
|
245
|
+
| --- | --- | --- |
|
|
246
|
+
| Runs in | the browser (or any client of this package's `/call` routes) | a backend process |
|
|
247
|
+
| Sends to | this app's own backend, through `Server.call` (`/call/<package>/<method>`) | a gateway of another service: `POST {endpoint}/query` |
|
|
248
|
+
| Authenticated by | the user's session | a bearer credential the gateway issued to this process |
|
|
249
|
+
| Who picks the dataset | the backend's `LinkedStorage` | the gateway, from the credential |
|
|
250
|
+
|
|
251
|
+
### BackendAPIStore
|
|
252
|
+
|
|
253
|
+
Export path `@_linked/server/shapes/quadstores/BackendAPIStore`. Selected in
|
|
254
|
+
`linked.frontend.datasets.json`; `config` may carry a `name` or `id`, neither of which affects
|
|
255
|
+
routing. Its error semantics are under [Error semantics](#error-semantics).
|
|
256
|
+
|
|
257
|
+
### GatewayStore
|
|
258
|
+
|
|
259
|
+
Export path `@_linked/server/shapes/quadstores/GatewayStore` (named export `GatewayStore`, so
|
|
260
|
+
`loadStores` resolves it by the last path segment). It is for a backend whose data lives behind
|
|
261
|
+
a gateway run by the host that started it — for example an app process started by Create Now,
|
|
262
|
+
whose queries go to CN's gateway so that branch forks, authorization and audit apply to app
|
|
263
|
+
traffic.
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"datasets": {
|
|
268
|
+
"gateway": {
|
|
269
|
+
"store": "@_linked/server/shapes/quadstores/GatewayStore",
|
|
270
|
+
"config": { "endpoint": "${CN_EG_GATEWAY:-}", "credential": "${CN_APP_SERVICE_SECRET:-}" }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
export interface GatewayStoreConfig {
|
|
278
|
+
endpoint: string; // base URL of the gateway, routes are appended: `${endpoint}/query`, `${endpoint}/health`
|
|
279
|
+
credential: string; // sent as `Authorization: Bearer <credential>`
|
|
280
|
+
fetch?: typeof fetch; // transport override, default `globalThis.fetch`
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
- The constructor throws at once when `endpoint` or `credential` is empty. The `:-` defaults in
|
|
285
|
+
the JSON keep `parseDatasetsConfig` from throwing when the variables are unset; a storage file
|
|
286
|
+
instantiates the alias only when the host set them (the presence of `CN_EG_GATEWAY` is the
|
|
287
|
+
mode switch; `CN_APP_SERVICE_SECRET` is injected into the process environment, never written
|
|
288
|
+
to `.env`).
|
|
289
|
+
- `init()` is `GET {endpoint}/health` and resolves with the gateway's answer
|
|
290
|
+
(`{projectId, branchName, dataset}`). It throws `GatewayUnreachableError` when the gateway
|
|
291
|
+
cannot be reached or refuses the credential. `LinkedStorage.setDefaultDataset` does not await
|
|
292
|
+
`init()`, so a boot that must fail loudly awaits `init()` itself before registering the store.
|
|
293
|
+
Calling it again is harmless.
|
|
294
|
+
- Every query method posts `{ "v": 1, "op": "select" | "ask" | "update" | "create" | "delete", "query": <query.toJSON()> }`
|
|
295
|
+
and resolves with the body's `result`. A count arrives through `selectQuery` with `op: "select"`
|
|
296
|
+
(the gateway dispatches on the query's own `op`). Nothing about the project, branch or actor is
|
|
297
|
+
in the body: the credential decides all three.
|
|
298
|
+
- Errors: a network failure and a `401` reject with `GatewayUnreachableError` (`status` 0 or 401);
|
|
299
|
+
any other non-2xx answer rejects with `GatewayStoreError`, whose `code` is the body's `code`
|
|
300
|
+
(`bad-request`, `wire-version`, `unknown-shape`, `refused`, `fork-failed`, `query-failed`),
|
|
301
|
+
else `http-<status>`, with the parsed body on `body`. `GatewayUnreachableError extends
|
|
302
|
+
GatewayStoreError`.
|
|
303
|
+
|
|
235
304
|
## API-only servers (`server.apiOnly`)
|
|
236
305
|
|
|
237
306
|
A backend that serves no frontend app (e.g. started with `linked start --api-only`) can set `apiOnly` in its server config:
|