@powerhousedao/reactor-api 6.2.2 → 6.2.3-dev.1
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/dist/{adapter-http-express-CgVPgEMm.mjs → adapter-http-express-54gdI9bu.mjs} +40 -26
- package/dist/adapter-http-express-54gdI9bu.mjs.map +1 -0
- package/dist/{adapter-http-fastify-D98iRljY.mjs → adapter-http-fastify-DmccBVOe.mjs} +45 -22
- package/dist/adapter-http-fastify-DmccBVOe.mjs.map +1 -0
- package/dist/index.d.mts +452 -228
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1203 -34
- package/dist/index.mjs.map +1 -1
- package/dist/src/packages/vite-loader.mjs +1 -1
- package/dist/{utils-lHoPB-ft.mjs → utils-XWVecaNn.mjs} +6 -3
- package/dist/utils-XWVecaNn.mjs.map +1 -0
- package/package.json +14 -14
- package/dist/adapter-http-express-CgVPgEMm.mjs.map +0 -1
- package/dist/adapter-http-fastify-D98iRljY.mjs.map +0 -1
- package/dist/utils-lHoPB-ft.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
3
|
-
import { _ as createAuthorizationService, a as isSubgraphClass, b as MalformedStoredOperationError, d as loadSubgraphs, f as BaseSubgraph, g as AuthorizedDocumentHandle, h as AuthorizationPolicy, i as buildGraphqlOperations, l as loadDocumentModels, m as createCanonicalDocumentIdResolver, n as buildGraphQlDriveDocument, o as debounce, p as CanonicalDocumentIdResolutionError, r as buildGraphqlOperation, s as extractUpgradeManifests, t as buildGraphQlDocument, u as loadProcessors, v as AuthEvaluationUnsupportedError, y as ForbiddenError } from "./utils-
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="46ad8545-02e9-5fb8-b003-7b9b93f82f75")}catch(e){}}();
|
|
3
|
+
import { _ as createAuthorizationService, a as isSubgraphClass, b as MalformedStoredOperationError, d as loadSubgraphs, f as BaseSubgraph, g as AuthorizedDocumentHandle, h as AuthorizationPolicy, i as buildGraphqlOperations, l as loadDocumentModels, m as createCanonicalDocumentIdResolver, n as buildGraphQlDriveDocument, o as debounce, p as CanonicalDocumentIdResolutionError, r as buildGraphqlOperation, s as extractUpgradeManifests, t as buildGraphQlDocument, u as loadProcessors, v as AuthEvaluationUnsupportedError, y as ForbiddenError } from "./utils-XWVecaNn.mjs";
|
|
4
4
|
import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
|
|
5
5
|
import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
|
|
6
6
|
import { gql } from "graphql-tag";
|
|
7
7
|
import { GraphQLError, Kind, parse, print } from "graphql";
|
|
8
8
|
import { AuthEnforcementDisabledError, DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, ModelReadGate, PropagationMode as PropagationMode$1, SyncScopeGate, consolidateSyncOperations, createReactorHostModuleBase, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, readDecisionModel, syncOperationErrorType } from "@powerhousedao/reactor";
|
|
9
9
|
import { ConsoleLogger, childLogger, documentModelDocumentModelModule } from "document-model";
|
|
10
|
-
import path from "node:path";
|
|
10
|
+
import path, { join } from "node:path";
|
|
11
11
|
import { match } from "path-to-regexp";
|
|
12
|
+
import posix from "node:path/posix";
|
|
13
|
+
import { Readable } from "node:stream";
|
|
14
|
+
import fs, { readFileSync, watchFile } from "node:fs";
|
|
15
|
+
import { pipeline } from "node:stream/promises";
|
|
16
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
12
17
|
import { buildSubgraphSchema } from "@apollo/subgraph";
|
|
13
18
|
import { typeDefs } from "@powerhousedao/document-engineering/graphql";
|
|
14
19
|
import { camelCase, kebabCase, pascalCase } from "change-case";
|
|
@@ -26,7 +31,6 @@ import { reactorDriveDocumentModelModule } from "@powerhousedao/reactor-drive";
|
|
|
26
31
|
import { ReactorGroupV1 } from "@powerhousedao/reactor-group";
|
|
27
32
|
import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
|
|
28
33
|
import EventEmitter from "node:events";
|
|
29
|
-
import fs, { watchFile } from "node:fs";
|
|
30
34
|
import { PostgresAnalyticsStore } from "@powerhousedao/analytics-engine-pg";
|
|
31
35
|
import { AttachmentBuilder, AttachmentReferenceIndexBuilder, createRef, createS3AttachmentBackend, parseAttachmentStorageConfig, parseRef } from "@powerhousedao/reactor-attachments";
|
|
32
36
|
import { createAttachmentClient } from "@powerhousedao/reactor-attachments/client";
|
|
@@ -319,16 +323,1101 @@ async function createGatewayAdapter(type, logger) {
|
|
|
319
323
|
async function createHttpAdapter(type) {
|
|
320
324
|
switch (type) {
|
|
321
325
|
case "express": {
|
|
322
|
-
const { createExpressHttpAdapter } = await import("./adapter-http-express-
|
|
326
|
+
const { createExpressHttpAdapter } = await import("./adapter-http-express-54gdI9bu.mjs");
|
|
323
327
|
return createExpressHttpAdapter();
|
|
324
328
|
}
|
|
325
329
|
case "fastify": {
|
|
326
|
-
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-
|
|
330
|
+
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-DmccBVOe.mjs");
|
|
327
331
|
return createFastifyHttpAdapter();
|
|
328
332
|
}
|
|
329
333
|
}
|
|
330
334
|
}
|
|
331
335
|
//#endregion
|
|
336
|
+
//#region src/http/namespace.ts
|
|
337
|
+
/**
|
|
338
|
+
* A package's namespace is its npm name, used verbatim as one or two path
|
|
339
|
+
* segments. Nothing is slugged: dropping the scope, folding `.`/`_` to `-` or
|
|
340
|
+
* lower-casing would all map distinct package names onto one namespace, and a
|
|
341
|
+
* lossy mapping means two packages can silently claim the same URL space.
|
|
342
|
+
* Verbatim is injective, so there is no collision left to detect.
|
|
343
|
+
*/
|
|
344
|
+
/**
|
|
345
|
+
* npm's package-name grammar, per segment. Deliberately the same shape the HTTP
|
|
346
|
+
* package loader validates with, so a name that can be installed can be routed.
|
|
347
|
+
*/
|
|
348
|
+
const SEGMENT = /^[a-z0-9][-a-z0-9._]*$/i;
|
|
349
|
+
/** Segments that would read as path traversal rather than as a name. */
|
|
350
|
+
const TRAVERSAL = new Set([".", ".."]);
|
|
351
|
+
var InvalidNamespaceError = class extends Error {};
|
|
352
|
+
/**
|
|
353
|
+
* Turns a package name into the path segments it occupies.
|
|
354
|
+
* `@powerhousedao/workflow` → ["@powerhousedao", "workflow"]
|
|
355
|
+
* `document-model` → ["document-model"]
|
|
356
|
+
*/
|
|
357
|
+
function namespaceSegments(packageName) {
|
|
358
|
+
if (!packageName) throw new InvalidNamespaceError("Package name is empty");
|
|
359
|
+
const scoped = packageName.startsWith("@");
|
|
360
|
+
const parts = (scoped ? packageName.slice(1) : packageName).split("/");
|
|
361
|
+
if (parts.length !== (scoped ? 2 : 1)) throw new InvalidNamespaceError(`"${packageName}" is not a package name: expected ${scoped ? "@scope/name" : "name"}`);
|
|
362
|
+
for (const part of parts) if (TRAVERSAL.has(part) || !SEGMENT.test(part)) throw new InvalidNamespaceError(`"${packageName}" contains a segment that is not a valid npm name part: "${part}"`);
|
|
363
|
+
return scoped ? [`@${parts[0]}`, parts[1]] : parts;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* The path a package's routes hang off, relative to the package prefix.
|
|
367
|
+
* Scoped names occupy two segments — the addressing unpkg and jsdelivr use.
|
|
368
|
+
*/
|
|
369
|
+
function namespacePath(packageName) {
|
|
370
|
+
return namespaceSegments(packageName).join("/");
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The same path with the leading `@` percent-encoded. Both routers match the
|
|
374
|
+
* raw pathname, so a client that percent-encodes each segment would otherwise
|
|
375
|
+
* 404. Registering both spellings avoids decoding the pathname before matching,
|
|
376
|
+
* which would turn `%2f` into `/` and let a crafted URL cross segment
|
|
377
|
+
* boundaries. `%40` cannot introduce a boundary, which is what makes this safe.
|
|
378
|
+
*/
|
|
379
|
+
function encodedNamespacePath(packageName) {
|
|
380
|
+
const path = namespacePath(packageName);
|
|
381
|
+
return path.startsWith("@") ? `%40${path.slice(1)}` : void 0;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* A route path a package supplied. Must be relative and must not climb out of
|
|
385
|
+
* the scope.
|
|
386
|
+
*/
|
|
387
|
+
function assertRelativeRoutePath(path) {
|
|
388
|
+
if (path.startsWith("/")) throw new InvalidNamespaceError(`Route path "${path}" must be relative to the scope, not absolute`);
|
|
389
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(path)) throw new InvalidNamespaceError(`Route path "${path}" must be a path, not a URL`);
|
|
390
|
+
if (path.split("/").some((segment) => TRAVERSAL.has(segment))) throw new InvalidNamespaceError(`Route path "${path}" must not contain "." or ".." segments`);
|
|
391
|
+
return path.replace(/^\/+|\/+$/g, "");
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Resolves the npm name a package is routed under from the key the package
|
|
395
|
+
* manager holds it by. That key is the specifier as configured, which is not
|
|
396
|
+
* always a name:
|
|
397
|
+
* - `@scope/pkg` — already a name.
|
|
398
|
+
* - `@scope/pkg@1.3.9` — a registry spec; only the loader strips the tag.
|
|
399
|
+
* - `/Users/me/project` — the local project, which switchboard registers by
|
|
400
|
+
* `process.cwd()`. Its real name lives in its package.json.
|
|
401
|
+
*
|
|
402
|
+
* Deriving a URL segment straight from the key would give a developer's home
|
|
403
|
+
* directory a route namespace, so the path form is resolved rather than slugged.
|
|
404
|
+
*/
|
|
405
|
+
function resolvePackageName(key, readPackageJsonName = defaultReadName) {
|
|
406
|
+
const withoutVersion = stripVersionSpec(key);
|
|
407
|
+
if (isPackageName(withoutVersion)) return withoutVersion;
|
|
408
|
+
if (key.startsWith("/") || key.startsWith(".")) {
|
|
409
|
+
const name = readPackageJsonName(key);
|
|
410
|
+
if (name && isPackageName(name)) return name;
|
|
411
|
+
throw new InvalidNamespaceError(`Package at "${key}" has no usable name in its package.json, so it cannot host routes`);
|
|
412
|
+
}
|
|
413
|
+
throw new InvalidNamespaceError(`"${key}" is not a package name and not a path, so it cannot host routes`);
|
|
414
|
+
}
|
|
415
|
+
function isPackageName(value) {
|
|
416
|
+
try {
|
|
417
|
+
namespaceSegments(value);
|
|
418
|
+
return true;
|
|
419
|
+
} catch {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/** `@scope/pkg@1.3.9` → `@scope/pkg`; `pkg@1.0.0` → `pkg`. */
|
|
424
|
+
function stripVersionSpec(key) {
|
|
425
|
+
const at = key.lastIndexOf("@");
|
|
426
|
+
if (at <= 0) return key;
|
|
427
|
+
return key.slice(0, at);
|
|
428
|
+
}
|
|
429
|
+
function defaultReadName(dir) {
|
|
430
|
+
try {
|
|
431
|
+
const raw = readFileSync(join(dir, "package.json"), "utf8");
|
|
432
|
+
const parsed = JSON.parse(raw);
|
|
433
|
+
return typeof parsed.name === "string" ? parsed.name : void 0;
|
|
434
|
+
} catch {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* A mount path the host named for one of its own route groups.
|
|
440
|
+
*
|
|
441
|
+
* The host owns the whole URL space — it holds the adapter — so unlike a
|
|
442
|
+
* package it may name an absolute path. That is the difference between hosting
|
|
443
|
+
* and being hosted, and it is why this lives on the service rather than on a
|
|
444
|
+
* scope: a package is only ever handed a scope, and a scope has no way to
|
|
445
|
+
* express a path outside itself.
|
|
446
|
+
*
|
|
447
|
+
* What the host still may not name is anything that is not one stable literal
|
|
448
|
+
* prefix. A traversal segment, or a pattern segment, would make the group's own
|
|
449
|
+
* routes unpredictable relative to each other. Returns the normalized path,
|
|
450
|
+
* with a leading and no trailing slash.
|
|
451
|
+
*/
|
|
452
|
+
function assertHostMountPath(path) {
|
|
453
|
+
if (!path.startsWith("/")) throw new InvalidNamespaceError(`Host mount path "${path}" must be absolute, e.g. "/webhooks"`);
|
|
454
|
+
const segments = path.split("/").filter((segment) => segment.length > 0);
|
|
455
|
+
if (segments.length === 0) throw new InvalidNamespaceError(`Host mount path "${path}" is the whole URL space, which is the adapter rather than a scope of it`);
|
|
456
|
+
for (const segment of segments) {
|
|
457
|
+
if (TRAVERSAL.has(segment)) throw new InvalidNamespaceError(`Host mount path "${path}" must not contain "." or ".." segments`);
|
|
458
|
+
if (segment.startsWith(":") || segment.includes("*")) throw new InvalidNamespaceError(`Host mount path "${path}" must be a literal prefix, not a pattern: "${segment}"`);
|
|
459
|
+
}
|
|
460
|
+
return `/${segments.join("/")}`;
|
|
461
|
+
}
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region src/http/node-response.ts
|
|
464
|
+
/** Converts Node incoming headers to Fetch Headers. */
|
|
465
|
+
function fetchHeaders(req) {
|
|
466
|
+
const headers = new Headers();
|
|
467
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
468
|
+
if (value === void 0) continue;
|
|
469
|
+
headers.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
470
|
+
}
|
|
471
|
+
return headers;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Where the request reached the host. `trustProxy` says whether the
|
|
475
|
+
* `x-forwarded-*` headers may be believed: they are client-written, so with no
|
|
476
|
+
* proxy in front a caller could pick the origin a route advertises. The `host`
|
|
477
|
+
* header is always read, being the request's own target.
|
|
478
|
+
*/
|
|
479
|
+
function resolveTransport(req, trustProxy = false) {
|
|
480
|
+
const header = (name) => {
|
|
481
|
+
const value = req.headers[name];
|
|
482
|
+
return (Array.isArray(value) ? value[0] : value)?.split(",")[0]?.trim() || void 0;
|
|
483
|
+
};
|
|
484
|
+
const forwarded = (name) => trustProxy ? header(name) : void 0;
|
|
485
|
+
const encrypted = req.socket.encrypted === true;
|
|
486
|
+
const proto = forwarded("x-forwarded-proto") ?? (encrypted ? "https" : "http");
|
|
487
|
+
const host = forwarded("x-forwarded-host") ?? header("host") ?? "localhost";
|
|
488
|
+
const prefix = forwarded("x-forwarded-prefix") ?? "";
|
|
489
|
+
return {
|
|
490
|
+
proto,
|
|
491
|
+
host,
|
|
492
|
+
prefix,
|
|
493
|
+
baseUrl: `${proto}://${host}${prefix}`
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
var BodyTooLargeError = class extends Error {};
|
|
497
|
+
/**
|
|
498
|
+
* Buffers the request body, refusing anything past `maxBytes`.
|
|
499
|
+
*
|
|
500
|
+
* Written with events rather than `for await`, because an async iterator
|
|
501
|
+
* destroys the stream when the loop exits early — which tears down the socket
|
|
502
|
+
* before a 413 can be written to it. The stream is left paused instead, so the
|
|
503
|
+
* caller can answer and then close the connection deliberately.
|
|
504
|
+
*/
|
|
505
|
+
function readBody(req, maxBytes) {
|
|
506
|
+
return new Promise((resolve, reject) => {
|
|
507
|
+
const chunks = [];
|
|
508
|
+
let size = 0;
|
|
509
|
+
let settled = false;
|
|
510
|
+
const settle = (error) => {
|
|
511
|
+
if (settled) return;
|
|
512
|
+
settled = true;
|
|
513
|
+
req.off("data", onData);
|
|
514
|
+
req.off("end", onEnd);
|
|
515
|
+
req.off("error", onError);
|
|
516
|
+
req.pause();
|
|
517
|
+
if (error) reject(error);
|
|
518
|
+
else resolve(Buffer.concat(chunks));
|
|
519
|
+
};
|
|
520
|
+
const onData = (chunk) => {
|
|
521
|
+
size += chunk.length;
|
|
522
|
+
if (size > maxBytes) {
|
|
523
|
+
settle(new BodyTooLargeError(`Body exceeds ${maxBytes} bytes`));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
chunks.push(chunk);
|
|
527
|
+
};
|
|
528
|
+
const onEnd = () => settle();
|
|
529
|
+
const onError = (error) => settle(error);
|
|
530
|
+
req.on("data", onData);
|
|
531
|
+
req.once("end", onEnd);
|
|
532
|
+
req.once("error", onError);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Copies a Fetch Response onto a Node response, streaming the body. A HEAD
|
|
537
|
+
* response carries headers only, however the handler filled the body.
|
|
538
|
+
*/
|
|
539
|
+
async function writeResponse(res, response, method) {
|
|
540
|
+
if (res.writableEnded || res.headersSent) return;
|
|
541
|
+
res.statusCode = response.status;
|
|
542
|
+
response.headers.forEach((value, key) => {
|
|
543
|
+
res.setHeader(key, value);
|
|
544
|
+
});
|
|
545
|
+
if (!response.body || method.toUpperCase() === "HEAD") {
|
|
546
|
+
res.end();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const source = Readable.fromWeb(response.body);
|
|
550
|
+
res.on("close", () => source.destroy());
|
|
551
|
+
await pipeline(source, res);
|
|
552
|
+
}
|
|
553
|
+
/** Answers with a JSON error, unless the response is already underway. */
|
|
554
|
+
function writeError(res, status, message) {
|
|
555
|
+
if (res.writableEnded || res.headersSent) {
|
|
556
|
+
res.destroy();
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const body = JSON.stringify({ error: message });
|
|
560
|
+
res.statusCode = status;
|
|
561
|
+
res.setHeader("content-type", "application/json");
|
|
562
|
+
res.setHeader("content-length", Buffer.byteLength(body));
|
|
563
|
+
res.end(body);
|
|
564
|
+
}
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/http/route-service.ts
|
|
567
|
+
const logger$2 = childLogger(["reactor-api", "http-routes"]);
|
|
568
|
+
/** The segment package routes live under, inside the host's base path. */
|
|
569
|
+
const PACKAGE_ROUTE_SEGMENT = "api";
|
|
570
|
+
/** The namespace core's own subgraphs and services register under. */
|
|
571
|
+
const CORE_PACKAGE_NAME = "@powerhousedao/reactor-api";
|
|
572
|
+
/** Generous enough for any request a route should be buffering at all. */
|
|
573
|
+
const DEFAULT_MAX_BODY_BYTES$1 = 1048576;
|
|
574
|
+
const ANONYMOUS = {
|
|
575
|
+
user: void 0,
|
|
576
|
+
authEnabled: false
|
|
577
|
+
};
|
|
578
|
+
/** Owns the URL space packages serve from, handing each a scope bound to its own namespace. The prefix nests inside
|
|
579
|
+
* the host's base path (`<basePath>/api`), so core collisions are prevented by URL shape, not a reserved-name list. */
|
|
580
|
+
var HttpRouteService = class {
|
|
581
|
+
#adapter;
|
|
582
|
+
#authService;
|
|
583
|
+
#prefix;
|
|
584
|
+
#publicUrl;
|
|
585
|
+
#trustProxy;
|
|
586
|
+
#scopes = /* @__PURE__ */ new Map();
|
|
587
|
+
#webhooks;
|
|
588
|
+
constructor({ httpAdapter, basePath = "/", authService, webhooks, publicUrl = "", trustProxy = false }) {
|
|
589
|
+
this.#adapter = httpAdapter;
|
|
590
|
+
this.#authService = authService;
|
|
591
|
+
this.#webhooks = webhooks;
|
|
592
|
+
this.#publicUrl = publicUrl.replace(/\/+$/, "");
|
|
593
|
+
this.#trustProxy = trustProxy;
|
|
594
|
+
this.#prefix = posix.join("/", basePath, "api");
|
|
595
|
+
}
|
|
596
|
+
/** The absolute path packages are mounted under. */
|
|
597
|
+
get prefix() {
|
|
598
|
+
return this.#prefix;
|
|
599
|
+
}
|
|
600
|
+
/** The scope for a package; idempotent per name, so a reload cannot accumulate namespaces. Throws when two packages
|
|
601
|
+
* resolve to one namespace — with verbatim names that means one package configured twice, a genuine ambiguity. */
|
|
602
|
+
scopeFor(packageKey) {
|
|
603
|
+
const packageName = resolvePackageName(packageKey);
|
|
604
|
+
const namespace = namespacePath(packageName);
|
|
605
|
+
const encoded = encodedNamespacePath(packageName);
|
|
606
|
+
return this.#scope({
|
|
607
|
+
owner: packageName,
|
|
608
|
+
base: posix.join(this.#prefix, namespace),
|
|
609
|
+
encodedBase: encoded ? posix.join(this.#prefix, encoded) : void 0,
|
|
610
|
+
claim: `the route namespace "${namespace}"`,
|
|
611
|
+
webhooks: this.#webhooks?.scopeFor(packageName),
|
|
612
|
+
onDispose: () => this.#webhooks?.disposeScope(packageName)
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
/** A scope at a host-named path; on the service, not `IHttpScope`, so a package can never name one. Namespacing is package
|
|
616
|
+
* policy: the host owns its URL space and third parties hold its URLs, so the path is verbatim and outside the package prefix. */
|
|
617
|
+
hostScope(name, mountPath) {
|
|
618
|
+
const base = assertHostMountPath(mountPath);
|
|
619
|
+
if (base === this.#prefix || base.startsWith(`${this.#prefix}/`)) throw new InvalidNamespaceError(`Host scope "${name}" cannot mount at "${base}": that is the package route prefix, which packages own`);
|
|
620
|
+
return this.#scope({
|
|
621
|
+
owner: name,
|
|
622
|
+
base,
|
|
623
|
+
encodedBase: void 0,
|
|
624
|
+
claim: `the mount path "${base}"`,
|
|
625
|
+
webhooks: void 0,
|
|
626
|
+
onDispose: () => void 0
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
/** Idempotent per base path, so a package reload cannot accumulate namespaces. Two owners wanting one path is an error —
|
|
630
|
+
* with verbatim names that needs one package configured twice at different versions, a genuine ambiguity, not a clash. */
|
|
631
|
+
#scope(options) {
|
|
632
|
+
const { owner, base } = options;
|
|
633
|
+
const existing = this.#scopes.get(base);
|
|
634
|
+
if (existing) {
|
|
635
|
+
if (existing.owner !== owner) throw new Error(`"${owner}" wants ${options.claim}, which "${existing.owner}" already holds`);
|
|
636
|
+
return existing;
|
|
637
|
+
}
|
|
638
|
+
const scope = new HttpScope({
|
|
639
|
+
owner,
|
|
640
|
+
base,
|
|
641
|
+
encodedBase: options.encodedBase,
|
|
642
|
+
publicUrl: this.#publicUrl,
|
|
643
|
+
trustProxy: this.#trustProxy,
|
|
644
|
+
adapter: this.#adapter,
|
|
645
|
+
authService: this.#authService,
|
|
646
|
+
webhooks: options.webhooks,
|
|
647
|
+
onDispose: () => {
|
|
648
|
+
this.#scopes.delete(base);
|
|
649
|
+
options.onDispose();
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
this.#scopes.set(base, scope);
|
|
653
|
+
return scope;
|
|
654
|
+
}
|
|
655
|
+
/** Like {@link scopeFor}, but never throws: a key resolving to no usable npm name gets a scope that refuses
|
|
656
|
+
* registrations, so one unroutable package cannot stop the host booting — it fails only if it serves a route. */
|
|
657
|
+
scopeForOrNull(packageKey) {
|
|
658
|
+
try {
|
|
659
|
+
return this.scopeFor(packageKey);
|
|
660
|
+
} catch (error) {
|
|
661
|
+
if (!(error instanceof InvalidNamespaceError)) throw error;
|
|
662
|
+
logger$2.warn("Package \"@package\" cannot host HTTP routes: @reason", packageKey, error.message);
|
|
663
|
+
return new UnroutableScope(packageKey, error.message);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/** Releases a package's routes, e.g. when it is replaced or removed. */
|
|
667
|
+
disposeScope(packageKey) {
|
|
668
|
+
try {
|
|
669
|
+
const namespace = namespacePath(resolvePackageName(packageKey));
|
|
670
|
+
this.#scopes.get(posix.join(this.#prefix, namespace))?.dispose();
|
|
671
|
+
} catch (error) {
|
|
672
|
+
if (!(error instanceof InvalidNamespaceError)) throw error;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
disposeAll() {
|
|
676
|
+
for (const scope of [...this.#scopes.values()]) scope.dispose();
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
var HttpScope = class {
|
|
680
|
+
owner;
|
|
681
|
+
#base;
|
|
682
|
+
#publicUrl;
|
|
683
|
+
#trustProxy;
|
|
684
|
+
#encodedBase;
|
|
685
|
+
#adapter;
|
|
686
|
+
#authService;
|
|
687
|
+
#onDispose;
|
|
688
|
+
#registrations = /* @__PURE__ */ new Map();
|
|
689
|
+
#webhooks;
|
|
690
|
+
constructor(options) {
|
|
691
|
+
this.owner = options.owner;
|
|
692
|
+
this.#base = options.base;
|
|
693
|
+
this.#publicUrl = options.publicUrl;
|
|
694
|
+
this.#trustProxy = options.trustProxy;
|
|
695
|
+
this.#encodedBase = options.encodedBase;
|
|
696
|
+
this.#adapter = options.adapter;
|
|
697
|
+
this.#authService = options.authService;
|
|
698
|
+
this.#onDispose = options.onDispose;
|
|
699
|
+
this.#webhooks = options.webhooks ?? unavailableWebhooks(options.owner);
|
|
700
|
+
}
|
|
701
|
+
get baseUrl() {
|
|
702
|
+
return `${this.#publicUrl}${this.#base}`;
|
|
703
|
+
}
|
|
704
|
+
get webhooks() {
|
|
705
|
+
return this.#webhooks;
|
|
706
|
+
}
|
|
707
|
+
get(path, a, b) {
|
|
708
|
+
return this.#shorthand("GET", path, a, b);
|
|
709
|
+
}
|
|
710
|
+
post(path, a, b) {
|
|
711
|
+
return this.#shorthand("POST", path, a, b);
|
|
712
|
+
}
|
|
713
|
+
put(path, a, b) {
|
|
714
|
+
return this.#shorthand("PUT", path, a, b);
|
|
715
|
+
}
|
|
716
|
+
patch(path, a, b) {
|
|
717
|
+
return this.#shorthand("PATCH", path, a, b);
|
|
718
|
+
}
|
|
719
|
+
delete(path, a, b) {
|
|
720
|
+
return this.#shorthand("DELETE", path, a, b);
|
|
721
|
+
}
|
|
722
|
+
head(path, a, b) {
|
|
723
|
+
return this.#shorthand("HEAD", path, a, b);
|
|
724
|
+
}
|
|
725
|
+
#shorthand(method, path, a, b) {
|
|
726
|
+
const options = typeof a === "function" ? {} : a;
|
|
727
|
+
const handler = typeof a === "function" ? a : b;
|
|
728
|
+
if (!handler) throw new Error(`Route ${method} ${path} was registered with no handler`);
|
|
729
|
+
return this.route({
|
|
730
|
+
...options,
|
|
731
|
+
method,
|
|
732
|
+
path,
|
|
733
|
+
handler
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
route(spec) {
|
|
737
|
+
const body = spec.body ?? "parsed";
|
|
738
|
+
return this.#register(spec, (req, res, ctx) => this.#serve(spec, body, req, res, ctx));
|
|
739
|
+
}
|
|
740
|
+
nodeRoute(spec) {
|
|
741
|
+
return this.#register(spec, (req, res, ctx) => spec.handler(req, res, ctx));
|
|
742
|
+
}
|
|
743
|
+
dispose() {
|
|
744
|
+
for (const registration of this.#registrations.values()) for (const handle of registration.handles) handle.dispose();
|
|
745
|
+
this.#registrations.clear();
|
|
746
|
+
this.#onDispose();
|
|
747
|
+
}
|
|
748
|
+
/** Mounts one route under this scope's namespace, in both the verbatim and the
|
|
749
|
+
* percent-encoded spelling of a scoped name. */
|
|
750
|
+
#register(spec, run) {
|
|
751
|
+
const relative = assertRelativeRoutePath(spec.path);
|
|
752
|
+
const methods = Array.isArray(spec.method) ? spec.method : [spec.method];
|
|
753
|
+
const routePath = relative ? posix.join(this.#base, relative) : this.#base;
|
|
754
|
+
const key = `${methods.join(",")} ${routePath}`;
|
|
755
|
+
if (this.#registrations.has(key)) throw new Error(`Route ${key} is already registered by "${this.owner}"`);
|
|
756
|
+
const handles = [];
|
|
757
|
+
const paths = [routePath];
|
|
758
|
+
if (this.#encodedBase) paths.push(relative ? posix.join(this.#encodedBase, relative) : this.#encodedBase);
|
|
759
|
+
for (const path of paths) for (const method of methods) handles.push(this.#adapter.mountNodeRoute(method, path, (req, res) => {
|
|
760
|
+
this.#guard(spec, req, res, run);
|
|
761
|
+
}, {
|
|
762
|
+
rawBody: true,
|
|
763
|
+
prefix: spec.prefix
|
|
764
|
+
}));
|
|
765
|
+
this.#registrations.set(key, {
|
|
766
|
+
path: routePath,
|
|
767
|
+
handles
|
|
768
|
+
});
|
|
769
|
+
logger$2.debug("Registered @route for @package", key, this.owner);
|
|
770
|
+
return {
|
|
771
|
+
url: `${this.#publicUrl}${routePath}`,
|
|
772
|
+
dispose: () => {
|
|
773
|
+
const registration = this.#registrations.get(key);
|
|
774
|
+
if (!registration) return;
|
|
775
|
+
for (const handle of registration.handles) handle.dispose();
|
|
776
|
+
this.#registrations.delete(key);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
/** Authenticate, then run. */
|
|
781
|
+
async #guard(spec, req, res, run) {
|
|
782
|
+
try {
|
|
783
|
+
const actor = await this.#authenticate(spec.auth ?? "renown", req, res);
|
|
784
|
+
if (actor === REJECTED) return;
|
|
785
|
+
await run(req, res, {
|
|
786
|
+
params: routeParams(req),
|
|
787
|
+
user: actor?.user,
|
|
788
|
+
authEnabled: actor?.authEnabled ?? false,
|
|
789
|
+
transport: resolveTransport(req, this.#trustProxy)
|
|
790
|
+
});
|
|
791
|
+
} catch (error) {
|
|
792
|
+
logger$2.error("Route handler failed for @package", this.owner, error);
|
|
793
|
+
writeError(res, 500, "Internal server error");
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
/** Resolves the caller, answering the request itself when the route refuses it;
|
|
797
|
+
* returns REJECTED in that case so the caller stops. */
|
|
798
|
+
async #authenticate(auth, req, res) {
|
|
799
|
+
if (auth === "public") return void 0;
|
|
800
|
+
if (typeof auth === "function") {
|
|
801
|
+
const result = await auth(req);
|
|
802
|
+
if (!result.authorized) {
|
|
803
|
+
if ("response" in result) await writeResponse(res, result.response, req.method ?? "GET");
|
|
804
|
+
else writeError(res, result.status, result.message);
|
|
805
|
+
return REJECTED;
|
|
806
|
+
}
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
if (!this.#authService) return ANONYMOUS;
|
|
810
|
+
const result = await this.#authService.verifyBearer(req.headers.authorization);
|
|
811
|
+
if (result instanceof Response) {
|
|
812
|
+
await writeResponse(res, result, req.method ?? "GET");
|
|
813
|
+
return REJECTED;
|
|
814
|
+
}
|
|
815
|
+
if (result.auth_enabled && !result.user && auth === "renown") {
|
|
816
|
+
writeError(res, 401, "Authentication required");
|
|
817
|
+
return REJECTED;
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
user: result.user,
|
|
821
|
+
authEnabled: result.auth_enabled
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
/** Builds the Fetch Request from the raw stream and runs the handler. */
|
|
825
|
+
async #serve(spec, body, req, res, ctx) {
|
|
826
|
+
const method = req.method ?? "GET";
|
|
827
|
+
const url = `${ctx.transport.baseUrl}${req.url ?? "/"}`;
|
|
828
|
+
const controller = new AbortController();
|
|
829
|
+
req.once("close", () => controller.abort());
|
|
830
|
+
let raw;
|
|
831
|
+
let init = {
|
|
832
|
+
method,
|
|
833
|
+
headers: fetchHeaders(req),
|
|
834
|
+
signal: controller.signal
|
|
835
|
+
};
|
|
836
|
+
if (method !== "GET" && method !== "HEAD" && body !== "none") if (body === "stream") init = {
|
|
837
|
+
...init,
|
|
838
|
+
body: Readable.toWeb(req),
|
|
839
|
+
duplex: "half"
|
|
840
|
+
};
|
|
841
|
+
else {
|
|
842
|
+
try {
|
|
843
|
+
raw = await readBody(req, spec.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES$1);
|
|
844
|
+
} catch (error) {
|
|
845
|
+
if (error instanceof BodyTooLargeError) {
|
|
846
|
+
res.setHeader("connection", "close");
|
|
847
|
+
res.once("finish", () => req.destroy());
|
|
848
|
+
writeError(res, 413, "Payload too large");
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
throw error;
|
|
852
|
+
}
|
|
853
|
+
if (raw.length) init = {
|
|
854
|
+
...init,
|
|
855
|
+
body: new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength)
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
await writeResponse(res, await spec.handler(new Request(url, init), {
|
|
859
|
+
...ctx,
|
|
860
|
+
rawBody: body === "raw" ? raw ?? Buffer.alloc(0) : void 0,
|
|
861
|
+
signal: controller.signal
|
|
862
|
+
}), method);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
const REJECTED = Symbol("rejected");
|
|
866
|
+
function routeParams(req) {
|
|
867
|
+
const params = req.params;
|
|
868
|
+
if (!params || typeof params !== "object") return {};
|
|
869
|
+
const out = {};
|
|
870
|
+
for (const [key, value] of Object.entries(params)) if (typeof value === "string") out[key] = value;
|
|
871
|
+
else if (Array.isArray(value)) out[key] = value.join("/");
|
|
872
|
+
return out;
|
|
873
|
+
}
|
|
874
|
+
/** Stands in for a package that cannot be routed — its key resolves to no npm name. Registering a route throws with
|
|
875
|
+
* the reason; nothing else does, so the package still loads and its non-HTTP behaviour is unaffected. */
|
|
876
|
+
var UnroutableScope = class {
|
|
877
|
+
owner;
|
|
878
|
+
baseUrl = "";
|
|
879
|
+
#reason;
|
|
880
|
+
constructor(packageKey, reason) {
|
|
881
|
+
this.owner = packageKey;
|
|
882
|
+
this.#reason = reason;
|
|
883
|
+
}
|
|
884
|
+
#refuse() {
|
|
885
|
+
throw new Error(`Package "${this.owner}" cannot host HTTP routes: ${this.#reason}`);
|
|
886
|
+
}
|
|
887
|
+
get() {
|
|
888
|
+
this.#refuse();
|
|
889
|
+
}
|
|
890
|
+
post() {
|
|
891
|
+
this.#refuse();
|
|
892
|
+
}
|
|
893
|
+
put() {
|
|
894
|
+
this.#refuse();
|
|
895
|
+
}
|
|
896
|
+
patch() {
|
|
897
|
+
this.#refuse();
|
|
898
|
+
}
|
|
899
|
+
delete() {
|
|
900
|
+
this.#refuse();
|
|
901
|
+
}
|
|
902
|
+
head() {
|
|
903
|
+
this.#refuse();
|
|
904
|
+
}
|
|
905
|
+
route() {
|
|
906
|
+
this.#refuse();
|
|
907
|
+
}
|
|
908
|
+
nodeRoute() {
|
|
909
|
+
this.#refuse();
|
|
910
|
+
}
|
|
911
|
+
get webhooks() {
|
|
912
|
+
return unavailableWebhooks(this.owner);
|
|
913
|
+
}
|
|
914
|
+
dispose() {}
|
|
915
|
+
};
|
|
916
|
+
/** Stands in when the host has no webhook store configured. */
|
|
917
|
+
function unavailableWebhooks(packageName) {
|
|
918
|
+
return {
|
|
919
|
+
hasPublicOrigin: false,
|
|
920
|
+
register: () => Promise.reject(/* @__PURE__ */ new Error(`Webhooks are not available on this host, so "${packageName}" cannot register one`))
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
/** Header each scheme reads when the caller does not name one: the name the format is
|
|
924
|
+
* most often carried in, not a property of it, so `WebhookVerification.header` overrides any of them. */
|
|
925
|
+
const DEFAULT_SCHEME_HEADER = {
|
|
926
|
+
none: "",
|
|
927
|
+
token: "x-webhook-token",
|
|
928
|
+
hmac: "x-signature",
|
|
929
|
+
"hmac-prefixed": "x-hub-signature-256",
|
|
930
|
+
"hmac-timestamped": "stripe-signature"
|
|
931
|
+
};
|
|
932
|
+
const OK = { ok: true };
|
|
933
|
+
/** Constant-time over equal-length inputs; a length mismatch is already a rejection, so
|
|
934
|
+
* the early return leaks nothing a caller could not measure. */
|
|
935
|
+
function secureEquals(a, b) {
|
|
936
|
+
const left = Buffer.from(a, "utf8");
|
|
937
|
+
const right = Buffer.from(b, "utf8");
|
|
938
|
+
if (left.length !== right.length) return false;
|
|
939
|
+
return timingSafeEqual(left, right);
|
|
940
|
+
}
|
|
941
|
+
function hmacDigest(secret, payload, verification) {
|
|
942
|
+
return createHmac(verification.algorithm ?? "sha256", secret).update(payload).digest(verification.encoding ?? "hex");
|
|
943
|
+
}
|
|
944
|
+
/** Hex folds case, so an uppercased digest still verifies. Base64 must not: two distinct
|
|
945
|
+
* digests can differ only in case, so folding there would accept a signature that is not the one computed. */
|
|
946
|
+
function digestEquals(presented, expected, verification) {
|
|
947
|
+
const candidate = presented.trim();
|
|
948
|
+
if ((verification.encoding ?? "hex") === "base64") return secureEquals(candidate, expected);
|
|
949
|
+
return secureEquals(candidate.toLowerCase(), expected.toLowerCase());
|
|
950
|
+
}
|
|
951
|
+
/** The label `hmac-prefixed` expects before the digest. */
|
|
952
|
+
function signaturePrefix(verification) {
|
|
953
|
+
return verification.prefix ?? `${verification.algorithm ?? "sha256"}=`;
|
|
954
|
+
}
|
|
955
|
+
/** `t=<unix>,v1=<hex>[,v1=<hex>…]`. Several `v1` values appear while a secret is being
|
|
956
|
+
* rotated, and any one of them matching is a pass. */
|
|
957
|
+
function parseTimestampedHeader(value) {
|
|
958
|
+
const signatures = [];
|
|
959
|
+
let timestamp;
|
|
960
|
+
for (const part of value.split(",")) {
|
|
961
|
+
const separator = part.indexOf("=");
|
|
962
|
+
if (separator < 0) continue;
|
|
963
|
+
const key = part.slice(0, separator).trim();
|
|
964
|
+
const item = part.slice(separator + 1).trim();
|
|
965
|
+
if (key === "t") timestamp = Number(item);
|
|
966
|
+
else if (key === "v1") signatures.push(item);
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
timestamp,
|
|
970
|
+
signatures
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function schemeHeader(verification) {
|
|
974
|
+
return (verification.header ?? DEFAULT_SCHEME_HEADER[verification.scheme]).toLowerCase();
|
|
975
|
+
}
|
|
976
|
+
/** Verifies a delivery against the configured scheme; `raw` must be the exact bytes
|
|
977
|
+
* received. The reason is for the log only: callers answer a fixed 401, so a probing sender learns nothing from the body. */
|
|
978
|
+
function verifyWebhook(options) {
|
|
979
|
+
const { verification, headers, raw } = options;
|
|
980
|
+
if (verification.scheme === "none") return OK;
|
|
981
|
+
if (!verification.secret) return {
|
|
982
|
+
ok: false,
|
|
983
|
+
reason: "signing secret missing"
|
|
984
|
+
};
|
|
985
|
+
const header = schemeHeader(verification);
|
|
986
|
+
const presented = headers[header];
|
|
987
|
+
if (!presented) return {
|
|
988
|
+
ok: false,
|
|
989
|
+
reason: `header "${header}" absent`
|
|
990
|
+
};
|
|
991
|
+
const secret = verification.secret;
|
|
992
|
+
const tolerance = verification.toleranceSeconds ?? 300;
|
|
993
|
+
switch (verification.scheme) {
|
|
994
|
+
case "token": return secureEquals(presented, secret) ? OK : {
|
|
995
|
+
ok: false,
|
|
996
|
+
reason: "token mismatch"
|
|
997
|
+
};
|
|
998
|
+
case "hmac": return digestEquals(presented, hmacDigest(secret, raw, verification), verification) ? OK : {
|
|
999
|
+
ok: false,
|
|
1000
|
+
reason: "hmac mismatch"
|
|
1001
|
+
};
|
|
1002
|
+
case "hmac-prefixed": return digestEquals(presented, `${signaturePrefix(verification)}${hmacDigest(secret, raw, verification)}`, verification) ? OK : {
|
|
1003
|
+
ok: false,
|
|
1004
|
+
reason: "hmac mismatch"
|
|
1005
|
+
};
|
|
1006
|
+
case "hmac-timestamped": {
|
|
1007
|
+
const { timestamp, signatures } = parseTimestampedHeader(presented);
|
|
1008
|
+
if (timestamp === void 0 || !Number.isFinite(timestamp)) return {
|
|
1009
|
+
ok: false,
|
|
1010
|
+
reason: "no timestamp in the signature header"
|
|
1011
|
+
};
|
|
1012
|
+
if (signatures.length === 0) return {
|
|
1013
|
+
ok: false,
|
|
1014
|
+
reason: "no v1 signature in the header"
|
|
1015
|
+
};
|
|
1016
|
+
const nowMs = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
1017
|
+
if (Math.abs(nowMs / 1e3 - timestamp) > tolerance) return {
|
|
1018
|
+
ok: false,
|
|
1019
|
+
reason: "timestamp outside the replay window"
|
|
1020
|
+
};
|
|
1021
|
+
const expected = hmacDigest(secret, Buffer.concat([Buffer.from(`${timestamp}.`, "utf8"), raw]), verification);
|
|
1022
|
+
let matched = false;
|
|
1023
|
+
for (const signature of signatures) if (digestEquals(signature, expected, verification)) matched = true;
|
|
1024
|
+
return matched ? OK : {
|
|
1025
|
+
ok: false,
|
|
1026
|
+
reason: "hmac mismatch"
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
/** Headers that would put a credential in a log or run journal. The configured signature
|
|
1032
|
+
* header joins them: it is an HMAC of the body under a live secret. */
|
|
1033
|
+
const REDACTED_HEADERS = new Set([
|
|
1034
|
+
"authorization",
|
|
1035
|
+
"proxy-authorization",
|
|
1036
|
+
"cookie",
|
|
1037
|
+
"set-cookie",
|
|
1038
|
+
"x-api-key",
|
|
1039
|
+
"x-webhook-token",
|
|
1040
|
+
"x-signature",
|
|
1041
|
+
"x-hub-signature",
|
|
1042
|
+
"x-hub-signature-256",
|
|
1043
|
+
"stripe-signature"
|
|
1044
|
+
]);
|
|
1045
|
+
const REDACTED = "[redacted]";
|
|
1046
|
+
function redactHeaders(headers, extra) {
|
|
1047
|
+
const redacted = {};
|
|
1048
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
1049
|
+
const key = name.toLowerCase();
|
|
1050
|
+
redacted[key] = REDACTED_HEADERS.has(key) || extra && key === extra.toLowerCase() ? REDACTED : value;
|
|
1051
|
+
}
|
|
1052
|
+
return redacted;
|
|
1053
|
+
}
|
|
1054
|
+
/** JSON and form bodies become objects; anything else stays decoded text, so a verified
|
|
1055
|
+
* XML or CSV payload is still reachable downstream. */
|
|
1056
|
+
function parseWebhookBody(raw, contentType) {
|
|
1057
|
+
if (raw.length === 0) return void 0;
|
|
1058
|
+
const type = (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
1059
|
+
const text = raw.toString("utf8");
|
|
1060
|
+
if (type === "application/json" || type.endsWith("+json")) try {
|
|
1061
|
+
return JSON.parse(text);
|
|
1062
|
+
} catch {
|
|
1063
|
+
return text;
|
|
1064
|
+
}
|
|
1065
|
+
if (type === "application/x-www-form-urlencoded") return Object.fromEntries(new URLSearchParams(text));
|
|
1066
|
+
return text;
|
|
1067
|
+
}
|
|
1068
|
+
//#endregion
|
|
1069
|
+
//#region src/http/webhook-service.ts
|
|
1070
|
+
const logger$1 = childLogger(["reactor-api", "webhooks"]);
|
|
1071
|
+
/** The flat, token-addressed path family. */
|
|
1072
|
+
const WEBHOOK_SEGMENT = "webhooks";
|
|
1073
|
+
const DEFAULT_MAX_BODY_BYTES = 1048576;
|
|
1074
|
+
const DEFAULT_DEDUPE_TTL_SECONDS = 300;
|
|
1075
|
+
/** One host route for `/webhooks/:token`, dispatching by token. The flat path leaks
|
|
1076
|
+
* neither package nor document; the namespace lives in the token record, which is also what stops one package's token reaching another's handler. */
|
|
1077
|
+
var WebhookService = class {
|
|
1078
|
+
#store;
|
|
1079
|
+
#path;
|
|
1080
|
+
#publicUrl;
|
|
1081
|
+
#scope;
|
|
1082
|
+
#registrations = /* @__PURE__ */ new Map();
|
|
1083
|
+
#routes = [];
|
|
1084
|
+
constructor({ store, basePath = "/", publicUrl }) {
|
|
1085
|
+
this.#store = store;
|
|
1086
|
+
this.#path = posix.join("/", basePath, WEBHOOK_SEGMENT);
|
|
1087
|
+
this.#publicUrl = publicUrl?.replace(/\/+$/, "");
|
|
1088
|
+
}
|
|
1089
|
+
/** The scope the endpoint family serves from, handed over once the route service exists;
|
|
1090
|
+
* separate from the constructor only because that service takes this one as an option. */
|
|
1091
|
+
attach(scope) {
|
|
1092
|
+
this.#scope = scope;
|
|
1093
|
+
}
|
|
1094
|
+
async init() {
|
|
1095
|
+
await this.#store.init();
|
|
1096
|
+
if (this.#routes.length) return;
|
|
1097
|
+
if (!this.#scope) throw new Error("The webhook service has no HTTP scope; the host must call attach() before a package registers");
|
|
1098
|
+
this.#routes = [this.#scope.nodeRoute({
|
|
1099
|
+
method: [
|
|
1100
|
+
"DELETE",
|
|
1101
|
+
"GET",
|
|
1102
|
+
"HEAD",
|
|
1103
|
+
"PATCH",
|
|
1104
|
+
"POST",
|
|
1105
|
+
"PUT"
|
|
1106
|
+
],
|
|
1107
|
+
path: ":token",
|
|
1108
|
+
auth: "public",
|
|
1109
|
+
handler: (req, res, ctx) => void this.#deliver(req, res, ctx.params)
|
|
1110
|
+
})];
|
|
1111
|
+
}
|
|
1112
|
+
dispose() {
|
|
1113
|
+
for (const handle of this.#routes) handle.dispose();
|
|
1114
|
+
this.#routes = [];
|
|
1115
|
+
this.#registrations.clear();
|
|
1116
|
+
}
|
|
1117
|
+
/** The facade a package's HTTP scope exposes as `scope.webhooks`. */
|
|
1118
|
+
scopeFor(namespace) {
|
|
1119
|
+
return {
|
|
1120
|
+
hasPublicOrigin: this.hasPublicOrigin,
|
|
1121
|
+
register: async (spec) => {
|
|
1122
|
+
await this.init();
|
|
1123
|
+
const key = `${namespace}|${spec.name}`;
|
|
1124
|
+
this.#registrations.set(key, {
|
|
1125
|
+
namespace,
|
|
1126
|
+
spec
|
|
1127
|
+
});
|
|
1128
|
+
return {
|
|
1129
|
+
endpointFor: async (ownerKey) => {
|
|
1130
|
+
const row = await this.#store.ensure(namespace, spec.name, ownerKey);
|
|
1131
|
+
return {
|
|
1132
|
+
token: row.token,
|
|
1133
|
+
url: this.urlFor(row.token),
|
|
1134
|
+
createdAt: row.createdAt
|
|
1135
|
+
};
|
|
1136
|
+
},
|
|
1137
|
+
revoke: (ownerKey) => this.#store.revoke(namespace, spec.name, ownerKey),
|
|
1138
|
+
list: async () => (await this.#store.list(namespace, spec.name)).map((row) => ({
|
|
1139
|
+
key: row.ownerKey,
|
|
1140
|
+
token: row.token,
|
|
1141
|
+
url: this.urlFor(row.token),
|
|
1142
|
+
createdAt: row.createdAt
|
|
1143
|
+
}))
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
/** Releases every registration a package made. */
|
|
1149
|
+
disposeScope(namespace) {
|
|
1150
|
+
for (const [key, registration] of this.#registrations) if (registration.namespace === namespace) this.#registrations.delete(key);
|
|
1151
|
+
}
|
|
1152
|
+
/** The URL to hand a provider. Absolute whenever the host knows its own origin: a
|
|
1153
|
+
* relative path is useless to the third party that has to call it. */
|
|
1154
|
+
urlFor(token) {
|
|
1155
|
+
const path = posix.join(this.#path, token);
|
|
1156
|
+
return this.#publicUrl ? `${this.#publicUrl}${path}` : path;
|
|
1157
|
+
}
|
|
1158
|
+
/** Whether `urlFor` can produce something a provider could call. */
|
|
1159
|
+
get hasPublicOrigin() {
|
|
1160
|
+
return this.#publicUrl !== void 0;
|
|
1161
|
+
}
|
|
1162
|
+
async #deliver(req, res, params) {
|
|
1163
|
+
const token = params.token;
|
|
1164
|
+
if (!token) {
|
|
1165
|
+
writeError(res, 404, "Unknown endpoint");
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
try {
|
|
1169
|
+
const row = await this.#store.find(token);
|
|
1170
|
+
if (!row) {
|
|
1171
|
+
writeError(res, 404, "Unknown endpoint");
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const registrationKey = `${row.namespace}|${row.endpoint}`;
|
|
1175
|
+
const registration = this.#registrations.get(registrationKey);
|
|
1176
|
+
if (!registration) {
|
|
1177
|
+
writeError(res, 503, "Endpoint temporarily unavailable");
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const { spec } = registration;
|
|
1181
|
+
const method = (req.method ?? "POST").toUpperCase();
|
|
1182
|
+
const perEndpoint = spec.policyFor ? await spec.policyFor(row.ownerKey) : {};
|
|
1183
|
+
if (!perEndpoint) {
|
|
1184
|
+
writeError(res, 404, "Unknown endpoint");
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
const policy = {
|
|
1188
|
+
...spec.defaults,
|
|
1189
|
+
...perEndpoint
|
|
1190
|
+
};
|
|
1191
|
+
if (policy.methods && !policy.methods.includes(method)) {
|
|
1192
|
+
writeError(res, 405, "Method not allowed");
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
let raw;
|
|
1196
|
+
try {
|
|
1197
|
+
raw = await readBody(req, policy.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
if (error instanceof BodyTooLargeError) {
|
|
1200
|
+
res.setHeader("connection", "close");
|
|
1201
|
+
res.once("finish", () => req.destroy());
|
|
1202
|
+
writeError(res, 413, "Payload too large");
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
throw error;
|
|
1206
|
+
}
|
|
1207
|
+
const verification = policy.verify;
|
|
1208
|
+
if (verification) {
|
|
1209
|
+
const result = verifyWebhook({
|
|
1210
|
+
verification,
|
|
1211
|
+
headers: headerRecord(req),
|
|
1212
|
+
raw
|
|
1213
|
+
});
|
|
1214
|
+
if (!result.ok) {
|
|
1215
|
+
logger$1.warn("Rejected webhook delivery for @package/@endpoint: @reason", row.namespace, row.endpoint, result.reason);
|
|
1216
|
+
writeError(res, 401, "Signature verification failed");
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
1221
|
+
const queryParams = Object.fromEntries(url.searchParams);
|
|
1222
|
+
const signatureHeader = verification ? schemeHeader(verification) : void 0;
|
|
1223
|
+
const headers = headerRecord(req);
|
|
1224
|
+
const body = parseWebhookBody(raw, headers["content-type"]);
|
|
1225
|
+
const challenge = challengeValue(policy.challengeField, queryParams, headers, body);
|
|
1226
|
+
if (challenge !== void 0) {
|
|
1227
|
+
res.statusCode = 200;
|
|
1228
|
+
res.setHeader("content-type", "text/plain");
|
|
1229
|
+
res.end(challenge);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (policy.dedupe) {
|
|
1233
|
+
const key = dedupeKey(policy.dedupe.field, queryParams, headers, body);
|
|
1234
|
+
if (key !== void 0 && await this.#store.seen(token, key, policy.dedupe.ttlSeconds ?? DEFAULT_DEDUPE_TTL_SECONDS)) {
|
|
1235
|
+
res.statusCode = 200;
|
|
1236
|
+
res.end();
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
const reply = await spec.onRequest({
|
|
1241
|
+
key: row.ownerKey,
|
|
1242
|
+
method,
|
|
1243
|
+
path: url.pathname,
|
|
1244
|
+
queryParams,
|
|
1245
|
+
headers: redactHeaders(headers, signatureHeader),
|
|
1246
|
+
raw,
|
|
1247
|
+
body
|
|
1248
|
+
});
|
|
1249
|
+
res.statusCode = reply.status;
|
|
1250
|
+
if (reply.body !== void 0 && method !== "HEAD") {
|
|
1251
|
+
res.setHeader("content-type", reply.contentType ?? "text/plain");
|
|
1252
|
+
res.end(reply.body);
|
|
1253
|
+
} else res.end();
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
logger$1.error("Webhook delivery failed for @method", req.method, error);
|
|
1256
|
+
writeError(res, 500, "Internal server error");
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
function headerRecord(req) {
|
|
1261
|
+
const headers = {};
|
|
1262
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
1263
|
+
if (value === void 0) continue;
|
|
1264
|
+
headers[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
|
|
1265
|
+
}
|
|
1266
|
+
return headers;
|
|
1267
|
+
}
|
|
1268
|
+
function scalar(value) {
|
|
1269
|
+
if (typeof value === "string" && value !== "") return value;
|
|
1270
|
+
if (typeof value === "number") return String(value);
|
|
1271
|
+
}
|
|
1272
|
+
/** Walks a dot-separated path into the parsed body. */
|
|
1273
|
+
function bodyPath(path, body) {
|
|
1274
|
+
let current = body;
|
|
1275
|
+
for (const segment of path.split(".")) {
|
|
1276
|
+
if (!current || typeof current !== "object") return void 0;
|
|
1277
|
+
current = current[segment];
|
|
1278
|
+
}
|
|
1279
|
+
return scalar(current);
|
|
1280
|
+
}
|
|
1281
|
+
/** Resolves where a provider put a value. A bare string means query first, then a
|
|
1282
|
+
* top-level body field; `{ header }` and `{ body }` reach what that cannot — GitHub's delivery id is a header, Stripe's event id is nested. */
|
|
1283
|
+
function fieldValue(field, queryParams, headers, body) {
|
|
1284
|
+
if (typeof field === "object") return "header" in field ? scalar(headers[field.header.toLowerCase()]) : bodyPath(field.body, body);
|
|
1285
|
+
const fromQuery = scalar(queryParams[field]);
|
|
1286
|
+
if (fromQuery !== void 0) return fromQuery;
|
|
1287
|
+
return body && typeof body === "object" ? scalar(body[field]) : void 0;
|
|
1288
|
+
}
|
|
1289
|
+
function challengeValue(field, queryParams, headers, body) {
|
|
1290
|
+
return field === void 0 ? void 0 : fieldValue(field, queryParams, headers, body);
|
|
1291
|
+
}
|
|
1292
|
+
function dedupeKey(field, queryParams, headers, body) {
|
|
1293
|
+
return fieldValue(field, queryParams, headers, body);
|
|
1294
|
+
}
|
|
1295
|
+
//#endregion
|
|
1296
|
+
//#region src/http/webhook-store.ts
|
|
1297
|
+
const TOKEN_BYTES = 16;
|
|
1298
|
+
const TOKEN_PATTERN = /^[0-9a-f]{32}$/;
|
|
1299
|
+
/** Opaque and unguessable: the URL is a bearer capability held by a third party. */
|
|
1300
|
+
function newWebhookToken() {
|
|
1301
|
+
return randomBytes(TOKEN_BYTES).toString("hex");
|
|
1302
|
+
}
|
|
1303
|
+
const NAMESPACE = "reactor_webhooks";
|
|
1304
|
+
/** Relational implementation, over the reactor's own database. */
|
|
1305
|
+
var RelationalWebhookStore = class {
|
|
1306
|
+
#db;
|
|
1307
|
+
constructor(relationalDb) {
|
|
1308
|
+
this.relationalDb = relationalDb;
|
|
1309
|
+
}
|
|
1310
|
+
async init() {
|
|
1311
|
+
if (this.#db) return;
|
|
1312
|
+
const db = await this.relationalDb.createNamespace(NAMESPACE);
|
|
1313
|
+
await db.schema.createTable("webhook_endpoints").ifNotExists().addColumn("token", "text", (col) => col.primaryKey()).addColumn("namespace", "text", (col) => col.notNull()).addColumn("endpoint", "text", (col) => col.notNull()).addColumn("owner_key", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.notNull()).execute();
|
|
1314
|
+
await db.schema.createIndex("webhook_endpoints_owner").ifNotExists().on("webhook_endpoints").columns([
|
|
1315
|
+
"namespace",
|
|
1316
|
+
"endpoint",
|
|
1317
|
+
"owner_key"
|
|
1318
|
+
]).unique().execute();
|
|
1319
|
+
await db.schema.createTable("webhook_deliveries").ifNotExists().addColumn("token", "text", (col) => col.notNull()).addColumn("dedupe_key", "text", (col) => col.notNull()).addColumn("seen_at", "text", (col) => col.notNull()).addColumn("claim_id", "text", (col) => col.notNull()).execute();
|
|
1320
|
+
await db.schema.createIndex("webhook_deliveries_key").ifNotExists().on("webhook_deliveries").columns(["token", "dedupe_key"]).unique().execute();
|
|
1321
|
+
await db.schema.createIndex("webhook_deliveries_seen_at").ifNotExists().on("webhook_deliveries").columns(["seen_at"]).execute();
|
|
1322
|
+
this.#db = db;
|
|
1323
|
+
}
|
|
1324
|
+
get #handle() {
|
|
1325
|
+
if (!this.#db) throw new Error("Webhook store used before init()");
|
|
1326
|
+
return this.#db;
|
|
1327
|
+
}
|
|
1328
|
+
async ensure(namespace, endpoint, ownerKey) {
|
|
1329
|
+
const existing = await this.#handle.selectFrom("webhook_endpoints").selectAll().where("namespace", "=", namespace).where("endpoint", "=", endpoint).where("owner_key", "=", ownerKey).executeTakeFirst();
|
|
1330
|
+
if (existing) return toRow(existing);
|
|
1331
|
+
const row = {
|
|
1332
|
+
token: newWebhookToken(),
|
|
1333
|
+
namespace,
|
|
1334
|
+
endpoint,
|
|
1335
|
+
owner_key: ownerKey,
|
|
1336
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1337
|
+
};
|
|
1338
|
+
await this.#handle.insertInto("webhook_endpoints").values(row).onConflict((oc) => oc.columns([
|
|
1339
|
+
"namespace",
|
|
1340
|
+
"endpoint",
|
|
1341
|
+
"owner_key"
|
|
1342
|
+
]).doNothing()).execute();
|
|
1343
|
+
const settled = await this.#handle.selectFrom("webhook_endpoints").selectAll().where("namespace", "=", namespace).where("endpoint", "=", endpoint).where("owner_key", "=", ownerKey).executeTakeFirst();
|
|
1344
|
+
return settled ? toRow(settled) : toRow(row);
|
|
1345
|
+
}
|
|
1346
|
+
async find(token) {
|
|
1347
|
+
if (!TOKEN_PATTERN.test(token)) return void 0;
|
|
1348
|
+
const found = await this.#handle.selectFrom("webhook_endpoints").selectAll().where("token", "=", token).executeTakeFirst();
|
|
1349
|
+
return found ? toRow(found) : void 0;
|
|
1350
|
+
}
|
|
1351
|
+
async list(namespace, endpoint) {
|
|
1352
|
+
let query = this.#handle.selectFrom("webhook_endpoints").selectAll().where("namespace", "=", namespace);
|
|
1353
|
+
if (endpoint) query = query.where("endpoint", "=", endpoint);
|
|
1354
|
+
return (await query.execute()).map(toRow);
|
|
1355
|
+
}
|
|
1356
|
+
async revoke(namespace, endpoint, ownerKey) {
|
|
1357
|
+
await this.#handle.deleteFrom("webhook_endpoints").where("namespace", "=", namespace).where("endpoint", "=", endpoint).where("owner_key", "=", ownerKey).execute();
|
|
1358
|
+
}
|
|
1359
|
+
async seen(token, key, ttlSeconds) {
|
|
1360
|
+
const now = Date.now();
|
|
1361
|
+
const cutoff = (/* @__PURE__ */ new Date(now - ttlSeconds * 1e3)).toISOString();
|
|
1362
|
+
await this.#handle.deleteFrom("webhook_deliveries").where("seen_at", "<", cutoff).execute();
|
|
1363
|
+
const claimId = randomBytes(16).toString("hex");
|
|
1364
|
+
await this.#handle.insertInto("webhook_deliveries").values({
|
|
1365
|
+
token,
|
|
1366
|
+
dedupe_key: key,
|
|
1367
|
+
seen_at: new Date(now).toISOString(),
|
|
1368
|
+
claim_id: claimId
|
|
1369
|
+
}).onConflict((oc) => oc.columns(["token", "dedupe_key"]).doNothing()).execute();
|
|
1370
|
+
return (await this.#handle.selectFrom("webhook_deliveries").select("claim_id").where("token", "=", token).where("dedupe_key", "=", key).executeTakeFirst())?.claim_id !== claimId;
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
function toRow(row) {
|
|
1374
|
+
return {
|
|
1375
|
+
token: row.token,
|
|
1376
|
+
namespace: row.namespace,
|
|
1377
|
+
endpoint: row.endpoint,
|
|
1378
|
+
ownerKey: row.owner_key,
|
|
1379
|
+
createdAt: row.created_at
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
/** In-memory store, for hosts with no relational database and for tests. */
|
|
1383
|
+
var MemoryWebhookStore = class {
|
|
1384
|
+
#byToken = /* @__PURE__ */ new Map();
|
|
1385
|
+
#deliveries = /* @__PURE__ */ new Map();
|
|
1386
|
+
init() {
|
|
1387
|
+
return Promise.resolve();
|
|
1388
|
+
}
|
|
1389
|
+
ensure(namespace, endpoint, ownerKey) {
|
|
1390
|
+
for (const row of this.#byToken.values()) if (row.namespace === namespace && row.endpoint === endpoint && row.ownerKey === ownerKey) return Promise.resolve(row);
|
|
1391
|
+
const row = {
|
|
1392
|
+
token: newWebhookToken(),
|
|
1393
|
+
namespace,
|
|
1394
|
+
endpoint,
|
|
1395
|
+
ownerKey,
|
|
1396
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1397
|
+
};
|
|
1398
|
+
this.#byToken.set(row.token, row);
|
|
1399
|
+
return Promise.resolve(row);
|
|
1400
|
+
}
|
|
1401
|
+
find(token) {
|
|
1402
|
+
return Promise.resolve(this.#byToken.get(token));
|
|
1403
|
+
}
|
|
1404
|
+
list(namespace, endpoint) {
|
|
1405
|
+
return Promise.resolve([...this.#byToken.values()].filter((row) => row.namespace === namespace && (endpoint === void 0 || row.endpoint === endpoint)));
|
|
1406
|
+
}
|
|
1407
|
+
revoke(namespace, endpoint, ownerKey) {
|
|
1408
|
+
for (const [token, row] of this.#byToken) if (row.namespace === namespace && row.endpoint === endpoint && row.ownerKey === ownerKey) this.#byToken.delete(token);
|
|
1409
|
+
return Promise.resolve();
|
|
1410
|
+
}
|
|
1411
|
+
seen(token, key, ttlSeconds) {
|
|
1412
|
+
const now = Date.now();
|
|
1413
|
+
for (const [k, at] of this.#deliveries) if (at < now - ttlSeconds * 1e3) this.#deliveries.delete(k);
|
|
1414
|
+
const composite = `${token}|${key}`;
|
|
1415
|
+
if (this.#deliveries.has(composite)) return Promise.resolve(true);
|
|
1416
|
+
this.#deliveries.set(composite, now);
|
|
1417
|
+
return Promise.resolve(false);
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
//#endregion
|
|
332
1421
|
//#region src/utils/create-schema.ts
|
|
333
1422
|
const logger = childLogger(["reactor-api", "create-schema"]);
|
|
334
1423
|
/**
|
|
@@ -3252,7 +4341,9 @@ var GraphQLManager = class {
|
|
|
3252
4341
|
*/
|
|
3253
4342
|
reactorDriveClient;
|
|
3254
4343
|
authorizationService;
|
|
3255
|
-
|
|
4344
|
+
/** Route service in use: the injected one, or one built on first need. */
|
|
4345
|
+
#fallbackRoutes;
|
|
4346
|
+
constructor(path, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, logger, httpAdapter, gatewayAdapter, authService, documentPermissionService, featureFlags = DefaultFeatureFlags, port = 4001, authorizationService, reactorDriveClient, syncServingGate, httpRoutes) {
|
|
3256
4347
|
this.path = path;
|
|
3257
4348
|
this.httpServer = httpServer;
|
|
3258
4349
|
this.wsServer = wsServer;
|
|
@@ -3268,6 +4359,7 @@ var GraphQLManager = class {
|
|
|
3268
4359
|
this.featureFlags = featureFlags;
|
|
3269
4360
|
this.port = port;
|
|
3270
4361
|
this.syncServingGate = syncServingGate;
|
|
4362
|
+
this.httpRoutes = httpRoutes;
|
|
3271
4363
|
if (!authorizationService) throw new Error("GraphQLManager requires an authorizationService");
|
|
3272
4364
|
this.authorizationService = authorizationService;
|
|
3273
4365
|
this.reactorDriveClient = reactorDriveClient;
|
|
@@ -3349,6 +4441,7 @@ var GraphQLManager = class {
|
|
|
3349
4441
|
if (!hasOperationSchemas(documentModel)) continue;
|
|
3350
4442
|
try {
|
|
3351
4443
|
const subgraphInstance = new DocumentModelSubgraph(documentModel, {
|
|
4444
|
+
http: this.scopeForPackage(CORE_PACKAGE_NAME),
|
|
3352
4445
|
relationalDb: this.relationalDb,
|
|
3353
4446
|
analyticsStore: this.analyticsStore,
|
|
3354
4447
|
reactorClient: this.reactorClient,
|
|
@@ -3421,7 +4514,7 @@ var GraphQLManager = class {
|
|
|
3421
4514
|
this.subgraphHandlerCache.delete(subgraphPath);
|
|
3422
4515
|
const routeHandle = this.subgraphRouteHandles.get(subgraphPath);
|
|
3423
4516
|
if (routeHandle !== void 0) {
|
|
3424
|
-
|
|
4517
|
+
routeHandle.dispose();
|
|
3425
4518
|
this.subgraphRouteHandles.delete(subgraphPath);
|
|
3426
4519
|
}
|
|
3427
4520
|
const wsDisposer = this.subgraphWsDisposers.get(subgraphPath);
|
|
@@ -3463,13 +4556,28 @@ var GraphQLManager = class {
|
|
|
3463
4556
|
return this.authorizationService;
|
|
3464
4557
|
}
|
|
3465
4558
|
/**
|
|
4559
|
+
* The HTTP scope a package's subgraphs get. Falls back to a scope over a
|
|
4560
|
+
* throwaway service when route hosting is not configured, so a subgraph can
|
|
4561
|
+
* always rely on `args.http` existing.
|
|
4562
|
+
*/
|
|
4563
|
+
scopeForPackage(packageName) {
|
|
4564
|
+
this.#fallbackRoutes ??= this.httpRoutes ?? new HttpRouteService({
|
|
4565
|
+
httpAdapter: this.httpAdapter,
|
|
4566
|
+
basePath: this.path,
|
|
4567
|
+
authService: this.authService
|
|
4568
|
+
});
|
|
4569
|
+
return this.#fallbackRoutes.scopeForOrNull(packageName);
|
|
4570
|
+
}
|
|
4571
|
+
/**
|
|
3466
4572
|
* Register a subgraph class. `packageName` labels the resulting instance
|
|
3467
4573
|
* with its contributing package so the instance can be torn down when the
|
|
3468
4574
|
* package is removed or drops the subgraph (see unregisterPackage and
|
|
3469
|
-
* prunePackageSubgraphs)
|
|
4575
|
+
* prunePackageSubgraphs), and decides the namespace its REST routes and
|
|
4576
|
+
* webhooks live under.
|
|
3470
4577
|
*/
|
|
3471
4578
|
async registerSubgraph(subgraph, supergraph = "", core = false, packageName) {
|
|
3472
4579
|
const subgraphInstance = new subgraph({
|
|
4580
|
+
http: this.scopeForPackage(packageName ?? "@powerhousedao/reactor-api"),
|
|
3473
4581
|
relationalDb: this.relationalDb,
|
|
3474
4582
|
analyticsStore: this.analyticsStore,
|
|
3475
4583
|
reactorClient: this.reactorClient,
|
|
@@ -3490,12 +4598,25 @@ var GraphQLManager = class {
|
|
|
3490
4598
|
* package has no registered subgraphs.
|
|
3491
4599
|
*/
|
|
3492
4600
|
async unregisterPackage(packageName) {
|
|
4601
|
+
this.#disposeHttpScope(packageName);
|
|
3493
4602
|
const byName = this.packageSubgraphs.get(packageName);
|
|
3494
4603
|
if (!byName || byName.size === 0) return;
|
|
3495
4604
|
for (const instance of Array.from(byName.values())) await this.#removeSubgraphInstance(instance);
|
|
3496
4605
|
await this.updateRouter();
|
|
3497
4606
|
}
|
|
3498
4607
|
/**
|
|
4608
|
+
* Releases the package's HTTP scope: every route it mounted, and its webhook
|
|
4609
|
+
* registrations. Idempotent, and silent for a package that never had one.
|
|
4610
|
+
*/
|
|
4611
|
+
#disposeHttpScope(packageName) {
|
|
4612
|
+
const routes = this.#fallbackRoutes ?? this.httpRoutes;
|
|
4613
|
+
try {
|
|
4614
|
+
routes?.disposeScope(packageName);
|
|
4615
|
+
} catch (error) {
|
|
4616
|
+
this.logger.warn(`Failed to release HTTP routes for "${packageName}": @error`, error);
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
/**
|
|
3499
4620
|
* Remove this package's registered subgraphs whose names are not in
|
|
3500
4621
|
* `keepNames` — the package is still loaded but dropped some of its
|
|
3501
4622
|
* subgraphs. Refreshes the router if anything was removed.
|
|
@@ -3730,7 +4851,7 @@ var GraphQLManager = class {
|
|
|
3730
4851
|
const modules = Array.from(allSubgraphs.values()).filter((subgraph) => subgraph.hasSubscriptions).map((subgraph) => this.#buildSubgraphSchemaModule(subgraph));
|
|
3731
4852
|
if (modules.length === 0) {
|
|
3732
4853
|
if (this.sseRouteHandle !== void 0) {
|
|
3733
|
-
this.
|
|
4854
|
+
this.sseRouteHandle.dispose();
|
|
3734
4855
|
this.sseRouteHandle = void 0;
|
|
3735
4856
|
}
|
|
3736
4857
|
return;
|
|
@@ -3758,7 +4879,7 @@ var GraphQLManager = class {
|
|
|
3758
4879
|
contextFactory: this.#makeContextFactory()
|
|
3759
4880
|
});
|
|
3760
4881
|
const handler = this.#composeFetchMiddleware(rawHandler);
|
|
3761
|
-
if (this.sseRouteHandle !== void 0) this.
|
|
4882
|
+
if (this.sseRouteHandle !== void 0) this.sseRouteHandle.dispose();
|
|
3762
4883
|
this.sseRouteHandle = this.httpAdapter.mount(ssePath, handler, { exact: true });
|
|
3763
4884
|
}
|
|
3764
4885
|
/**
|
|
@@ -4902,10 +6023,10 @@ const ADMIN_USERS = getAdminUsers();
|
|
|
4902
6023
|
//#endregion
|
|
4903
6024
|
//#region src/graphql/system/version.ts
|
|
4904
6025
|
function getVersion() {
|
|
4905
|
-
return "6.2.
|
|
6026
|
+
return "6.2.3-dev.1";
|
|
4906
6027
|
}
|
|
4907
6028
|
function getGitHash() {
|
|
4908
|
-
return "
|
|
6029
|
+
return "4f706b751c94b165e31129acf22704dbb96b56c2";
|
|
4909
6030
|
}
|
|
4910
6031
|
function getGitUrl() {
|
|
4911
6032
|
return buildTreeUrl(getGitHash());
|
|
@@ -6557,8 +7678,8 @@ function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
|
|
|
6557
7678
|
/**
|
|
6558
7679
|
* Sets up the subgraph manager and registers subgraphs
|
|
6559
7680
|
*/
|
|
6560
|
-
async function setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate) {
|
|
6561
|
-
const graphqlManager = new GraphQLManager(config.basePath, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, logger, httpAdapter, await createGatewayAdapter("apollo", logger), authService, documentPermissionService, { enableDocumentModelSubgraphs }, port, authorizationService, reactorDriveClient, syncServingGate);
|
|
7681
|
+
async function setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes) {
|
|
7682
|
+
const graphqlManager = new GraphQLManager(config.basePath, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, logger, httpAdapter, await createGatewayAdapter("apollo", logger), authService, documentPermissionService, { enableDocumentModelSubgraphs }, port, authorizationService, reactorDriveClient, syncServingGate, httpRoutes);
|
|
6562
7683
|
await graphqlManager.init(subgraphs.core, authFetchMiddleware, requireAuthFetchMiddleware);
|
|
6563
7684
|
for (const [packageName, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql", false, packageName);
|
|
6564
7685
|
await graphqlManager.updateRouter();
|
|
@@ -6567,7 +7688,7 @@ async function setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuth
|
|
|
6567
7688
|
/**
|
|
6568
7689
|
* Sets up event listeners for package manager changes
|
|
6569
7690
|
*/
|
|
6570
|
-
function setupEventListeners(pkgManager, graphqlManager, reactorProcessorManager,
|
|
7691
|
+
function setupEventListeners(pkgManager, graphqlManager, reactorProcessorManager, moduleFor, documentModelRegistry) {
|
|
6571
7692
|
pkgManager.onDocumentModelsChange((packagedModels) => {
|
|
6572
7693
|
if (documentModelRegistry) {
|
|
6573
7694
|
const newModules = getUniqueDocumentModels(Object.values(packagedModels).flat());
|
|
@@ -6611,7 +7732,7 @@ function setupEventListeners(pkgManager, graphqlManager, reactorProcessorManager
|
|
|
6611
7732
|
knownProcessorPackages = new Set(processors.keys());
|
|
6612
7733
|
for (const [packageName, fns] of processors) {
|
|
6613
7734
|
await reactorProcessorManager.unregisterFactory(packageName);
|
|
6614
|
-
const validBuilders = fns.map((fn) => fn(
|
|
7735
|
+
const validBuilders = fns.map((fn) => fn(moduleFor(packageName))).filter((factory) => typeof factory === "function");
|
|
6615
7736
|
if (!validBuilders.length) continue;
|
|
6616
7737
|
await reactorProcessorManager.registerFactory(packageName, async (driveHeader) => (await Promise.all(validBuilders.map(async (driveFactory) => {
|
|
6617
7738
|
try {
|
|
@@ -6790,9 +7911,25 @@ async function _setupCommonInfrastructure(options) {
|
|
|
6790
7911
|
configFile: options.configFile,
|
|
6791
7912
|
packages: options.packages ?? []
|
|
6792
7913
|
});
|
|
7914
|
+
const publicUrl = resolvePublicOrigin(port);
|
|
7915
|
+
const webhooks = new WebhookService({
|
|
7916
|
+
store: new RelationalWebhookStore(relationalDb),
|
|
7917
|
+
basePath: config.basePath,
|
|
7918
|
+
publicUrl
|
|
7919
|
+
});
|
|
7920
|
+
const httpRoutes = new HttpRouteService({
|
|
7921
|
+
httpAdapter,
|
|
7922
|
+
basePath: config.basePath,
|
|
7923
|
+
authService,
|
|
7924
|
+
webhooks,
|
|
7925
|
+
publicUrl,
|
|
7926
|
+
trustProxy: true
|
|
7927
|
+
});
|
|
7928
|
+
webhooks.attach(httpRoutes.hostScope(CORE_PACKAGE_NAME, path.posix.join("/", config.basePath ?? "/", WEBHOOK_SEGMENT)));
|
|
6793
7929
|
return {
|
|
6794
7930
|
port,
|
|
6795
7931
|
httpAdapter,
|
|
7932
|
+
httpRoutes,
|
|
6796
7933
|
authFetchMiddleware,
|
|
6797
7934
|
requireAuthFetchMiddleware,
|
|
6798
7935
|
authService,
|
|
@@ -6810,8 +7947,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
6810
7947
|
/**
|
|
6811
7948
|
* Private helper function containing common setup logic for API initialization
|
|
6812
7949
|
*/
|
|
6813
|
-
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, readModels, attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers = [], reactorDriveClient, syncServingGate) {
|
|
6814
|
-
const
|
|
7950
|
+
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, readModels, attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers = [], reactorDriveClient, syncServingGate, httpRoutes) {
|
|
7951
|
+
const hostModuleBase = {
|
|
6815
7952
|
...createReactorHostModuleBase({
|
|
6816
7953
|
client: reactorClient,
|
|
6817
7954
|
readModels,
|
|
@@ -6822,6 +7959,10 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6822
7959
|
}),
|
|
6823
7960
|
attachments: createAttachmentClient(attachments.service)
|
|
6824
7961
|
};
|
|
7962
|
+
const moduleFor = (packageName) => ({
|
|
7963
|
+
...hostModuleBase,
|
|
7964
|
+
http: httpRoutes?.scopeForOrNull(packageName)
|
|
7965
|
+
});
|
|
6825
7966
|
const mcpServerEnabled = options.mcp ?? true;
|
|
6826
7967
|
const logger = options.logger ?? defaultLogger;
|
|
6827
7968
|
const configuredProcessorEntries = Object.entries(options.processors ?? {});
|
|
@@ -6829,7 +7970,7 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6829
7970
|
for (const [packageName, fns] of processorEntries) {
|
|
6830
7971
|
const validFactories = (await Promise.allSettled(fns.map(async (fn) => {
|
|
6831
7972
|
try {
|
|
6832
|
-
return fn(
|
|
7973
|
+
return fn(moduleFor(packageName));
|
|
6833
7974
|
} catch (e) {
|
|
6834
7975
|
logger.error(`Error initializing processor factory for package ${packageName}:`, e);
|
|
6835
7976
|
return null;
|
|
@@ -6858,8 +7999,8 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6858
7999
|
const graphqlManager = await setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, {
|
|
6859
8000
|
extended: subgraphs,
|
|
6860
8001
|
core: coreSubgraphs
|
|
6861
|
-
}, logger.child(["graphql-manager"]), authorizationService, authService, documentPermissionService, options.enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate);
|
|
6862
|
-
setupEventListeners(packages, graphqlManager, reactorProcessorManager,
|
|
8002
|
+
}, logger.child(["graphql-manager"]), authorizationService, authService, documentPermissionService, options.enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes);
|
|
8003
|
+
setupEventListeners(packages, graphqlManager, reactorProcessorManager, moduleFor, documentModelRegistry);
|
|
6863
8004
|
if (mcpServerEnabled) {
|
|
6864
8005
|
await setupMcpServer({
|
|
6865
8006
|
client: reactorClient,
|
|
@@ -6868,21 +8009,24 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6868
8009
|
}, httpAdapter);
|
|
6869
8010
|
logger.info(`MCP server available at http://localhost:${port}/mcp`);
|
|
6870
8011
|
}
|
|
8012
|
+
const dispose = buildApiDispose({
|
|
8013
|
+
graphqlManager,
|
|
8014
|
+
httpRoutes,
|
|
8015
|
+
httpServer,
|
|
8016
|
+
wsServer,
|
|
8017
|
+
dbClosers,
|
|
8018
|
+
logger
|
|
8019
|
+
});
|
|
6871
8020
|
return {
|
|
6872
8021
|
httpAdapter,
|
|
8022
|
+
httpRoutes: httpRoutes ?? new HttpRouteService({ httpAdapter }),
|
|
6873
8023
|
graphqlManager,
|
|
6874
8024
|
packages,
|
|
6875
8025
|
attachments,
|
|
6876
8026
|
attachmentReferenceIndex,
|
|
6877
8027
|
attachmentAccess,
|
|
6878
8028
|
authService,
|
|
6879
|
-
dispose
|
|
6880
|
-
graphqlManager,
|
|
6881
|
-
httpServer,
|
|
6882
|
-
wsServer,
|
|
6883
|
-
dbClosers,
|
|
6884
|
-
logger
|
|
6885
|
-
})
|
|
8029
|
+
dispose
|
|
6886
8030
|
};
|
|
6887
8031
|
}
|
|
6888
8032
|
/**
|
|
@@ -6893,11 +8037,16 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6893
8037
|
* since this runs on the way to process exit.
|
|
6894
8038
|
*/
|
|
6895
8039
|
function buildApiDispose(args) {
|
|
6896
|
-
const { graphqlManager, httpServer, wsServer, dbClosers, logger } = args;
|
|
8040
|
+
const { graphqlManager, httpRoutes, httpServer, wsServer, dbClosers, logger } = args;
|
|
6897
8041
|
let disposed = false;
|
|
6898
8042
|
return async () => {
|
|
6899
8043
|
if (disposed) return;
|
|
6900
8044
|
disposed = true;
|
|
8045
|
+
try {
|
|
8046
|
+
httpRoutes?.disposeAll();
|
|
8047
|
+
} catch (error) {
|
|
8048
|
+
logger.error("API dispose: releasing HTTP routes failed: @error", error);
|
|
8049
|
+
}
|
|
6901
8050
|
try {
|
|
6902
8051
|
await graphqlManager.shutdown();
|
|
6903
8052
|
} catch (error) {
|
|
@@ -6923,7 +8072,7 @@ function buildApiDispose(args) {
|
|
|
6923
8072
|
};
|
|
6924
8073
|
}
|
|
6925
8074
|
async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
6926
|
-
const { port, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness } = await _setupCommonInfrastructure(options);
|
|
8075
|
+
const { port, httpAdapter, httpRoutes, authFetchMiddleware, requireAuthFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness } = await _setupCommonInfrastructure(options);
|
|
6927
8076
|
const { documentModels, upgradeManifests, processors, subgraphs } = await packages.init();
|
|
6928
8077
|
const { module: reactorClientModule, reactorDriveClient, attachmentReferenceProjection = {
|
|
6929
8078
|
status: "unavailable",
|
|
@@ -6940,7 +8089,7 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
6940
8089
|
const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
|
|
6941
8090
|
if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from InProcessReactorClientModule");
|
|
6942
8091
|
return {
|
|
6943
|
-
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers, reactorDriveClient, buildSyncServingGate(reactorClientModule.reactorModule, authorizationConfig, options.logger ?? defaultLogger)),
|
|
8092
|
+
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers, reactorDriveClient, buildSyncServingGate(reactorClientModule.reactorModule, authorizationConfig, options.logger ?? defaultLogger), httpRoutes),
|
|
6944
8093
|
client: reactorClient,
|
|
6945
8094
|
syncManager,
|
|
6946
8095
|
documentModelRegistry,
|
|
@@ -6949,6 +8098,26 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
6949
8098
|
packageManager: packages
|
|
6950
8099
|
};
|
|
6951
8100
|
}
|
|
8101
|
+
/**
|
|
8102
|
+
* The origin to advertise in a webhook URL.
|
|
8103
|
+
*
|
|
8104
|
+
* A provider is a third party: it has to be given something it can resolve, so
|
|
8105
|
+
* a relative path is not an option. The platform variables come first, then a
|
|
8106
|
+
* bare deploy domain, and finally the local origin — which is right for `ph
|
|
8107
|
+
* dev` behind a tunnel and, at worst, obviously wrong rather than silently
|
|
8108
|
+
* unusable.
|
|
8109
|
+
*/
|
|
8110
|
+
function resolvePublicOrigin(port) {
|
|
8111
|
+
const explicit = process.env.PUBLIC_URL ?? process.env.RENDER_EXTERNAL_URL;
|
|
8112
|
+
if (explicit) return withScheme(explicit);
|
|
8113
|
+
const domain = process.env.HEROKU_APP_DEFAULT_DOMAIN_NAME;
|
|
8114
|
+
if (domain) return withScheme(domain);
|
|
8115
|
+
return `http://localhost:${port}`;
|
|
8116
|
+
}
|
|
8117
|
+
function withScheme(origin) {
|
|
8118
|
+
const trimmed = origin.replace(/\/+$/, "");
|
|
8119
|
+
return /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
8120
|
+
}
|
|
6952
8121
|
//#endregion
|
|
6953
8122
|
//#region src/services/package-storage.ts
|
|
6954
8123
|
var InMemoryPackageStorage = class {
|
|
@@ -7053,7 +8222,7 @@ var PackageManagementService = class {
|
|
|
7053
8222
|
}
|
|
7054
8223
|
};
|
|
7055
8224
|
//#endregion
|
|
7056
|
-
export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessService, AuthDecision, AuthDecisionSchema, AuthService, AuthSubgraph, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, CanonicalDocumentIdResolutionError, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, EvaluateActionsDocument, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, ImportPackageLoader, InMemoryPackageStorage, JobChangesDocument, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PGLITE_UTC_PARSERS, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createCanonicalDocumentIdResolver, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createRenownCredentialVerifier, createSchema, decodeExplorerUrlState, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getExplorerPrefix, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getUniqueUpgradeManifests, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, parseDriveUrl, renderGraphqlPlayground, resolveRenownConfig };
|
|
8225
|
+
export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessService, AuthDecision, AuthDecisionSchema, AuthService, AuthSubgraph, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, CORE_PACKAGE_NAME, CanonicalDocumentIdResolutionError, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, EvaluateActionsDocument, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, HttpRouteService, ImportPackageLoader, InMemoryPackageStorage, InvalidNamespaceError, JobChangesDocument, MemoryWebhookStore, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PACKAGE_ROUTE_SEGMENT, PGLITE_UTC_PARSERS, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RelationalWebhookStore, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, WEBHOOK_SEGMENT, WebhookService, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertHostMountPath, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createCanonicalDocumentIdResolver, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createRenownCredentialVerifier, createSchema, decodeExplorerUrlState, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getExplorerPrefix, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getUniqueUpgradeManifests, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, namespacePath, namespaceSegments, newWebhookToken, parseDriveUrl, parseWebhookBody, redactHeaders, renderGraphqlPlayground, resolvePackageName, resolveRenownConfig, verifyWebhook };
|
|
7057
8226
|
|
|
7058
8227
|
//# sourceMappingURL=index.mjs.map
|
|
7059
|
-
//# debugId=
|
|
8228
|
+
//# debugId=46ad8545-02e9-5fb8-b003-7b9b93f82f75
|