@geolonia/geonicdb-sdk 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -248,8 +248,10 @@ const entities = await db.getEntities({ type: 'GeoJSON' });
248
248
  > restricts where `@context` may come from based on `Content-Type` and forbids
249
249
  > mixing the two. Since the SDK always sends `application/ld+json`, adding a
250
250
  > JSON-LD `Link` header to a `POST` / `PATCH` / `PUT` via the `request()` /
251
- > `requestRaw()` escape hatch returns `400 BadRequestData`. Put the `@context`
252
- > in the body for writes; the `Link` header remains the channel for reads.
251
+ > `requestRaw()` escape hatch (`options.headers`) returns `400 BadRequestData`. Put the `@context`
252
+ > in the body for writes; the `Link` header remains the channel for reads and is
253
+ > passed as `options.headers`. `Authorization`, `DPoP`, `Fiware-Service`, and
254
+ > `NGSILD-Tenant` (any casing) are reserved and throw `TypeError`. A CR or LF in a header name or value also throws `TypeError`.
253
255
 
254
256
  ```typescript
255
257
  // Create
@@ -644,8 +646,28 @@ handle.stop();
644
646
 
645
647
  | Method | Parameters | Returns | Description |
646
648
  |--------|-----------|---------|-------------|
647
- | `request(method, path, body?)` | `string, string, unknown` | `Promise<unknown>` | Authenticated request with automatic JSON parsing |
648
- | `requestRaw(method, path, body?)` | `string, string, unknown` | `Promise<Response>` | Authenticated request returning raw `Response` (for accessing headers) |
649
+ | `request(method, path, body?, options?)` | `string, string, unknown, RequestOptions` | `Promise<unknown>` | Authenticated request with automatic JSON parsing. `options.headers` overrides `Content-Type` / `Accept` (header names are case-insensitive) |
650
+ | `requestRaw(method, path, body?, options?)` | `string, string, unknown, RequestOptions` | `Promise<Response>` | Authenticated request returning raw `Response` (for accessing headers). Same `options` as `request()` |
651
+
652
+ #### Calling `POST /mcp` (#3322)
653
+
654
+ `POST /mcp` requires `Content-Type: application/json` and `Accept: application/json, text/event-stream`. The SDK defaults are `application/ld+json`, so omitting `headers` returns **406**.
655
+
656
+ ```javascript
657
+ const rpc = await db.request('POST', '/mcp', {
658
+ jsonrpc: '2.0',
659
+ id: 1,
660
+ method: 'tools/call',
661
+ params: { name: 'config', arguments: { resource: 'rules', action: 'list' } },
662
+ }, {
663
+ headers: {
664
+ 'Content-Type': 'application/json',
665
+ Accept: 'application/json, text/event-stream',
666
+ },
667
+ });
668
+ ```
669
+
670
+ The body is JSON-RPC, not SSE. Tool failures are still HTTP 200 — check `result.isError`. `Authorization`, `DPoP`, `Fiware-Service`, and `NGSILD-Tenant` (any casing) throw `TypeError`, as does a CR or LF in a header name or value. A GET or HEAD that passes `headers` skips the ETag cache and in-flight dedup. After `login()`, the DPoP session calls `/mcp` as that user. `config` / `admin` need `tenant_admin`; an API-key principal gets `Admin privileges required`.
649
671
 
650
672
  ### Cache & Polling
651
673
 
package/geonicdb.cjs CHANGED
@@ -585,6 +585,46 @@ function isCacheableMethod(method) {
585
585
  }
586
586
 
587
587
  // src/sdk/auth.ts
588
+ var SDK_RESERVED_REQUEST_HEADERS = /* @__PURE__ */ new Set([
589
+ "authorization",
590
+ "dpop",
591
+ "fiware-service",
592
+ "ngsild-tenant"
593
+ ]);
594
+ function assertCallerHeadersAllowed(headers) {
595
+ if (!headers) return;
596
+ for (const [name, value] of Object.entries(headers)) {
597
+ if (/[\r\n]/.test(name) || /[\r\n]/.test(value)) {
598
+ throw new TypeError("Header name or value contains CR or LF");
599
+ }
600
+ if (SDK_RESERVED_REQUEST_HEADERS.has(name.toLowerCase())) {
601
+ throw new TypeError(
602
+ `Header "${name}" is reserved; the SDK sets Authorization, DPoP, Fiware-Service, and NGSILD-Tenant`
603
+ );
604
+ }
605
+ }
606
+ }
607
+ function mergeCallerHeaders(defaults, extraHeaders) {
608
+ const merged = new Headers(defaults);
609
+ const callerNames = /* @__PURE__ */ new Map();
610
+ if (extraHeaders) {
611
+ for (const [name, value] of Object.entries(extraHeaders)) {
612
+ merged.set(name, value);
613
+ callerNames.set(name.toLowerCase(), name);
614
+ }
615
+ }
616
+ const headers = {};
617
+ for (const name of Object.keys(defaults)) {
618
+ if (callerNames.has(name.toLowerCase())) continue;
619
+ const value = merged.get(name);
620
+ if (value !== null) headers[name] = value;
621
+ }
622
+ for (const [lower, name] of callerNames) {
623
+ const value = merged.get(lower);
624
+ if (value !== null) headers[name] = value;
625
+ }
626
+ return headers;
627
+ }
588
628
  var _AuthManager = class _AuthManager {
589
629
  constructor(baseUrl, apiKey, tenant, debug = false, anonymous = false) {
590
630
  __publicField(this, "_baseUrl");
@@ -1205,11 +1245,12 @@ var _AuthManager = class _AuthManager {
1205
1245
  * the cache.
1206
1246
  * - Deduplicates concurrent in-flight requests to the same path.
1207
1247
  */
1208
- async request(method, path, body) {
1248
+ async request(method, path, body, headers) {
1209
1249
  this._log(method, path);
1210
- if (!this._cache || !isCacheableMethod(method) || body !== void 0) {
1250
+ assertCallerHeadersAllowed(headers);
1251
+ if (!this._cache || !isCacheableMethod(method) || body !== void 0 || headers !== void 0) {
1211
1252
  const token = this.isAnonymous() ? null : await this.ensureToken();
1212
- const res = await this._doAuthenticatedRequest(method, path, body, token);
1253
+ const res = await this._doAuthenticatedRequest(method, path, body, token, 0, headers);
1213
1254
  this._log(method, path, "\u2192", res.status);
1214
1255
  return res;
1215
1256
  }
@@ -1304,11 +1345,13 @@ var _AuthManager = class _AuthManager {
1304
1345
  reqToken,
1305
1346
  dpopNonce
1306
1347
  ) : null;
1307
- const headers = {
1308
- "Content-Type": "application/ld+json",
1309
- Accept: "application/ld+json",
1310
- ...extraHeaders
1311
- };
1348
+ const headers = mergeCallerHeaders(
1349
+ {
1350
+ "Content-Type": "application/ld+json",
1351
+ Accept: "application/ld+json"
1352
+ },
1353
+ extraHeaders
1354
+ );
1312
1355
  if (reqToken !== null) {
1313
1356
  headers["Authorization"] = (reqIsDPoP ? "DPoP " : "Bearer ") + reqToken;
1314
1357
  }
@@ -2216,8 +2259,8 @@ var GeonicDB = class extends EventEmitter {
2216
2259
  * 空だが、サーバ実装が `Content-Type: application/ld+json` を付けて返す
2217
2260
  * ケースがあり、そこで `res.json()` が SyntaxError を投げて止まっていた。
2218
2261
  */
2219
- async request(method, path, body) {
2220
- const res = await this._auth.request(method, path, body);
2262
+ async request(method, path, body, options) {
2263
+ const res = await this._auth.request(method, path, body, options?.headers);
2221
2264
  if (!res.ok) {
2222
2265
  const e = await res.json().catch(() => ({}));
2223
2266
  throw createErrorFromResponse(res.status, e, "Request failed: " + res.status);
@@ -2237,8 +2280,8 @@ var GeonicDB = class extends EventEmitter {
2237
2280
  * Make an authenticated API request and return the raw Response object.
2238
2281
  * Use this when you need access to response headers (e.g. NGSILD-Results-Count).
2239
2282
  */
2240
- async requestRaw(method, path, body) {
2241
- return this._auth.request(method, path, body);
2283
+ async requestRaw(method, path, body, options) {
2284
+ return this._auth.request(method, path, body, options?.headers);
2242
2285
  }
2243
2286
  // --- WebSocket ---
2244
2287
  /** Establish WebSocket connection (authentication is automatic). */