@_linked/server 3.13.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/lib/esm/backend.d.ts +23 -5
  3. package/lib/esm/backend.js +116 -25
  4. package/lib/esm/backend.js.map +1 -1
  5. package/lib/esm/shapes/LincdAPI.d.ts +1 -1
  6. package/lib/esm/shapes/LincdAPI.js +43 -7
  7. package/lib/esm/shapes/LincdAPI.js.map +1 -1
  8. package/lib/esm/shapes/LinkedServer.d.ts +50 -37
  9. package/lib/esm/shapes/LinkedServer.js +121 -76
  10. package/lib/esm/shapes/LinkedServer.js.map +1 -1
  11. package/lib/esm/shapes/quadstores/RemoteDataset.d.ts +35 -6
  12. package/lib/esm/shapes/quadstores/RemoteDataset.js +77 -12
  13. package/lib/esm/shapes/quadstores/RemoteDataset.js.map +1 -1
  14. package/lib/esm/utils/adminChannel.d.ts +22 -51
  15. package/lib/esm/utils/adminChannel.js +48 -36
  16. package/lib/esm/utils/adminChannel.js.map +1 -1
  17. package/lib/esm/utils/effectiveRoutes.d.ts +6 -0
  18. package/lib/esm/utils/effectiveRoutes.js +2 -1
  19. package/lib/esm/utils/effectiveRoutes.js.map +1 -1
  20. package/lib/esm/utils/forwardedTransactions.d.ts +46 -0
  21. package/lib/esm/utils/forwardedTransactions.js +155 -0
  22. package/lib/esm/utils/forwardedTransactions.js.map +1 -0
  23. package/lib/esm/utils/graphTargets.d.ts +37 -20
  24. package/lib/esm/utils/graphTargets.js +61 -82
  25. package/lib/esm/utils/graphTargets.js.map +1 -1
  26. package/lib/esm/utils/ontologySync.d.ts +40 -23
  27. package/lib/esm/utils/ontologySync.js +59 -37
  28. package/lib/esm/utils/ontologySync.js.map +1 -1
  29. package/lib/esm/utils/queryPlane.d.ts +13 -0
  30. package/lib/esm/utils/queryPlane.js +49 -2
  31. package/lib/esm/utils/queryPlane.js.map +1 -1
  32. package/lib/esm/utils/reloadSource.js +1 -1
  33. package/lib/esm/utils/reloadSource.js.map +1 -1
  34. package/lib/esm/utils/serverTransactions.d.ts +22 -0
  35. package/lib/esm/utils/serverTransactions.js +81 -0
  36. package/lib/esm/utils/serverTransactions.js.map +1 -0
  37. package/lib/esm/utils/syncShapes.d.ts +120 -8
  38. package/lib/esm/utils/syncShapes.js +151 -32
  39. package/lib/esm/utils/syncShapes.js.map +1 -1
  40. package/package.json +2 -2
  41. package/readme.md +134 -24
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @\_linked/server
2
2
 
3
+ ## 3.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#223](https://github.com/linked-fw/server/pull/223) [`47f7d72`](https://github.com/linked-fw/server/commit/47f7d7294ef433e46c1098576286a5311ad55fb5) Thanks [@flyon](https://github.com/flyon)! - `POST /admin/sync-shapes` is removed from the admin channel. A host that changed an app's shape
8
+ code asks `POST /admin/graph-content {reload: [packages]}`: it reloads those packages (in dev),
9
+ registers the ontologies again, runs the shape check and answers the content of the app's graphs,
10
+ writing nothing; the host writes them (`LINKED_GRAPH_WRITER=host`). The route answers 404, and
11
+ `ShapeSyncResult`, `parseSyncShapesBody`, the `syncShapes` admin handler and
12
+ `syncOntologiesThenShapes` are gone.
13
+
14
+ The env boot flags `LINKED_SYNC_SHAPES_ON_BOOT` and `LINKED_SYNC_ONTOLOGIES_ON_BOOT` are removed.
15
+ The boot syncs the shapes and the ontologies when the app writes its own graphs
16
+ (`LINKED_GRAPH_WRITER` unset or `app`) and `linked.config` does not set `syncShapesOnBoot: false`,
17
+ the one switch for both. `shapeSyncOn(configFlag)` takes the config switch only;
18
+ `bootSyncsOntologies` is gone.
19
+
20
+ - [#223](https://github.com/linked-fw/server/pull/223) [`c7e09cc`](https://github.com/linked-fw/server/commit/c7e09cc4e8c1d04d418e5d730890e517f1406c62) Thanks [@flyon](https://github.com/flyon)! - The boot's ontology and shape syncs follow the app's storage
21
+ routing through core's routed syncs: each package's graphs go into every dataset the routing needs
22
+ them in, a graph routing no longer sends to the default dataset is dropped there, and one left in
23
+ another dataset is reported. With pins the boot logs one line naming the target datasets; the
24
+ earlier warning that the syncs write into the default dataset only is gone.
25
+
26
+ New `LINKED_GRAPH_WRITER=app|host`: under `host` the app writes no shape or ontology graph, and
27
+ `GET /admin/routes` reports `graphWriter`. New `POST /admin/graph-content {packages?, reload?}` on
28
+ the admin channel answers, writing nothing, each package's shape and ontology graph and the app's
29
+ own shape graph as `{graph, mediaType, text, hash, sources}`: the exact bytes and hash the
30
+ `'package'`-mode sync writes.
31
+
32
+ - [#223](https://github.com/linked-fw/server/pull/223) [`34ac38d`](https://github.com/linked-fw/server/commit/34ac38d7f5f7a8c695504598bd4b7b82dca77485) Thanks [@flyon](https://github.com/flyon)! - One read method, for `@_linked/core`'s `IDataset.readQuery` (core 2.46, DSL-JSON 1.10; the `@_linked/core` range is raised to `^2.46.0`). The generic provider's `selectQuery` and `askQuery` endpoints are replaced by one, `POST /call/@_linked/server/readQuery` (`@callable('public')`), which answers a select, a count or an ask: the body's kind is read before it is decoded (`readKindOfJSON`) and picks the access rule (`select` for a select or a count, `ask` for an ask), and the decoded query must be of that kind. A triples or terms read, a graph query and a write posted to it answer 400 (and roll back the caller's transaction they name). `/api/select` checks its read by kind the same way (an ask posted there is now answered, under the ask rule, where it was refused with 400; a body naming a transaction that does not decode now rolls it back). A store's function `read` rule sees `operation: 'ask'` for an ask, as it did from `askQuery`. `RemoteDataset` implements `readQuery` (select, count and ask go to the `readQuery` endpoint; triples and terms reads are refused without being sent) and declares `supports: ['ask']`, plus `'transactions'` after a ping that lists them. `graphQuery` and the write endpoints are unchanged. A client on an older `@_linked/server` gets `501 No provider` for its reads from this server.
33
+
34
+ - [#223](https://github.com/linked-fw/server/pull/223) [`6538642`](https://github.com/linked-fw/server/commit/653864235823695adbc1a1a21739226bace334bc) Thanks [@flyon](https://github.com/flyon)! - Store transactions over `/call`: `beginTransaction`, `commit(txId)` and `rollback(txId)` on the generic provider, and queries that carry a `txId` run in the caller's transaction (checked against the store rules as if sent alone; a refusal rolls it back). The server holds transactions only when every dataset `LinkedStorage` routes to declares `'transactions'`, and its `ping` then answers `supports: ['transactions']`. A txId is bound to the session's user; another caller's is refused. `RemoteDataset` declares `'transactions'` after such a ping (the no-url form now pings its backend for this), and gains `beginTransaction`, `commit` and `rollback`. A query with a txId on `/api/*`, and a graph query with one, are refused; any refusal of a request naming the caller's own transaction rolls it back.
35
+
3
36
  ## 3.13.0
4
37
 
5
38
  ### Minor Changes
@@ -4,15 +4,33 @@ export default class LincdServerBackendProvider extends BackendProvider {
4
4
  /**
5
5
  * Reachability check for a `RemoteDataset` pointed at this server: reads nothing, changes
6
6
  * nothing. A plain linked server has no token concept, so it always answers `{ok: true}`;
7
- * a host that serves the wire with tokens answers 401 for a refused one.
7
+ * a host that serves the wire with tokens answers 401 for a refused one. `supports` lists
8
+ * `'transactions'` when this server holds store transactions (every store it routes to does);
9
+ * `RemoteDataset` declares them only then.
8
10
  */
9
11
  ping(): Promise<{
10
12
  ok: true;
13
+ supports: string[];
11
14
  }>;
12
- selectQuery(json: any): Promise<unknown>;
13
- askQuery(json: any): Promise<boolean>;
14
- updateQuery(json: any): Promise<unknown>;
15
- createQuery(json: any): Promise<unknown>;
15
+ beginTransaction(): Promise<string>;
16
+ commit(txId: unknown): Promise<void>;
17
+ rollback(txId: unknown): Promise<void>;
18
+ /**
19
+ * Decode, check and run one generic query. `decode` turns the body into the builder the store
20
+ * runs and the access rule it is checked under (a write's rule is its endpoint's; a read's
21
+ * follows its kind, see `toReadQuery`). One that carries a txId runs in its transaction: the
22
+ * caller's own, checked against the store rules as if it were sent alone. Any refusal or
23
+ * failure of it (a body that does not decode included) rolls the transaction back.
24
+ */
25
+ private genericQuery;
26
+ /**
27
+ * Every read: a select, a count or an ask (DSL-JSON, told apart by `op`). The body's kind picks
28
+ * the access rule (`select` for a select or a count, `ask` for an ask; see `toReadQuery`).
29
+ * Triples and terms reads, graph queries and writes are refused with 400.
30
+ */
31
+ readQuery(json: any): Promise<number | boolean | string[] | import("@_linked/core/queries/IntermediateRepresentation.js").SelectResult | import("@_linked/core").Triple[]>;
32
+ updateQuery(json: any): Promise<import("@_linked/core/queries/IntermediateRepresentation.js").UpdateResult>;
33
+ createQuery(json: any): Promise<import("@_linked/core/queries/IntermediateRepresentation.js").ResultRow>;
16
34
  deleteQuery(json: any): Promise<import("@_linked/core/queries/DeleteQuery.js").DeleteResponse>;
17
35
  /**
18
36
  * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`), as
@@ -12,10 +12,25 @@ import { callable } from '@_linked/server-utils/utils/callable';
12
12
  import { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';
13
13
  import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
14
14
  import { LocalFileStore } from './shapes/filestores/LocalFileStore.js';
15
- import { authorizeGenericQuery, authorizeGraphQuery, graphQueryCallError, resolveQueryCatalog, toGraphQuery, toQueryBuilder, } from './utils/queryPlane.js';
15
+ import { authorizeGenericQuery, authorizeGraphQuery, graphQueryCallError, resolveQueryCatalog, toGraphQuery, toQueryBuilder, toReadQuery, } from './utils/queryPlane.js';
16
16
  import { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';
17
+ import { txIdOf } from '@_linked/core/queries/transactionQuery';
18
+ import { UnresolvedContextError } from '@_linked/core/queries/QueryContext';
19
+ import { isLinkedError } from '@_linked/core/utils/linkedError';
20
+ import { abandonTransactionOf, assertStoresHoldTransactions, storesHoldTransactions, transactionCallError, transactionCaller, transactions, } from './utils/serverTransactions.js';
17
21
  import path from 'path';
18
22
  import fs from 'fs/promises';
23
+ /** A write's decoder for `genericQuery`: the body as its builder, checked under the endpoint's own rule. */
24
+ function writeOf(operation) {
25
+ return (json, catalog) => ({
26
+ query: toQueryBuilder(json, catalog),
27
+ operation,
28
+ });
29
+ }
30
+ function assertTxId(txId) {
31
+ if (typeof txId !== 'string' || !txId)
32
+ throw new ServerCallError(400, 'Expected a transaction id');
33
+ }
19
34
  export default class LincdServerBackendProvider extends BackendProvider {
20
35
  async setupBeforeControllers() {
21
36
  if (!LinkedFileStorage.getDefaultStore()) {
@@ -34,10 +49,76 @@ export default class LincdServerBackendProvider extends BackendProvider {
34
49
  /**
35
50
  * Reachability check for a `RemoteDataset` pointed at this server: reads nothing, changes
36
51
  * nothing. A plain linked server has no token concept, so it always answers `{ok: true}`;
37
- * a host that serves the wire with tokens answers 401 for a refused one.
52
+ * a host that serves the wire with tokens answers 401 for a refused one. `supports` lists
53
+ * `'transactions'` when this server holds store transactions (every store it routes to does);
54
+ * `RemoteDataset` declares them only then.
38
55
  */
39
56
  async ping() {
40
- return { ok: true };
57
+ return { ok: true, supports: storesHoldTransactions() ? ['transactions'] : [] };
58
+ }
59
+ // ── Store transactions ───────────────────────────────────────────────────
60
+ //
61
+ // `RemoteDataset.beginTransaction()` / `commit(txId)` / `rollback(txId)` (core report 103). The
62
+ // server answers its own txId and holds a core `Transaction` for it, which opens a transaction
63
+ // in each store a query of it routes to. The txId is bound to the caller that began it.
64
+ // A server whose stores do not all hold transactions answers 501 `transactions-unsupported`;
65
+ // an unknown or expired txId 409 `transaction-unknown` (a rollback of one resolves); another
66
+ // caller's txId 403 `transaction-not-yours`.
67
+ async beginTransaction() {
68
+ try {
69
+ const caller = transactionCaller();
70
+ assertStoresHoldTransactions();
71
+ return transactions.begin(caller, LinkedStorage.begin());
72
+ }
73
+ catch (err) {
74
+ throw transactionCallError(err);
75
+ }
76
+ }
77
+ async commit(txId) {
78
+ assertTxId(txId);
79
+ try {
80
+ await transactions.commit(txId, transactionCaller());
81
+ }
82
+ catch (err) {
83
+ throw transactionCallError(err);
84
+ }
85
+ }
86
+ async rollback(txId) {
87
+ assertTxId(txId);
88
+ try {
89
+ await transactions.rollback(txId, transactionCaller());
90
+ }
91
+ catch (err) {
92
+ throw transactionCallError(err);
93
+ }
94
+ }
95
+ /**
96
+ * Decode, check and run one generic query. `decode` turns the body into the builder the store
97
+ * runs and the access rule it is checked under (a write's rule is its endpoint's; a read's
98
+ * follows its kind, see `toReadQuery`). One that carries a txId runs in its transaction: the
99
+ * caller's own, checked against the store rules as if it were sent alone. Any refusal or
100
+ * failure of it (a body that does not decode included) rolls the transaction back.
101
+ */
102
+ async genericQuery(json, decode, endpoint, inTransaction, direct) {
103
+ try {
104
+ const { query, operation } = decode(json, await resolveQueryCatalog(json));
105
+ const authorize = () => authorizeGenericQuery(operation, json, query, endpoint);
106
+ if (!txIdOf(query)) {
107
+ await authorize();
108
+ return await direct(query);
109
+ }
110
+ assertStoresHoldTransactions();
111
+ return await transactions.query(query, transactionCaller(), async () => {
112
+ assertStoresHoldTransactions();
113
+ await authorize();
114
+ }, inTransaction);
115
+ }
116
+ catch (err) {
117
+ // A select whose query context is not ready did not run: it fails nothing (as in core).
118
+ if (!isLinkedError(err, UnresolvedContextError))
119
+ await abandonTransactionOf(json);
120
+ throw transactionCallError(err);
121
+ }
41
122
  }
42
123
  // ── Generic query execution ──────────────────────────────────────────────
43
124
  //
@@ -49,8 +130,10 @@ export default class LincdServerBackendProvider extends BackendProvider {
49
130
  // A query cannot cross the wire live, so the client ships `toJSON()` and `fromJSON()`
50
131
  // rehydrates it here into a live, closed query. `LinkedStorage` then routes it by the
51
132
  // QUERY's shape, which is what decides the target dataset; the store instance never
52
- // participates. The host's catalog resolver (`setQueryCatalogResolver`) picks the shape
53
- // catalog the JSON is decoded in; the builder keeps it for both lowerings.
133
+ // participates. A query that carries a txId (DSL-JSON 1.5) runs in the caller's
134
+ // transaction instead (see "Store transactions" above). The host's catalog resolver
135
+ // (`setQueryCatalogResolver`) picks the shape catalog the JSON is decoded in; the builder
136
+ // keeps it for both lowerings.
54
137
  //
55
138
  // Every query is checked first against the access rules of the stores it
56
139
  // maps to, declared in the app's storage config (`withAccess`; a store with
@@ -58,30 +141,22 @@ export default class LincdServerBackendProvider extends BackendProvider {
58
141
  // rethought. The store runs the builder that was checked. They are declared
59
142
  // 'public' because that check, not the RPC layer, decides whether a session
60
143
  // is required.
61
- async selectQuery(json) {
62
- const query = toQueryBuilder(json, await resolveQueryCatalog(json));
63
- await authorizeGenericQuery('select', json, query, 'selectQuery');
64
- return LinkedStorage.selectQuery(query);
65
- }
66
- async askQuery(json) {
67
- const query = toQueryBuilder(json, await resolveQueryCatalog(json));
68
- await authorizeGenericQuery('ask', json, query, 'askQuery');
69
- return LinkedStorage.askQuery(query);
144
+ /**
145
+ * Every read: a select, a count or an ask (DSL-JSON, told apart by `op`). The body's kind picks
146
+ * the access rule (`select` for a select or a count, `ask` for an ask; see `toReadQuery`).
147
+ * Triples and terms reads, graph queries and writes are refused with 400.
148
+ */
149
+ async readQuery(json) {
150
+ return this.genericQuery(json, toReadQuery, 'readQuery', (tx, q) => tx.readQuery(q), (q) => LinkedStorage.readQuery(q));
70
151
  }
71
152
  async updateQuery(json) {
72
- const query = toQueryBuilder(json, await resolveQueryCatalog(json));
73
- await authorizeGenericQuery('update', json, query, 'updateQuery');
74
- return LinkedStorage.updateQuery(query);
153
+ return this.genericQuery(json, writeOf('update'), 'updateQuery', (tx, q) => tx.updateQuery(q), (q) => LinkedStorage.updateQuery(q));
75
154
  }
76
155
  async createQuery(json) {
77
- const query = toQueryBuilder(json, await resolveQueryCatalog(json));
78
- await authorizeGenericQuery('create', json, query, 'createQuery');
79
- return LinkedStorage.createQuery(query);
156
+ return this.genericQuery(json, writeOf('create'), 'createQuery', (tx, q) => tx.createQuery(q), (q) => LinkedStorage.createQuery(q));
80
157
  }
81
158
  async deleteQuery(json) {
82
- const query = toQueryBuilder(json, await resolveQueryCatalog(json));
83
- await authorizeGenericQuery('delete', json, query, 'deleteQuery');
84
- return LinkedStorage.deleteQuery(query);
159
+ return this.genericQuery(json, writeOf('delete'), 'deleteQuery', (tx, q) => tx.deleteQuery(q), (q) => LinkedStorage.deleteQuery(q));
85
160
  }
86
161
  /**
87
162
  * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`), as
@@ -92,6 +167,10 @@ export default class LincdServerBackendProvider extends BackendProvider {
92
167
  * `graphQueryCallError`).
93
168
  */
94
169
  async graphQuery(json) {
170
+ if (json && typeof json === 'object' && 'txId' in json) {
171
+ await abandonTransactionOf(json);
172
+ throw new ServerCallError(400, 'A graph query does not run in a transaction');
173
+ }
95
174
  const query = toGraphQuery(json);
96
175
  await authorizeGraphQuery(json, query, 'graphQuery');
97
176
  if (!query)
@@ -110,18 +189,30 @@ __decorate([
110
189
  __metadata("design:paramtypes", []),
111
190
  __metadata("design:returntype", Promise)
112
191
  ], LincdServerBackendProvider.prototype, "ping", null);
192
+ __decorate([
193
+ callable('public'),
194
+ __metadata("design:type", Function),
195
+ __metadata("design:paramtypes", []),
196
+ __metadata("design:returntype", Promise)
197
+ ], LincdServerBackendProvider.prototype, "beginTransaction", null);
198
+ __decorate([
199
+ callable('public'),
200
+ __metadata("design:type", Function),
201
+ __metadata("design:paramtypes", [Object]),
202
+ __metadata("design:returntype", Promise)
203
+ ], LincdServerBackendProvider.prototype, "commit", null);
113
204
  __decorate([
114
205
  callable('public'),
115
206
  __metadata("design:type", Function),
116
207
  __metadata("design:paramtypes", [Object]),
117
208
  __metadata("design:returntype", Promise)
118
- ], LincdServerBackendProvider.prototype, "selectQuery", null);
209
+ ], LincdServerBackendProvider.prototype, "rollback", null);
119
210
  __decorate([
120
211
  callable('public'),
121
212
  __metadata("design:type", Function),
122
213
  __metadata("design:paramtypes", [Object]),
123
214
  __metadata("design:returntype", Promise)
124
- ], LincdServerBackendProvider.prototype, "askQuery", null);
215
+ ], LincdServerBackendProvider.prototype, "readQuery", null);
125
216
  __decorate([
126
217
  callable('public'),
127
218
  __metadata("design:type", Function),
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { BackendProvider } from '@_linked/server-utils/utils/BackendProvider';\nimport { callable } from '@_linked/server-utils/utils/callable';\nimport { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { LocalFileStore } from './shapes/filestores/LocalFileStore.js';\nimport {\n authorizeGenericQuery,\n authorizeGraphQuery,\n graphQueryCallError,\n resolveQueryCatalog,\n toGraphQuery,\n toQueryBuilder,\n} from './utils/queryPlane.js';\nimport { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';\nimport path from 'path';\nimport fs from 'fs/promises';\n\nexport default class LincdServerBackendProvider extends BackendProvider {\n async setupBeforeControllers() {\n if (!LinkedFileStorage.getDefaultStore()) {\n LinkedFileStorage.setDefaultStore(\n new LocalFileStore(process.env.NODE_ENV + '-filestore')\n );\n }\n let fileSystemUploadPath = path.join('data', 'filestores');\n try {\n await fs.access(fileSystemUploadPath);\n } catch (err) {\n await fs.mkdir(path.join(fileSystemUploadPath, 'filestores'), {\n recursive: true,\n });\n }\n }\n\n /**\n * Reachability check for a `RemoteDataset` pointed at this server: reads nothing, changes\n * nothing. A plain linked server has no token concept, so it always answers `{ok: true}`;\n * a host that serves the wire with tokens answers 401 for a refused one.\n */\n @callable('public')\n async ping(): Promise<{ ok: true }> {\n return { ok: true };\n }\n\n // ── Generic query execution ──────────────────────────────────────────────\n //\n // These answer `RemoteDataset`: a browser's store calls\n // `Server.call(packageName, {method}, json)` (the PACKAGE form), and a remote app's store\n // posts the same `{args}` body to `{url}/call/@_linked/server/<method>`. Either way the\n // request arrives here on the generic provider rather than through Shape resolution.\n //\n // A query cannot cross the wire live, so the client ships `toJSON()` and `fromJSON()`\n // rehydrates it here into a live, closed query. `LinkedStorage` then routes it by the\n // QUERY's shape, which is what decides the target dataset; the store instance never\n // participates. The host's catalog resolver (`setQueryCatalogResolver`) picks the shape\n // catalog the JSON is decoded in; the builder keeps it for both lowerings.\n //\n // Every query is checked first against the access rules of the stores it\n // maps to, declared in the app's storage config (`withAccess`; a store with\n // no rule requires a session): see utils/queryPlane. INTERIM model, to be\n // rethought. The store runs the builder that was checked. They are declared\n // 'public' because that check, not the RPC layer, decides whether a session\n // is required.\n\n @callable('public')\n async selectQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('select', json, query, 'selectQuery');\n return LinkedStorage.selectQuery(query);\n }\n\n @callable('public')\n async askQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('ask', json, query, 'askQuery');\n return LinkedStorage.askQuery(query);\n }\n\n @callable('public')\n async updateQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('update', json, query, 'updateQuery');\n return LinkedStorage.updateQuery(query);\n }\n\n @callable('public')\n async createQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('create', json, query, 'createQuery');\n return LinkedStorage.createQuery(query);\n }\n\n @callable('public')\n async deleteQuery(json: any) {\n const query = toQueryBuilder(json, await resolveQueryCatalog(json));\n await authorizeGenericQuery('delete', json, query, 'deleteQuery');\n return LinkedStorage.deleteQuery(query);\n }\n\n /**\n * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`), as\n * `RemoteDataset.graphQuery` sends it. The query is plain data: core's `parseGraphQuery` checks\n * it, the `graph` rule of the store its record class routes to decides (no rule refuses), and\n * `LinkedStorage.graphQuery` runs it outside any shape catalog; no catalog is resolved for it,\n * so the check resolves the same store. A store's refusal answers with its code (see\n * `graphQueryCallError`).\n */\n @callable('public')\n async graphQuery(json: any) {\n const query = toGraphQuery(json);\n await authorizeGraphQuery(json, query, 'graphQuery');\n if (!query) throw new ServerCallError(400, 'Invalid graph query');\n try {\n return await LinkedStorage.graphQuery(query);\n } catch (err) {\n throw graphQueryCallError(err);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { BackendProvider } from '@_linked/server-utils/utils/BackendProvider';\nimport { callable } from '@_linked/server-utils/utils/callable';\nimport { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { LocalFileStore } from './shapes/filestores/LocalFileStore.js';\nimport type { QueryOperation } from '@_linked/server-utils/utils/QueryAccess';\nimport {\n authorizeGenericQuery,\n authorizeGraphQuery,\n graphQueryCallError,\n resolveQueryCatalog,\n toGraphQuery,\n toQueryBuilder,\n toReadQuery,\n} from './utils/queryPlane.js';\nimport { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';\nimport { txIdOf } from '@_linked/core/queries/transactionQuery';\nimport { UnresolvedContextError } from '@_linked/core/queries/QueryContext';\nimport { isLinkedError } from '@_linked/core/utils/linkedError';\nimport type { Transaction } from '@_linked/core/utils/Transaction';\nimport type { ShapeCatalog } from '@_linked/core/utils/ShapeCatalog';\nimport {\n abandonTransactionOf,\n assertStoresHoldTransactions,\n storesHoldTransactions,\n transactionCallError,\n transactionCaller,\n transactions,\n} from './utils/serverTransactions.js';\nimport path from 'path';\nimport fs from 'fs/promises';\n\n/** A write's decoder for `genericQuery`: the body as its builder, checked under the endpoint's own rule. */\nfunction writeOf(operation: Extract<QueryOperation, 'create' | 'update' | 'delete'>) {\n return (json: unknown, catalog: ShapeCatalog | undefined) => ({\n query: toQueryBuilder(json, catalog),\n operation,\n });\n}\n\nfunction assertTxId(txId: unknown): asserts txId is string {\n if (typeof txId !== 'string' || !txId) throw new ServerCallError(400, 'Expected a transaction id');\n}\n\nexport default class LincdServerBackendProvider extends BackendProvider {\n async setupBeforeControllers() {\n if (!LinkedFileStorage.getDefaultStore()) {\n LinkedFileStorage.setDefaultStore(\n new LocalFileStore(process.env.NODE_ENV + '-filestore')\n );\n }\n let fileSystemUploadPath = path.join('data', 'filestores');\n try {\n await fs.access(fileSystemUploadPath);\n } catch (err) {\n await fs.mkdir(path.join(fileSystemUploadPath, 'filestores'), {\n recursive: true,\n });\n }\n }\n\n /**\n * Reachability check for a `RemoteDataset` pointed at this server: reads nothing, changes\n * nothing. A plain linked server has no token concept, so it always answers `{ok: true}`;\n * a host that serves the wire with tokens answers 401 for a refused one. `supports` lists\n * `'transactions'` when this server holds store transactions (every store it routes to does);\n * `RemoteDataset` declares them only then.\n */\n @callable('public')\n async ping(): Promise<{ ok: true; supports: string[] }> {\n return { ok: true, supports: storesHoldTransactions() ? ['transactions'] : [] };\n }\n\n // ── Store transactions ───────────────────────────────────────────────────\n //\n // `RemoteDataset.beginTransaction()` / `commit(txId)` / `rollback(txId)` (core report 103). The\n // server answers its own txId and holds a core `Transaction` for it, which opens a transaction\n // in each store a query of it routes to. The txId is bound to the caller that began it.\n // A server whose stores do not all hold transactions answers 501 `transactions-unsupported`;\n // an unknown or expired txId 409 `transaction-unknown` (a rollback of one resolves); another\n // caller's txId 403 `transaction-not-yours`.\n\n @callable('public')\n async beginTransaction(): Promise<string> {\n try {\n const caller = transactionCaller();\n assertStoresHoldTransactions();\n return transactions.begin(caller, LinkedStorage.begin());\n } catch (err) {\n throw transactionCallError(err);\n }\n }\n\n @callable('public')\n async commit(txId: unknown): Promise<void> {\n assertTxId(txId);\n try {\n await transactions.commit(txId, transactionCaller());\n } catch (err) {\n throw transactionCallError(err);\n }\n }\n\n @callable('public')\n async rollback(txId: unknown): Promise<void> {\n assertTxId(txId);\n try {\n await transactions.rollback(txId, transactionCaller());\n } catch (err) {\n throw transactionCallError(err);\n }\n }\n\n /**\n * Decode, check and run one generic query. `decode` turns the body into the builder the store\n * runs and the access rule it is checked under (a write's rule is its endpoint's; a read's\n * follows its kind, see `toReadQuery`). One that carries a txId runs in its transaction: the\n * caller's own, checked against the store rules as if it were sent alone. Any refusal or\n * failure of it (a body that does not decode included) rolls the transaction back.\n */\n private async genericQuery<R>(\n json: any,\n decode: (json: unknown, catalog: ShapeCatalog | undefined) => { query: any; operation: QueryOperation },\n endpoint: string,\n inTransaction: (tx: Transaction, query: any) => Promise<R>,\n direct: (query: any) => Promise<R>\n ): Promise<R> {\n try {\n const { query, operation } = decode(json, await resolveQueryCatalog(json));\n const authorize = () => authorizeGenericQuery(operation, json, query, endpoint);\n if (!txIdOf(query)) {\n await authorize();\n return await direct(query);\n }\n assertStoresHoldTransactions();\n return await transactions.query(\n query,\n transactionCaller(),\n async () => {\n assertStoresHoldTransactions();\n await authorize();\n },\n inTransaction\n );\n } catch (err) {\n // A select whose query context is not ready did not run: it fails nothing (as in core).\n if (!isLinkedError(err, UnresolvedContextError)) await abandonTransactionOf(json);\n throw transactionCallError(err);\n }\n }\n\n // ── Generic query execution ──────────────────────────────────────────────\n //\n // These answer `RemoteDataset`: a browser's store calls\n // `Server.call(packageName, {method}, json)` (the PACKAGE form), and a remote app's store\n // posts the same `{args}` body to `{url}/call/@_linked/server/<method>`. Either way the\n // request arrives here on the generic provider rather than through Shape resolution.\n //\n // A query cannot cross the wire live, so the client ships `toJSON()` and `fromJSON()`\n // rehydrates it here into a live, closed query. `LinkedStorage` then routes it by the\n // QUERY's shape, which is what decides the target dataset; the store instance never\n // participates. A query that carries a txId (DSL-JSON 1.5) runs in the caller's\n // transaction instead (see \"Store transactions\" above). The host's catalog resolver\n // (`setQueryCatalogResolver`) picks the shape catalog the JSON is decoded in; the builder\n // keeps it for both lowerings.\n //\n // Every query is checked first against the access rules of the stores it\n // maps to, declared in the app's storage config (`withAccess`; a store with\n // no rule requires a session): see utils/queryPlane. INTERIM model, to be\n // rethought. The store runs the builder that was checked. They are declared\n // 'public' because that check, not the RPC layer, decides whether a session\n // is required.\n\n /**\n * Every read: a select, a count or an ask (DSL-JSON, told apart by `op`). The body's kind picks\n * the access rule (`select` for a select or a count, `ask` for an ask; see `toReadQuery`).\n * Triples and terms reads, graph queries and writes are refused with 400.\n */\n @callable('public')\n async readQuery(json: any) {\n return this.genericQuery(json, toReadQuery, 'readQuery', (tx, q) => tx.readQuery(q), (q) => LinkedStorage.readQuery(q));\n }\n\n @callable('public')\n async updateQuery(json: any) {\n return this.genericQuery(json, writeOf('update'), 'updateQuery', (tx, q) => tx.updateQuery(q), (q) => LinkedStorage.updateQuery(q));\n }\n\n @callable('public')\n async createQuery(json: any) {\n return this.genericQuery(json, writeOf('create'), 'createQuery', (tx, q) => tx.createQuery(q), (q) => LinkedStorage.createQuery(q));\n }\n\n @callable('public')\n async deleteQuery(json: any) {\n return this.genericQuery(json, writeOf('delete'), 'deleteQuery', (tx, q) => tx.deleteQuery(q), (q) => LinkedStorage.deleteQuery(q));\n }\n\n /**\n * A whole-graph operation (`graph_create`, `graph_replace`, `graph_patch`, `graph_drop`), as\n * `RemoteDataset.graphQuery` sends it. The query is plain data: core's `parseGraphQuery` checks\n * it, the `graph` rule of the store its record class routes to decides (no rule refuses), and\n * `LinkedStorage.graphQuery` runs it outside any shape catalog; no catalog is resolved for it,\n * so the check resolves the same store. A store's refusal answers with its code (see\n * `graphQueryCallError`).\n */\n @callable('public')\n async graphQuery(json: any) {\n if (json && typeof json === 'object' && 'txId' in json) {\n await abandonTransactionOf(json);\n throw new ServerCallError(400, 'A graph query does not run in a transaction');\n }\n const query = toGraphQuery(json);\n await authorizeGraphQuery(json, query, 'graphQuery');\n if (!query) throw new ServerCallError(400, 'Invalid graph query');\n try {\n return await LinkedStorage.graphQuery(query);\n } catch (err) {\n throw graphQueryCallError(err);\n }\n }\n}\n"]}
@@ -4,7 +4,7 @@ export declare class LincdAPI extends Shape {
4
4
  process(request: any, response: any, httpMethod: 'get' | 'post' | 'put' | 'delete'): Promise<void>;
5
5
  post_select({ query }: {
6
6
  query: any;
7
- }): Promise<unknown>;
7
+ }): Promise<number | boolean | string[] | import("@_linked/core/queries/IntermediateRepresentation.js").SelectResult | import("@_linked/core").Triple[]>;
8
8
  post_create({ query }: {
9
9
  query: any;
10
10
  }): Promise<unknown>;
@@ -10,8 +10,44 @@ import { server } from '../ontologies/server.js';
10
10
  import { JSONParser } from '@_linked/server-utils/utils/JSONParser';
11
11
  import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
12
12
  import { JSONWriter } from '@_linked/server-utils/utils/JSONWriter';
13
- import { authorizeGenericQuery, authorizeRawQuery, resolveQueryCatalog, toQueryBuilder, } from '../utils/queryPlane.js';
13
+ import { authorizeGenericQuery, authorizeRawQuery, resolveQueryCatalog, toQueryBuilder, toReadQuery, } from '../utils/queryPlane.js';
14
14
  import { getRpcExposureMode } from '../utils/rpcExposure.js';
15
+ import { txIdOf } from '@_linked/core/queries/transactionQuery';
16
+ import { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';
17
+ import { abandonTransactionOf } from '../utils/serverTransactions.js';
18
+ /**
19
+ * The builder of an `/api/*` query, wrapped (a query builder is a thenable: resolving an async
20
+ * function with one would run it). A query of a store transaction (a `txId`) is refused (400):
21
+ * transactions run over `/call/@_linked/server`, which holds them; here it would run outside.
22
+ * The caller's transaction it names is rolled back, as for any refused query of it.
23
+ */
24
+ async function apiBuilder(query) {
25
+ const builder = toQueryBuilder(query, await resolveQueryCatalog(query));
26
+ await refuseTransactionQuery(query, builder);
27
+ return { builder };
28
+ }
29
+ /**
30
+ * The read of an `/api/select` query (a select, a count or an ask) with the access rule its kind
31
+ * is checked under (see `toReadQuery`); a query of a transaction is refused as in {@link apiBuilder}.
32
+ */
33
+ async function apiRead(query) {
34
+ let read;
35
+ try {
36
+ read = toReadQuery(query, await resolveQueryCatalog(query));
37
+ }
38
+ catch (error) {
39
+ await abandonTransactionOf(query);
40
+ throw error;
41
+ }
42
+ await refuseTransactionQuery(query, read.query);
43
+ return { builder: read.query, operation: read.operation };
44
+ }
45
+ async function refuseTransactionQuery(query, builder) {
46
+ if (txIdOf(builder)) {
47
+ await abandonTransactionOf(query);
48
+ throw new ServerCallError(400, 'A query of a transaction is sent to /call/@_linked/server, not /api');
49
+ }
50
+ }
15
51
  /** An error carrying an HTTP status, such as a `ServerCallError` (401/403). */
16
52
  function isStatusError(error) {
17
53
  return (!!error &&
@@ -56,22 +92,22 @@ let LincdAPI = class LincdAPI extends Shape {
56
92
  }
57
93
  }
58
94
  async post_select({ query }) {
59
- const builder = toQueryBuilder(query, await resolveQueryCatalog(query));
60
- await authorizeGenericQuery('select', query, builder, 'api/select');
61
- return LinkedStorage.selectQuery(builder);
95
+ const { builder, operation } = await apiRead(query);
96
+ await authorizeGenericQuery(operation, query, builder, 'api/select');
97
+ return LinkedStorage.readQuery(builder);
62
98
  }
63
99
  async post_create({ query }) {
64
- const builder = toQueryBuilder(query, await resolveQueryCatalog(query));
100
+ const { builder } = await apiBuilder(query);
65
101
  await authorizeGenericQuery('create', query, builder, 'api/create');
66
102
  return LinkedStorage.createQuery(builder);
67
103
  }
68
104
  async post_update({ query }) {
69
- const builder = toQueryBuilder(query, await resolveQueryCatalog(query));
105
+ const { builder } = await apiBuilder(query);
70
106
  await authorizeGenericQuery('update', query, builder, 'api/update');
71
107
  return LinkedStorage.updateQuery(builder);
72
108
  }
73
109
  async post_delete({ query }) {
74
- const builder = toQueryBuilder(query, await resolveQueryCatalog(query));
110
+ const { builder } = await apiBuilder(query);
75
111
  await authorizeGenericQuery('delete', query, builder, 'api/delete');
76
112
  return LinkedStorage.deleteQuery(builder);
77
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LincdAPI.js","sourceRoot":"","sources":["../../../src/shapes/LincdAPI.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,wCAAwC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,wCAAwC,CAAC;AAEpE,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,+EAA+E;AAC/E,SAAS,aAAa,CAAC,KAAU;IAC/B,OAAO,CACL,CAAC,CAAC,KAAK;QACP,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;QAChC,KAAK,CAAC,MAAM,IAAI,GAAG;QACnB,KAAK,CAAC,MAAM,GAAG,GAAG;QAClB,KAAK,CAAC,IAAI,KAAK,iBAAiB,CACjC,CAAC;AACJ,CAAC;AAED,iGAAiG;AACjG,gGAAgG;AAChG,4EAA4E;AAErE,IAAM,QAAQ,GAAd,MAAM,QAAS,SAAQ,KAAK;IAGjC,KAAK,CAAC,OAAO,CACX,OAAO,EACP,QAAQ,EACR,UAA6C;QAE7C,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QACxC,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,CAAM,OAAO,CAAC,IAAI,EAAE;YACrD,YAAY,EAAE,kBAAkB,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM;SACrE,CAAC,CAAC;QAEH,kCAAkC;QAClC,IAAI,WAAW,GAAG,UAAU,GAAG,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC/D,IAAI,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,UAAU,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,WAAW,CAAC,CAAC;QACpD,CAAC;QAED,+DAA+D;QAC/D,oBAAoB;QACpB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;YAChE,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;gBAC3D,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;gBACjD,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,QAAQ,CAAC,WAAW,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClD,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC7D,OAAO;YACT,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,0BAA0B,WAAW,GAAG,EAAE,KAAK,CAAC,CAAC;YAC/D,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;gBAC1B,MAAM,OAAO,GACX,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC;gBAClE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE;QACzB,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,MAAM,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,MAAM,qBAAqB,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;QACpE,OAAO,aAAa,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IACD,KAAK,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE;QACzB,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,MAAM,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,MAAM,qBAAqB,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;QACpE,OAAO,aAAa,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IACD,KAAK,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE;QACzB,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,MAAM,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,MAAM,qBAAqB,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;QACpE,OAAO,aAAa,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IACD,KAAK,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE;QACzB,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,MAAM,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,MAAM,qBAAqB,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;QACpE,OAAO,aAAa,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE;QAC7B,wEAAwE;QACxE,+DAA+D;QAC/D,MAAM,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;QACjD,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC5B,OAAQ,aAAa,CAAC,iBAAiB,EAA6B,CAAC,QAAQ,CAC3E,KAAK,CACN,CAAC;IACJ,CAAC;IAEO,oBAAoB;QAC1B,IAAI,CAAE,aAAa,CAAC,iBAAiB,EAA6B,CAAC,QAAQ,EAAE,CAAC;YAC5E,MAAM,IAAI,KAAK,CACb,kBACE,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,iBAAiB,EAAE,CAAC,CAAC,WAAW;iBACjE,IACL,uCAAuC,CACxC,CAAC;QACJ,CAAC;IACH,CAAC;;AAhFM,oBAAW,GAAG,MAAM,CAAC,QAAQ,AAAlB,CAAmB;AAD1B,QAAQ;IADpB,WAAW,CAAC,EAAC,IAAI,EAAE,UAAU,EAAC,CAAC;GACnB,QAAQ,CAkFpB","sourcesContent":["import { Shape } from '@_linked/core/shapes/Shape';\nimport { linkedShape } from '../package.js';\nimport { server } from '../ontologies/server.js';\nimport { JSONParser } from '@_linked/server-utils/utils/JSONParser';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { JSONWriter } from '@_linked/server-utils/utils/JSONWriter';\nimport { SparqlDataset as SPARQLStore } from '@_linked/core/sparql/SparqlDataset';\nimport {\n authorizeGenericQuery,\n authorizeRawQuery,\n resolveQueryCatalog,\n toQueryBuilder,\n} from '../utils/queryPlane.js';\nimport { getRpcExposureMode } from '../utils/rpcExposure.js';\n\n/** An error carrying an HTTP status, such as a `ServerCallError` (401/403). */\nfunction isStatusError(error: any): error is { status: number; message: string } {\n return (\n !!error &&\n typeof error.status === 'number' &&\n error.status >= 400 &&\n error.status < 600 &&\n error.name === 'ServerCallError'\n );\n}\n\n// Named explicitly: tsc emits `let X = class X`, and any later esbuild pass over that JS (Vite's\n// SSR `define` replacement runs one on every file mentioning a defined `process.env.*`) renames\n// the inner binding to `X2`, which would otherwise become this shape's IRI.\n@linkedShape({name: 'LincdAPI'})\nexport class LincdAPI extends Shape {\n static targetClass = server.LincdAPI;\n\n async process(\n request,\n response,\n httpMethod: 'get' | 'post' | 'put' | 'delete'\n ) {\n let { method, action } = request.params;\n const body = JSONParser.parseObject<any>(request.body, {\n shapeClasses: getRpcExposureMode() === 'enforce' ? 'reject' : 'warn',\n });\n\n //replace - with _ in action names\n let localMethod = httpMethod + '_' + method.replace(/-/g, '_');\n if (typeof this[localMethod] !== 'function') {\n throw new Error('Unknown method: ' + localMethod);\n }\n\n // The query routes (`post_*`) check access themselves, through\n // utils/queryPlane.\n try {\n const result = await this[localMethod](body, request, response);\n if (typeof result !== 'undefined' || !response.headersSent) {\n const jsonObject = JSONWriter.toJsObject(result);\n response.json(jsonObject);\n }\n } catch (error) {\n if (!response.headersSent && isStatusError(error)) {\n response.status(error.status).json({ error: error.message });\n return;\n }\n console.error(`Error while processing ${localMethod}:`, error);\n if (!response.headersSent) {\n const message =\n error instanceof Error ? error.message : 'Unknown server error';\n response.status(500).json({ error: message });\n }\n }\n }\n\n async post_select({ query }) {\n const builder = toQueryBuilder(query, await resolveQueryCatalog(query));\n await authorizeGenericQuery('select', query, builder, 'api/select');\n return LinkedStorage.selectQuery(builder);\n }\n async post_create({ query }) {\n const builder = toQueryBuilder(query, await resolveQueryCatalog(query));\n await authorizeGenericQuery('create', query, builder, 'api/create');\n return LinkedStorage.createQuery(builder);\n }\n async post_update({ query }) {\n const builder = toQueryBuilder(query, await resolveQueryCatalog(query));\n await authorizeGenericQuery('update', query, builder, 'api/update');\n return LinkedStorage.updateQuery(builder);\n }\n async post_delete({ query }) {\n const builder = toQueryBuilder(query, await resolveQueryCatalog(query));\n await authorizeGenericQuery('delete', query, builder, 'api/delete');\n return LinkedStorage.deleteQuery(builder);\n }\n\n async post_select_raw({ query }) {\n // A raw SPARQL string cannot be analysed for the shapes it touches: the\n // `rawQueries` setting decides (a session by default, or off).\n await authorizeRawQuery(query, 'api/select-raw');\n this.checkRawQuerySupport();\n return (LinkedStorage.getDefaultDataset() as unknown as SPARQLStore).rawQuery(\n query\n );\n }\n\n private checkRawQuerySupport() {\n if (!(LinkedStorage.getDefaultDataset() as unknown as SPARQLStore).rawQuery) {\n throw new Error(\n `Default store (${\n Object.getPrototypeOf(LinkedStorage.getDefaultDataset()).constructor\n .name\n }) does not support raw SPARQL queries`\n );\n }\n }\n}\n"]}
1
+ {"version":3,"file":"LincdAPI.js","sourceRoot":"","sources":["../../../src/shapes/LincdAPI.ts"],"names":[],"mappings":"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","sourcesContent":["import { Shape } from '@_linked/core/shapes/Shape';\nimport { linkedShape } from '../package.js';\nimport { server } from '../ontologies/server.js';\nimport { JSONParser } from '@_linked/server-utils/utils/JSONParser';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { JSONWriter } from '@_linked/server-utils/utils/JSONWriter';\nimport { SparqlDataset as SPARQLStore } from '@_linked/core/sparql/SparqlDataset';\nimport {\n authorizeGenericQuery,\n authorizeRawQuery,\n resolveQueryCatalog,\n toQueryBuilder,\n toReadQuery,\n type ReadOperation,\n} from '../utils/queryPlane.js';\nimport { getRpcExposureMode } from '../utils/rpcExposure.js';\nimport { txIdOf } from '@_linked/core/queries/transactionQuery';\nimport { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';\nimport { abandonTransactionOf } from '../utils/serverTransactions.js';\n\n/**\n * The builder of an `/api/*` query, wrapped (a query builder is a thenable: resolving an async\n * function with one would run it). A query of a store transaction (a `txId`) is refused (400):\n * transactions run over `/call/@_linked/server`, which holds them; here it would run outside.\n * The caller's transaction it names is rolled back, as for any refused query of it.\n */\nasync function apiBuilder(query: unknown): Promise<{ builder: any }> {\n const builder = toQueryBuilder(query, await resolveQueryCatalog(query));\n await refuseTransactionQuery(query, builder);\n return { builder };\n}\n\n/**\n * The read of an `/api/select` query (a select, a count or an ask) with the access rule its kind\n * is checked under (see `toReadQuery`); a query of a transaction is refused as in {@link apiBuilder}.\n */\nasync function apiRead(query: unknown): Promise<{ builder: any; operation: ReadOperation }> {\n let read: { query: any; operation: ReadOperation };\n try {\n read = toReadQuery(query, await resolveQueryCatalog(query));\n } catch (error) {\n await abandonTransactionOf(query);\n throw error;\n }\n await refuseTransactionQuery(query, read.query);\n return { builder: read.query, operation: read.operation };\n}\n\nasync function refuseTransactionQuery(query: unknown, builder: any): Promise<void> {\n if (txIdOf(builder)) {\n await abandonTransactionOf(query);\n throw new ServerCallError(400, 'A query of a transaction is sent to /call/@_linked/server, not /api');\n }\n}\n\n/** An error carrying an HTTP status, such as a `ServerCallError` (401/403). */\nfunction isStatusError(error: any): error is { status: number; message: string } {\n return (\n !!error &&\n typeof error.status === 'number' &&\n error.status >= 400 &&\n error.status < 600 &&\n error.name === 'ServerCallError'\n );\n}\n\n// Named explicitly: tsc emits `let X = class X`, and any later esbuild pass over that JS (Vite's\n// SSR `define` replacement runs one on every file mentioning a defined `process.env.*`) renames\n// the inner binding to `X2`, which would otherwise become this shape's IRI.\n@linkedShape({name: 'LincdAPI'})\nexport class LincdAPI extends Shape {\n static targetClass = server.LincdAPI;\n\n async process(\n request,\n response,\n httpMethod: 'get' | 'post' | 'put' | 'delete'\n ) {\n let { method, action } = request.params;\n const body = JSONParser.parseObject<any>(request.body, {\n shapeClasses: getRpcExposureMode() === 'enforce' ? 'reject' : 'warn',\n });\n\n //replace - with _ in action names\n let localMethod = httpMethod + '_' + method.replace(/-/g, '_');\n if (typeof this[localMethod] !== 'function') {\n throw new Error('Unknown method: ' + localMethod);\n }\n\n // The query routes (`post_*`) check access themselves, through\n // utils/queryPlane.\n try {\n const result = await this[localMethod](body, request, response);\n if (typeof result !== 'undefined' || !response.headersSent) {\n const jsonObject = JSONWriter.toJsObject(result);\n response.json(jsonObject);\n }\n } catch (error) {\n if (!response.headersSent && isStatusError(error)) {\n response.status(error.status).json({ error: error.message });\n return;\n }\n console.error(`Error while processing ${localMethod}:`, error);\n if (!response.headersSent) {\n const message =\n error instanceof Error ? error.message : 'Unknown server error';\n response.status(500).json({ error: message });\n }\n }\n }\n\n async post_select({ query }) {\n const { builder, operation } = await apiRead(query);\n await authorizeGenericQuery(operation, query, builder, 'api/select');\n return LinkedStorage.readQuery(builder);\n }\n async post_create({ query }) {\n const { builder } = await apiBuilder(query);\n await authorizeGenericQuery('create', query, builder, 'api/create');\n return LinkedStorage.createQuery(builder);\n }\n async post_update({ query }) {\n const { builder } = await apiBuilder(query);\n await authorizeGenericQuery('update', query, builder, 'api/update');\n return LinkedStorage.updateQuery(builder);\n }\n async post_delete({ query }) {\n const { builder } = await apiBuilder(query);\n await authorizeGenericQuery('delete', query, builder, 'api/delete');\n return LinkedStorage.deleteQuery(builder);\n }\n\n async post_select_raw({ query }) {\n // A raw SPARQL string cannot be analysed for the shapes it touches: the\n // `rawQueries` setting decides (a session by default, or off).\n await authorizeRawQuery(query, 'api/select-raw');\n this.checkRawQuerySupport();\n return (LinkedStorage.getDefaultDataset() as unknown as SPARQLStore).rawQuery(\n query\n );\n }\n\n private checkRawQuerySupport() {\n if (!(LinkedStorage.getDefaultDataset() as unknown as SPARQLStore).rawQuery) {\n throw new Error(\n `Default store (${\n Object.getPrototypeOf(LinkedStorage.getDefaultDataset()).constructor\n .name\n }) does not support raw SPARQL queries`\n );\n }\n }\n}\n"]}