@powerhousedao/reactor-api 6.2.2-staging.0 → 6.2.3-dev.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/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 +1202 -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]="ef62c13d-c8af-5733-beae-202ad8381942")}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,1100 @@ 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
|
+
actor,
|
|
788
|
+
transport: resolveTransport(req, this.#trustProxy)
|
|
789
|
+
});
|
|
790
|
+
} catch (error) {
|
|
791
|
+
logger$2.error("Route handler failed for @package", this.owner, error);
|
|
792
|
+
writeError(res, 500, "Internal server error");
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/** Resolves the caller, answering the request itself when the route refuses it;
|
|
796
|
+
* returns REJECTED in that case so the caller stops. */
|
|
797
|
+
async #authenticate(auth, req, res) {
|
|
798
|
+
if (auth === "public") return void 0;
|
|
799
|
+
if (typeof auth === "function") {
|
|
800
|
+
const result = await auth(req);
|
|
801
|
+
if (!result.authorized) {
|
|
802
|
+
if ("response" in result) await writeResponse(res, result.response, req.method ?? "GET");
|
|
803
|
+
else writeError(res, result.status, result.message);
|
|
804
|
+
return REJECTED;
|
|
805
|
+
}
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
if (!this.#authService) return ANONYMOUS;
|
|
809
|
+
const result = await this.#authService.verifyBearer(req.headers.authorization);
|
|
810
|
+
if (result instanceof Response) {
|
|
811
|
+
await writeResponse(res, result, req.method ?? "GET");
|
|
812
|
+
return REJECTED;
|
|
813
|
+
}
|
|
814
|
+
if (result.auth_enabled && !result.user && auth === "renown") {
|
|
815
|
+
writeError(res, 401, "Authentication required");
|
|
816
|
+
return REJECTED;
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
user: result.user,
|
|
820
|
+
authEnabled: result.auth_enabled
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
/** Builds the Fetch Request from the raw stream and runs the handler. */
|
|
824
|
+
async #serve(spec, body, req, res, ctx) {
|
|
825
|
+
const method = req.method ?? "GET";
|
|
826
|
+
const url = `${ctx.transport.baseUrl}${req.url ?? "/"}`;
|
|
827
|
+
const controller = new AbortController();
|
|
828
|
+
req.once("close", () => controller.abort());
|
|
829
|
+
let raw;
|
|
830
|
+
let init = {
|
|
831
|
+
method,
|
|
832
|
+
headers: fetchHeaders(req),
|
|
833
|
+
signal: controller.signal
|
|
834
|
+
};
|
|
835
|
+
if (method !== "GET" && method !== "HEAD" && body !== "none") if (body === "stream") init = {
|
|
836
|
+
...init,
|
|
837
|
+
body: Readable.toWeb(req),
|
|
838
|
+
duplex: "half"
|
|
839
|
+
};
|
|
840
|
+
else {
|
|
841
|
+
try {
|
|
842
|
+
raw = await readBody(req, spec.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES$1);
|
|
843
|
+
} catch (error) {
|
|
844
|
+
if (error instanceof BodyTooLargeError) {
|
|
845
|
+
res.setHeader("connection", "close");
|
|
846
|
+
res.once("finish", () => req.destroy());
|
|
847
|
+
writeError(res, 413, "Payload too large");
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
throw error;
|
|
851
|
+
}
|
|
852
|
+
if (raw.length) init = {
|
|
853
|
+
...init,
|
|
854
|
+
body: new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength)
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
await writeResponse(res, await spec.handler(new Request(url, init), {
|
|
858
|
+
...ctx,
|
|
859
|
+
rawBody: body === "raw" ? raw ?? Buffer.alloc(0) : void 0,
|
|
860
|
+
signal: controller.signal
|
|
861
|
+
}), method);
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
const REJECTED = Symbol("rejected");
|
|
865
|
+
function routeParams(req) {
|
|
866
|
+
const params = req.params;
|
|
867
|
+
if (!params || typeof params !== "object") return {};
|
|
868
|
+
const out = {};
|
|
869
|
+
for (const [key, value] of Object.entries(params)) if (typeof value === "string") out[key] = value;
|
|
870
|
+
else if (Array.isArray(value)) out[key] = value.join("/");
|
|
871
|
+
return out;
|
|
872
|
+
}
|
|
873
|
+
/** Stands in for a package that cannot be routed — its key resolves to no npm name. Registering a route throws with
|
|
874
|
+
* the reason; nothing else does, so the package still loads and its non-HTTP behaviour is unaffected. */
|
|
875
|
+
var UnroutableScope = class {
|
|
876
|
+
owner;
|
|
877
|
+
baseUrl = "";
|
|
878
|
+
#reason;
|
|
879
|
+
constructor(packageKey, reason) {
|
|
880
|
+
this.owner = packageKey;
|
|
881
|
+
this.#reason = reason;
|
|
882
|
+
}
|
|
883
|
+
#refuse() {
|
|
884
|
+
throw new Error(`Package "${this.owner}" cannot host HTTP routes: ${this.#reason}`);
|
|
885
|
+
}
|
|
886
|
+
get() {
|
|
887
|
+
this.#refuse();
|
|
888
|
+
}
|
|
889
|
+
post() {
|
|
890
|
+
this.#refuse();
|
|
891
|
+
}
|
|
892
|
+
put() {
|
|
893
|
+
this.#refuse();
|
|
894
|
+
}
|
|
895
|
+
patch() {
|
|
896
|
+
this.#refuse();
|
|
897
|
+
}
|
|
898
|
+
delete() {
|
|
899
|
+
this.#refuse();
|
|
900
|
+
}
|
|
901
|
+
head() {
|
|
902
|
+
this.#refuse();
|
|
903
|
+
}
|
|
904
|
+
route() {
|
|
905
|
+
this.#refuse();
|
|
906
|
+
}
|
|
907
|
+
nodeRoute() {
|
|
908
|
+
this.#refuse();
|
|
909
|
+
}
|
|
910
|
+
get webhooks() {
|
|
911
|
+
return unavailableWebhooks(this.owner);
|
|
912
|
+
}
|
|
913
|
+
dispose() {}
|
|
914
|
+
};
|
|
915
|
+
/** Stands in when the host has no webhook store configured. */
|
|
916
|
+
function unavailableWebhooks(packageName) {
|
|
917
|
+
return {
|
|
918
|
+
hasPublicOrigin: false,
|
|
919
|
+
register: () => Promise.reject(/* @__PURE__ */ new Error(`Webhooks are not available on this host, so "${packageName}" cannot register one`))
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
/** Header each scheme reads when the caller does not name one: the name the format is
|
|
923
|
+
* most often carried in, not a property of it, so `WebhookVerification.header` overrides any of them. */
|
|
924
|
+
const DEFAULT_SCHEME_HEADER = {
|
|
925
|
+
none: "",
|
|
926
|
+
token: "x-webhook-token",
|
|
927
|
+
hmac: "x-signature",
|
|
928
|
+
"hmac-prefixed": "x-hub-signature-256",
|
|
929
|
+
"hmac-timestamped": "stripe-signature"
|
|
930
|
+
};
|
|
931
|
+
const OK = { ok: true };
|
|
932
|
+
/** Constant-time over equal-length inputs; a length mismatch is already a rejection, so
|
|
933
|
+
* the early return leaks nothing a caller could not measure. */
|
|
934
|
+
function secureEquals(a, b) {
|
|
935
|
+
const left = Buffer.from(a, "utf8");
|
|
936
|
+
const right = Buffer.from(b, "utf8");
|
|
937
|
+
if (left.length !== right.length) return false;
|
|
938
|
+
return timingSafeEqual(left, right);
|
|
939
|
+
}
|
|
940
|
+
function hmacDigest(secret, payload, verification) {
|
|
941
|
+
return createHmac(verification.algorithm ?? "sha256", secret).update(payload).digest(verification.encoding ?? "hex");
|
|
942
|
+
}
|
|
943
|
+
/** Hex folds case, so an uppercased digest still verifies. Base64 must not: two distinct
|
|
944
|
+
* digests can differ only in case, so folding there would accept a signature that is not the one computed. */
|
|
945
|
+
function digestEquals(presented, expected, verification) {
|
|
946
|
+
const candidate = presented.trim();
|
|
947
|
+
if ((verification.encoding ?? "hex") === "base64") return secureEquals(candidate, expected);
|
|
948
|
+
return secureEquals(candidate.toLowerCase(), expected.toLowerCase());
|
|
949
|
+
}
|
|
950
|
+
/** The label `hmac-prefixed` expects before the digest. */
|
|
951
|
+
function signaturePrefix(verification) {
|
|
952
|
+
return verification.prefix ?? `${verification.algorithm ?? "sha256"}=`;
|
|
953
|
+
}
|
|
954
|
+
/** `t=<unix>,v1=<hex>[,v1=<hex>…]`. Several `v1` values appear while a secret is being
|
|
955
|
+
* rotated, and any one of them matching is a pass. */
|
|
956
|
+
function parseTimestampedHeader(value) {
|
|
957
|
+
const signatures = [];
|
|
958
|
+
let timestamp;
|
|
959
|
+
for (const part of value.split(",")) {
|
|
960
|
+
const separator = part.indexOf("=");
|
|
961
|
+
if (separator < 0) continue;
|
|
962
|
+
const key = part.slice(0, separator).trim();
|
|
963
|
+
const item = part.slice(separator + 1).trim();
|
|
964
|
+
if (key === "t") timestamp = Number(item);
|
|
965
|
+
else if (key === "v1") signatures.push(item);
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
timestamp,
|
|
969
|
+
signatures
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
function schemeHeader(verification) {
|
|
973
|
+
return (verification.header ?? DEFAULT_SCHEME_HEADER[verification.scheme]).toLowerCase();
|
|
974
|
+
}
|
|
975
|
+
/** Verifies a delivery against the configured scheme; `raw` must be the exact bytes
|
|
976
|
+
* received. The reason is for the log only: callers answer a fixed 401, so a probing sender learns nothing from the body. */
|
|
977
|
+
function verifyWebhook(options) {
|
|
978
|
+
const { verification, headers, raw } = options;
|
|
979
|
+
if (verification.scheme === "none") return OK;
|
|
980
|
+
if (!verification.secret) return {
|
|
981
|
+
ok: false,
|
|
982
|
+
reason: "signing secret missing"
|
|
983
|
+
};
|
|
984
|
+
const header = schemeHeader(verification);
|
|
985
|
+
const presented = headers[header];
|
|
986
|
+
if (!presented) return {
|
|
987
|
+
ok: false,
|
|
988
|
+
reason: `header "${header}" absent`
|
|
989
|
+
};
|
|
990
|
+
const secret = verification.secret;
|
|
991
|
+
const tolerance = verification.toleranceSeconds ?? 300;
|
|
992
|
+
switch (verification.scheme) {
|
|
993
|
+
case "token": return secureEquals(presented, secret) ? OK : {
|
|
994
|
+
ok: false,
|
|
995
|
+
reason: "token mismatch"
|
|
996
|
+
};
|
|
997
|
+
case "hmac": return digestEquals(presented, hmacDigest(secret, raw, verification), verification) ? OK : {
|
|
998
|
+
ok: false,
|
|
999
|
+
reason: "hmac mismatch"
|
|
1000
|
+
};
|
|
1001
|
+
case "hmac-prefixed": return digestEquals(presented, `${signaturePrefix(verification)}${hmacDigest(secret, raw, verification)}`, verification) ? OK : {
|
|
1002
|
+
ok: false,
|
|
1003
|
+
reason: "hmac mismatch"
|
|
1004
|
+
};
|
|
1005
|
+
case "hmac-timestamped": {
|
|
1006
|
+
const { timestamp, signatures } = parseTimestampedHeader(presented);
|
|
1007
|
+
if (timestamp === void 0 || !Number.isFinite(timestamp)) return {
|
|
1008
|
+
ok: false,
|
|
1009
|
+
reason: "no timestamp in the signature header"
|
|
1010
|
+
};
|
|
1011
|
+
if (signatures.length === 0) return {
|
|
1012
|
+
ok: false,
|
|
1013
|
+
reason: "no v1 signature in the header"
|
|
1014
|
+
};
|
|
1015
|
+
const nowMs = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
1016
|
+
if (Math.abs(nowMs / 1e3 - timestamp) > tolerance) return {
|
|
1017
|
+
ok: false,
|
|
1018
|
+
reason: "timestamp outside the replay window"
|
|
1019
|
+
};
|
|
1020
|
+
const expected = hmacDigest(secret, Buffer.concat([Buffer.from(`${timestamp}.`, "utf8"), raw]), verification);
|
|
1021
|
+
let matched = false;
|
|
1022
|
+
for (const signature of signatures) if (digestEquals(signature, expected, verification)) matched = true;
|
|
1023
|
+
return matched ? OK : {
|
|
1024
|
+
ok: false,
|
|
1025
|
+
reason: "hmac mismatch"
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
/** Headers that would put a credential in a log or run journal. The configured signature
|
|
1031
|
+
* header joins them: it is an HMAC of the body under a live secret. */
|
|
1032
|
+
const REDACTED_HEADERS = new Set([
|
|
1033
|
+
"authorization",
|
|
1034
|
+
"proxy-authorization",
|
|
1035
|
+
"cookie",
|
|
1036
|
+
"set-cookie",
|
|
1037
|
+
"x-api-key",
|
|
1038
|
+
"x-webhook-token",
|
|
1039
|
+
"x-signature",
|
|
1040
|
+
"x-hub-signature",
|
|
1041
|
+
"x-hub-signature-256",
|
|
1042
|
+
"stripe-signature"
|
|
1043
|
+
]);
|
|
1044
|
+
const REDACTED = "[redacted]";
|
|
1045
|
+
function redactHeaders(headers, extra) {
|
|
1046
|
+
const redacted = {};
|
|
1047
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
1048
|
+
const key = name.toLowerCase();
|
|
1049
|
+
redacted[key] = REDACTED_HEADERS.has(key) || extra && key === extra.toLowerCase() ? REDACTED : value;
|
|
1050
|
+
}
|
|
1051
|
+
return redacted;
|
|
1052
|
+
}
|
|
1053
|
+
/** JSON and form bodies become objects; anything else stays decoded text, so a verified
|
|
1054
|
+
* XML or CSV payload is still reachable downstream. */
|
|
1055
|
+
function parseWebhookBody(raw, contentType) {
|
|
1056
|
+
if (raw.length === 0) return void 0;
|
|
1057
|
+
const type = (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
1058
|
+
const text = raw.toString("utf8");
|
|
1059
|
+
if (type === "application/json" || type.endsWith("+json")) try {
|
|
1060
|
+
return JSON.parse(text);
|
|
1061
|
+
} catch {
|
|
1062
|
+
return text;
|
|
1063
|
+
}
|
|
1064
|
+
if (type === "application/x-www-form-urlencoded") return Object.fromEntries(new URLSearchParams(text));
|
|
1065
|
+
return text;
|
|
1066
|
+
}
|
|
1067
|
+
//#endregion
|
|
1068
|
+
//#region src/http/webhook-service.ts
|
|
1069
|
+
const logger$1 = childLogger(["reactor-api", "webhooks"]);
|
|
1070
|
+
/** The flat, token-addressed path family. */
|
|
1071
|
+
const WEBHOOK_SEGMENT = "webhooks";
|
|
1072
|
+
const DEFAULT_MAX_BODY_BYTES = 1048576;
|
|
1073
|
+
const DEFAULT_DEDUPE_TTL_SECONDS = 300;
|
|
1074
|
+
/** One host route for `/webhooks/:token`, dispatching by token. The flat path leaks
|
|
1075
|
+
* neither package nor document; the namespace lives in the token record, which is also what stops one package's token reaching another's handler. */
|
|
1076
|
+
var WebhookService = class {
|
|
1077
|
+
#store;
|
|
1078
|
+
#path;
|
|
1079
|
+
#publicUrl;
|
|
1080
|
+
#scope;
|
|
1081
|
+
#registrations = /* @__PURE__ */ new Map();
|
|
1082
|
+
#routes = [];
|
|
1083
|
+
constructor({ store, basePath = "/", publicUrl }) {
|
|
1084
|
+
this.#store = store;
|
|
1085
|
+
this.#path = posix.join("/", basePath, WEBHOOK_SEGMENT);
|
|
1086
|
+
this.#publicUrl = publicUrl?.replace(/\/+$/, "");
|
|
1087
|
+
}
|
|
1088
|
+
/** The scope the endpoint family serves from, handed over once the route service exists;
|
|
1089
|
+
* separate from the constructor only because that service takes this one as an option. */
|
|
1090
|
+
attach(scope) {
|
|
1091
|
+
this.#scope = scope;
|
|
1092
|
+
}
|
|
1093
|
+
async init() {
|
|
1094
|
+
await this.#store.init();
|
|
1095
|
+
if (this.#routes.length) return;
|
|
1096
|
+
if (!this.#scope) throw new Error("The webhook service has no HTTP scope; the host must call attach() before a package registers");
|
|
1097
|
+
this.#routes = [this.#scope.nodeRoute({
|
|
1098
|
+
method: [
|
|
1099
|
+
"DELETE",
|
|
1100
|
+
"GET",
|
|
1101
|
+
"HEAD",
|
|
1102
|
+
"PATCH",
|
|
1103
|
+
"POST",
|
|
1104
|
+
"PUT"
|
|
1105
|
+
],
|
|
1106
|
+
path: ":token",
|
|
1107
|
+
auth: "public",
|
|
1108
|
+
handler: (req, res, ctx) => void this.#deliver(req, res, ctx.params)
|
|
1109
|
+
})];
|
|
1110
|
+
}
|
|
1111
|
+
dispose() {
|
|
1112
|
+
for (const handle of this.#routes) handle.dispose();
|
|
1113
|
+
this.#routes = [];
|
|
1114
|
+
this.#registrations.clear();
|
|
1115
|
+
}
|
|
1116
|
+
/** The facade a package's HTTP scope exposes as `scope.webhooks`. */
|
|
1117
|
+
scopeFor(namespace) {
|
|
1118
|
+
return {
|
|
1119
|
+
hasPublicOrigin: this.hasPublicOrigin,
|
|
1120
|
+
register: async (spec) => {
|
|
1121
|
+
await this.init();
|
|
1122
|
+
const key = `${namespace}|${spec.name}`;
|
|
1123
|
+
this.#registrations.set(key, {
|
|
1124
|
+
namespace,
|
|
1125
|
+
spec
|
|
1126
|
+
});
|
|
1127
|
+
return {
|
|
1128
|
+
endpointFor: async (ownerKey) => {
|
|
1129
|
+
const row = await this.#store.ensure(namespace, spec.name, ownerKey);
|
|
1130
|
+
return {
|
|
1131
|
+
token: row.token,
|
|
1132
|
+
url: this.urlFor(row.token),
|
|
1133
|
+
createdAt: row.createdAt
|
|
1134
|
+
};
|
|
1135
|
+
},
|
|
1136
|
+
revoke: (ownerKey) => this.#store.revoke(namespace, spec.name, ownerKey),
|
|
1137
|
+
list: async () => (await this.#store.list(namespace, spec.name)).map((row) => ({
|
|
1138
|
+
key: row.ownerKey,
|
|
1139
|
+
token: row.token,
|
|
1140
|
+
url: this.urlFor(row.token),
|
|
1141
|
+
createdAt: row.createdAt
|
|
1142
|
+
}))
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
/** Releases every registration a package made. */
|
|
1148
|
+
disposeScope(namespace) {
|
|
1149
|
+
for (const [key, registration] of this.#registrations) if (registration.namespace === namespace) this.#registrations.delete(key);
|
|
1150
|
+
}
|
|
1151
|
+
/** The URL to hand a provider. Absolute whenever the host knows its own origin: a
|
|
1152
|
+
* relative path is useless to the third party that has to call it. */
|
|
1153
|
+
urlFor(token) {
|
|
1154
|
+
const path = posix.join(this.#path, token);
|
|
1155
|
+
return this.#publicUrl ? `${this.#publicUrl}${path}` : path;
|
|
1156
|
+
}
|
|
1157
|
+
/** Whether `urlFor` can produce something a provider could call. */
|
|
1158
|
+
get hasPublicOrigin() {
|
|
1159
|
+
return this.#publicUrl !== void 0;
|
|
1160
|
+
}
|
|
1161
|
+
async #deliver(req, res, params) {
|
|
1162
|
+
const token = params.token;
|
|
1163
|
+
if (!token) {
|
|
1164
|
+
writeError(res, 404, "Unknown endpoint");
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
try {
|
|
1168
|
+
const row = await this.#store.find(token);
|
|
1169
|
+
if (!row) {
|
|
1170
|
+
writeError(res, 404, "Unknown endpoint");
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
const registrationKey = `${row.namespace}|${row.endpoint}`;
|
|
1174
|
+
const registration = this.#registrations.get(registrationKey);
|
|
1175
|
+
if (!registration) {
|
|
1176
|
+
writeError(res, 503, "Endpoint temporarily unavailable");
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const { spec } = registration;
|
|
1180
|
+
const method = (req.method ?? "POST").toUpperCase();
|
|
1181
|
+
const perEndpoint = spec.policyFor ? await spec.policyFor(row.ownerKey) : {};
|
|
1182
|
+
if (!perEndpoint) {
|
|
1183
|
+
writeError(res, 404, "Unknown endpoint");
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
const policy = {
|
|
1187
|
+
...spec.defaults,
|
|
1188
|
+
...perEndpoint
|
|
1189
|
+
};
|
|
1190
|
+
if (policy.methods && !policy.methods.includes(method)) {
|
|
1191
|
+
writeError(res, 405, "Method not allowed");
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
let raw;
|
|
1195
|
+
try {
|
|
1196
|
+
raw = await readBody(req, policy.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
if (error instanceof BodyTooLargeError) {
|
|
1199
|
+
res.setHeader("connection", "close");
|
|
1200
|
+
res.once("finish", () => req.destroy());
|
|
1201
|
+
writeError(res, 413, "Payload too large");
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
throw error;
|
|
1205
|
+
}
|
|
1206
|
+
const verification = policy.verify;
|
|
1207
|
+
if (verification) {
|
|
1208
|
+
const result = verifyWebhook({
|
|
1209
|
+
verification,
|
|
1210
|
+
headers: headerRecord(req),
|
|
1211
|
+
raw
|
|
1212
|
+
});
|
|
1213
|
+
if (!result.ok) {
|
|
1214
|
+
logger$1.warn("Rejected webhook delivery for @package/@endpoint: @reason", row.namespace, row.endpoint, result.reason);
|
|
1215
|
+
writeError(res, 401, "Signature verification failed");
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
1220
|
+
const queryParams = Object.fromEntries(url.searchParams);
|
|
1221
|
+
const signatureHeader = verification ? schemeHeader(verification) : void 0;
|
|
1222
|
+
const headers = headerRecord(req);
|
|
1223
|
+
const body = parseWebhookBody(raw, headers["content-type"]);
|
|
1224
|
+
const challenge = challengeValue(policy.challengeField, queryParams, headers, body);
|
|
1225
|
+
if (challenge !== void 0) {
|
|
1226
|
+
res.statusCode = 200;
|
|
1227
|
+
res.setHeader("content-type", "text/plain");
|
|
1228
|
+
res.end(challenge);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
if (policy.dedupe) {
|
|
1232
|
+
const key = dedupeKey(policy.dedupe.field, queryParams, headers, body);
|
|
1233
|
+
if (key !== void 0 && await this.#store.seen(token, key, policy.dedupe.ttlSeconds ?? DEFAULT_DEDUPE_TTL_SECONDS)) {
|
|
1234
|
+
res.statusCode = 200;
|
|
1235
|
+
res.end();
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const reply = await spec.onRequest({
|
|
1240
|
+
key: row.ownerKey,
|
|
1241
|
+
method,
|
|
1242
|
+
path: url.pathname,
|
|
1243
|
+
queryParams,
|
|
1244
|
+
headers: redactHeaders(headers, signatureHeader),
|
|
1245
|
+
raw,
|
|
1246
|
+
body
|
|
1247
|
+
});
|
|
1248
|
+
res.statusCode = reply.status;
|
|
1249
|
+
if (reply.body !== void 0 && method !== "HEAD") {
|
|
1250
|
+
res.setHeader("content-type", reply.contentType ?? "text/plain");
|
|
1251
|
+
res.end(reply.body);
|
|
1252
|
+
} else res.end();
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
logger$1.error("Webhook delivery failed for @method", req.method, error);
|
|
1255
|
+
writeError(res, 500, "Internal server error");
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
function headerRecord(req) {
|
|
1260
|
+
const headers = {};
|
|
1261
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
1262
|
+
if (value === void 0) continue;
|
|
1263
|
+
headers[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
|
|
1264
|
+
}
|
|
1265
|
+
return headers;
|
|
1266
|
+
}
|
|
1267
|
+
function scalar(value) {
|
|
1268
|
+
if (typeof value === "string" && value !== "") return value;
|
|
1269
|
+
if (typeof value === "number") return String(value);
|
|
1270
|
+
}
|
|
1271
|
+
/** Walks a dot-separated path into the parsed body. */
|
|
1272
|
+
function bodyPath(path, body) {
|
|
1273
|
+
let current = body;
|
|
1274
|
+
for (const segment of path.split(".")) {
|
|
1275
|
+
if (!current || typeof current !== "object") return void 0;
|
|
1276
|
+
current = current[segment];
|
|
1277
|
+
}
|
|
1278
|
+
return scalar(current);
|
|
1279
|
+
}
|
|
1280
|
+
/** Resolves where a provider put a value. A bare string means query first, then a
|
|
1281
|
+
* top-level body field; `{ header }` and `{ body }` reach what that cannot — GitHub's delivery id is a header, Stripe's event id is nested. */
|
|
1282
|
+
function fieldValue(field, queryParams, headers, body) {
|
|
1283
|
+
if (typeof field === "object") return "header" in field ? scalar(headers[field.header.toLowerCase()]) : bodyPath(field.body, body);
|
|
1284
|
+
const fromQuery = scalar(queryParams[field]);
|
|
1285
|
+
if (fromQuery !== void 0) return fromQuery;
|
|
1286
|
+
return body && typeof body === "object" ? scalar(body[field]) : void 0;
|
|
1287
|
+
}
|
|
1288
|
+
function challengeValue(field, queryParams, headers, body) {
|
|
1289
|
+
return field === void 0 ? void 0 : fieldValue(field, queryParams, headers, body);
|
|
1290
|
+
}
|
|
1291
|
+
function dedupeKey(field, queryParams, headers, body) {
|
|
1292
|
+
return fieldValue(field, queryParams, headers, body);
|
|
1293
|
+
}
|
|
1294
|
+
//#endregion
|
|
1295
|
+
//#region src/http/webhook-store.ts
|
|
1296
|
+
const TOKEN_BYTES = 16;
|
|
1297
|
+
const TOKEN_PATTERN = /^[0-9a-f]{32}$/;
|
|
1298
|
+
/** Opaque and unguessable: the URL is a bearer capability held by a third party. */
|
|
1299
|
+
function newWebhookToken() {
|
|
1300
|
+
return randomBytes(TOKEN_BYTES).toString("hex");
|
|
1301
|
+
}
|
|
1302
|
+
const NAMESPACE = "reactor_webhooks";
|
|
1303
|
+
/** Relational implementation, over the reactor's own database. */
|
|
1304
|
+
var RelationalWebhookStore = class {
|
|
1305
|
+
#db;
|
|
1306
|
+
constructor(relationalDb) {
|
|
1307
|
+
this.relationalDb = relationalDb;
|
|
1308
|
+
}
|
|
1309
|
+
async init() {
|
|
1310
|
+
if (this.#db) return;
|
|
1311
|
+
const db = await this.relationalDb.createNamespace(NAMESPACE);
|
|
1312
|
+
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();
|
|
1313
|
+
await db.schema.createIndex("webhook_endpoints_owner").ifNotExists().on("webhook_endpoints").columns([
|
|
1314
|
+
"namespace",
|
|
1315
|
+
"endpoint",
|
|
1316
|
+
"owner_key"
|
|
1317
|
+
]).unique().execute();
|
|
1318
|
+
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();
|
|
1319
|
+
await db.schema.createIndex("webhook_deliveries_key").ifNotExists().on("webhook_deliveries").columns(["token", "dedupe_key"]).unique().execute();
|
|
1320
|
+
await db.schema.createIndex("webhook_deliveries_seen_at").ifNotExists().on("webhook_deliveries").columns(["seen_at"]).execute();
|
|
1321
|
+
this.#db = db;
|
|
1322
|
+
}
|
|
1323
|
+
get #handle() {
|
|
1324
|
+
if (!this.#db) throw new Error("Webhook store used before init()");
|
|
1325
|
+
return this.#db;
|
|
1326
|
+
}
|
|
1327
|
+
async ensure(namespace, endpoint, ownerKey) {
|
|
1328
|
+
const existing = await this.#handle.selectFrom("webhook_endpoints").selectAll().where("namespace", "=", namespace).where("endpoint", "=", endpoint).where("owner_key", "=", ownerKey).executeTakeFirst();
|
|
1329
|
+
if (existing) return toRow(existing);
|
|
1330
|
+
const row = {
|
|
1331
|
+
token: newWebhookToken(),
|
|
1332
|
+
namespace,
|
|
1333
|
+
endpoint,
|
|
1334
|
+
owner_key: ownerKey,
|
|
1335
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1336
|
+
};
|
|
1337
|
+
await this.#handle.insertInto("webhook_endpoints").values(row).onConflict((oc) => oc.columns([
|
|
1338
|
+
"namespace",
|
|
1339
|
+
"endpoint",
|
|
1340
|
+
"owner_key"
|
|
1341
|
+
]).doNothing()).execute();
|
|
1342
|
+
const settled = await this.#handle.selectFrom("webhook_endpoints").selectAll().where("namespace", "=", namespace).where("endpoint", "=", endpoint).where("owner_key", "=", ownerKey).executeTakeFirst();
|
|
1343
|
+
return settled ? toRow(settled) : toRow(row);
|
|
1344
|
+
}
|
|
1345
|
+
async find(token) {
|
|
1346
|
+
if (!TOKEN_PATTERN.test(token)) return void 0;
|
|
1347
|
+
const found = await this.#handle.selectFrom("webhook_endpoints").selectAll().where("token", "=", token).executeTakeFirst();
|
|
1348
|
+
return found ? toRow(found) : void 0;
|
|
1349
|
+
}
|
|
1350
|
+
async list(namespace, endpoint) {
|
|
1351
|
+
let query = this.#handle.selectFrom("webhook_endpoints").selectAll().where("namespace", "=", namespace);
|
|
1352
|
+
if (endpoint) query = query.where("endpoint", "=", endpoint);
|
|
1353
|
+
return (await query.execute()).map(toRow);
|
|
1354
|
+
}
|
|
1355
|
+
async revoke(namespace, endpoint, ownerKey) {
|
|
1356
|
+
await this.#handle.deleteFrom("webhook_endpoints").where("namespace", "=", namespace).where("endpoint", "=", endpoint).where("owner_key", "=", ownerKey).execute();
|
|
1357
|
+
}
|
|
1358
|
+
async seen(token, key, ttlSeconds) {
|
|
1359
|
+
const now = Date.now();
|
|
1360
|
+
const cutoff = (/* @__PURE__ */ new Date(now - ttlSeconds * 1e3)).toISOString();
|
|
1361
|
+
await this.#handle.deleteFrom("webhook_deliveries").where("seen_at", "<", cutoff).execute();
|
|
1362
|
+
const claimId = randomBytes(16).toString("hex");
|
|
1363
|
+
await this.#handle.insertInto("webhook_deliveries").values({
|
|
1364
|
+
token,
|
|
1365
|
+
dedupe_key: key,
|
|
1366
|
+
seen_at: new Date(now).toISOString(),
|
|
1367
|
+
claim_id: claimId
|
|
1368
|
+
}).onConflict((oc) => oc.columns(["token", "dedupe_key"]).doNothing()).execute();
|
|
1369
|
+
return (await this.#handle.selectFrom("webhook_deliveries").select("claim_id").where("token", "=", token).where("dedupe_key", "=", key).executeTakeFirst())?.claim_id !== claimId;
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
function toRow(row) {
|
|
1373
|
+
return {
|
|
1374
|
+
token: row.token,
|
|
1375
|
+
namespace: row.namespace,
|
|
1376
|
+
endpoint: row.endpoint,
|
|
1377
|
+
ownerKey: row.owner_key,
|
|
1378
|
+
createdAt: row.created_at
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
/** In-memory store, for hosts with no relational database and for tests. */
|
|
1382
|
+
var MemoryWebhookStore = class {
|
|
1383
|
+
#byToken = /* @__PURE__ */ new Map();
|
|
1384
|
+
#deliveries = /* @__PURE__ */ new Map();
|
|
1385
|
+
init() {
|
|
1386
|
+
return Promise.resolve();
|
|
1387
|
+
}
|
|
1388
|
+
ensure(namespace, endpoint, ownerKey) {
|
|
1389
|
+
for (const row of this.#byToken.values()) if (row.namespace === namespace && row.endpoint === endpoint && row.ownerKey === ownerKey) return Promise.resolve(row);
|
|
1390
|
+
const row = {
|
|
1391
|
+
token: newWebhookToken(),
|
|
1392
|
+
namespace,
|
|
1393
|
+
endpoint,
|
|
1394
|
+
ownerKey,
|
|
1395
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1396
|
+
};
|
|
1397
|
+
this.#byToken.set(row.token, row);
|
|
1398
|
+
return Promise.resolve(row);
|
|
1399
|
+
}
|
|
1400
|
+
find(token) {
|
|
1401
|
+
return Promise.resolve(this.#byToken.get(token));
|
|
1402
|
+
}
|
|
1403
|
+
list(namespace, endpoint) {
|
|
1404
|
+
return Promise.resolve([...this.#byToken.values()].filter((row) => row.namespace === namespace && (endpoint === void 0 || row.endpoint === endpoint)));
|
|
1405
|
+
}
|
|
1406
|
+
revoke(namespace, endpoint, ownerKey) {
|
|
1407
|
+
for (const [token, row] of this.#byToken) if (row.namespace === namespace && row.endpoint === endpoint && row.ownerKey === ownerKey) this.#byToken.delete(token);
|
|
1408
|
+
return Promise.resolve();
|
|
1409
|
+
}
|
|
1410
|
+
seen(token, key, ttlSeconds) {
|
|
1411
|
+
const now = Date.now();
|
|
1412
|
+
for (const [k, at] of this.#deliveries) if (at < now - ttlSeconds * 1e3) this.#deliveries.delete(k);
|
|
1413
|
+
const composite = `${token}|${key}`;
|
|
1414
|
+
if (this.#deliveries.has(composite)) return Promise.resolve(true);
|
|
1415
|
+
this.#deliveries.set(composite, now);
|
|
1416
|
+
return Promise.resolve(false);
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
//#endregion
|
|
332
1420
|
//#region src/utils/create-schema.ts
|
|
333
1421
|
const logger = childLogger(["reactor-api", "create-schema"]);
|
|
334
1422
|
/**
|
|
@@ -3252,7 +4340,9 @@ var GraphQLManager = class {
|
|
|
3252
4340
|
*/
|
|
3253
4341
|
reactorDriveClient;
|
|
3254
4342
|
authorizationService;
|
|
3255
|
-
|
|
4343
|
+
/** Route service in use: the injected one, or one built on first need. */
|
|
4344
|
+
#fallbackRoutes;
|
|
4345
|
+
constructor(path, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, logger, httpAdapter, gatewayAdapter, authService, documentPermissionService, featureFlags = DefaultFeatureFlags, port = 4001, authorizationService, reactorDriveClient, syncServingGate, httpRoutes) {
|
|
3256
4346
|
this.path = path;
|
|
3257
4347
|
this.httpServer = httpServer;
|
|
3258
4348
|
this.wsServer = wsServer;
|
|
@@ -3268,6 +4358,7 @@ var GraphQLManager = class {
|
|
|
3268
4358
|
this.featureFlags = featureFlags;
|
|
3269
4359
|
this.port = port;
|
|
3270
4360
|
this.syncServingGate = syncServingGate;
|
|
4361
|
+
this.httpRoutes = httpRoutes;
|
|
3271
4362
|
if (!authorizationService) throw new Error("GraphQLManager requires an authorizationService");
|
|
3272
4363
|
this.authorizationService = authorizationService;
|
|
3273
4364
|
this.reactorDriveClient = reactorDriveClient;
|
|
@@ -3349,6 +4440,7 @@ var GraphQLManager = class {
|
|
|
3349
4440
|
if (!hasOperationSchemas(documentModel)) continue;
|
|
3350
4441
|
try {
|
|
3351
4442
|
const subgraphInstance = new DocumentModelSubgraph(documentModel, {
|
|
4443
|
+
http: this.scopeForPackage(CORE_PACKAGE_NAME),
|
|
3352
4444
|
relationalDb: this.relationalDb,
|
|
3353
4445
|
analyticsStore: this.analyticsStore,
|
|
3354
4446
|
reactorClient: this.reactorClient,
|
|
@@ -3421,7 +4513,7 @@ var GraphQLManager = class {
|
|
|
3421
4513
|
this.subgraphHandlerCache.delete(subgraphPath);
|
|
3422
4514
|
const routeHandle = this.subgraphRouteHandles.get(subgraphPath);
|
|
3423
4515
|
if (routeHandle !== void 0) {
|
|
3424
|
-
|
|
4516
|
+
routeHandle.dispose();
|
|
3425
4517
|
this.subgraphRouteHandles.delete(subgraphPath);
|
|
3426
4518
|
}
|
|
3427
4519
|
const wsDisposer = this.subgraphWsDisposers.get(subgraphPath);
|
|
@@ -3463,13 +4555,28 @@ var GraphQLManager = class {
|
|
|
3463
4555
|
return this.authorizationService;
|
|
3464
4556
|
}
|
|
3465
4557
|
/**
|
|
4558
|
+
* The HTTP scope a package's subgraphs get. Falls back to a scope over a
|
|
4559
|
+
* throwaway service when route hosting is not configured, so a subgraph can
|
|
4560
|
+
* always rely on `args.http` existing.
|
|
4561
|
+
*/
|
|
4562
|
+
scopeForPackage(packageName) {
|
|
4563
|
+
this.#fallbackRoutes ??= this.httpRoutes ?? new HttpRouteService({
|
|
4564
|
+
httpAdapter: this.httpAdapter,
|
|
4565
|
+
basePath: this.path,
|
|
4566
|
+
authService: this.authService
|
|
4567
|
+
});
|
|
4568
|
+
return this.#fallbackRoutes.scopeForOrNull(packageName);
|
|
4569
|
+
}
|
|
4570
|
+
/**
|
|
3466
4571
|
* Register a subgraph class. `packageName` labels the resulting instance
|
|
3467
4572
|
* with its contributing package so the instance can be torn down when the
|
|
3468
4573
|
* package is removed or drops the subgraph (see unregisterPackage and
|
|
3469
|
-
* prunePackageSubgraphs)
|
|
4574
|
+
* prunePackageSubgraphs), and decides the namespace its REST routes and
|
|
4575
|
+
* webhooks live under.
|
|
3470
4576
|
*/
|
|
3471
4577
|
async registerSubgraph(subgraph, supergraph = "", core = false, packageName) {
|
|
3472
4578
|
const subgraphInstance = new subgraph({
|
|
4579
|
+
http: this.scopeForPackage(packageName ?? "@powerhousedao/reactor-api"),
|
|
3473
4580
|
relationalDb: this.relationalDb,
|
|
3474
4581
|
analyticsStore: this.analyticsStore,
|
|
3475
4582
|
reactorClient: this.reactorClient,
|
|
@@ -3490,12 +4597,25 @@ var GraphQLManager = class {
|
|
|
3490
4597
|
* package has no registered subgraphs.
|
|
3491
4598
|
*/
|
|
3492
4599
|
async unregisterPackage(packageName) {
|
|
4600
|
+
this.#disposeHttpScope(packageName);
|
|
3493
4601
|
const byName = this.packageSubgraphs.get(packageName);
|
|
3494
4602
|
if (!byName || byName.size === 0) return;
|
|
3495
4603
|
for (const instance of Array.from(byName.values())) await this.#removeSubgraphInstance(instance);
|
|
3496
4604
|
await this.updateRouter();
|
|
3497
4605
|
}
|
|
3498
4606
|
/**
|
|
4607
|
+
* Releases the package's HTTP scope: every route it mounted, and its webhook
|
|
4608
|
+
* registrations. Idempotent, and silent for a package that never had one.
|
|
4609
|
+
*/
|
|
4610
|
+
#disposeHttpScope(packageName) {
|
|
4611
|
+
const routes = this.#fallbackRoutes ?? this.httpRoutes;
|
|
4612
|
+
try {
|
|
4613
|
+
routes?.disposeScope(packageName);
|
|
4614
|
+
} catch (error) {
|
|
4615
|
+
this.logger.warn(`Failed to release HTTP routes for "${packageName}": @error`, error);
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
/**
|
|
3499
4619
|
* Remove this package's registered subgraphs whose names are not in
|
|
3500
4620
|
* `keepNames` — the package is still loaded but dropped some of its
|
|
3501
4621
|
* subgraphs. Refreshes the router if anything was removed.
|
|
@@ -3730,7 +4850,7 @@ var GraphQLManager = class {
|
|
|
3730
4850
|
const modules = Array.from(allSubgraphs.values()).filter((subgraph) => subgraph.hasSubscriptions).map((subgraph) => this.#buildSubgraphSchemaModule(subgraph));
|
|
3731
4851
|
if (modules.length === 0) {
|
|
3732
4852
|
if (this.sseRouteHandle !== void 0) {
|
|
3733
|
-
this.
|
|
4853
|
+
this.sseRouteHandle.dispose();
|
|
3734
4854
|
this.sseRouteHandle = void 0;
|
|
3735
4855
|
}
|
|
3736
4856
|
return;
|
|
@@ -3758,7 +4878,7 @@ var GraphQLManager = class {
|
|
|
3758
4878
|
contextFactory: this.#makeContextFactory()
|
|
3759
4879
|
});
|
|
3760
4880
|
const handler = this.#composeFetchMiddleware(rawHandler);
|
|
3761
|
-
if (this.sseRouteHandle !== void 0) this.
|
|
4881
|
+
if (this.sseRouteHandle !== void 0) this.sseRouteHandle.dispose();
|
|
3762
4882
|
this.sseRouteHandle = this.httpAdapter.mount(ssePath, handler, { exact: true });
|
|
3763
4883
|
}
|
|
3764
4884
|
/**
|
|
@@ -4902,10 +6022,10 @@ const ADMIN_USERS = getAdminUsers();
|
|
|
4902
6022
|
//#endregion
|
|
4903
6023
|
//#region src/graphql/system/version.ts
|
|
4904
6024
|
function getVersion() {
|
|
4905
|
-
return "6.2.
|
|
6025
|
+
return "6.2.3-dev.0";
|
|
4906
6026
|
}
|
|
4907
6027
|
function getGitHash() {
|
|
4908
|
-
return "
|
|
6028
|
+
return "cb65fac68a4a59f9dd4b9d4e77270a395dfb3386";
|
|
4909
6029
|
}
|
|
4910
6030
|
function getGitUrl() {
|
|
4911
6031
|
return buildTreeUrl(getGitHash());
|
|
@@ -6557,8 +7677,8 @@ function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
|
|
|
6557
7677
|
/**
|
|
6558
7678
|
* Sets up the subgraph manager and registers subgraphs
|
|
6559
7679
|
*/
|
|
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);
|
|
7680
|
+
async function setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes) {
|
|
7681
|
+
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
7682
|
await graphqlManager.init(subgraphs.core, authFetchMiddleware, requireAuthFetchMiddleware);
|
|
6563
7683
|
for (const [packageName, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql", false, packageName);
|
|
6564
7684
|
await graphqlManager.updateRouter();
|
|
@@ -6567,7 +7687,7 @@ async function setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuth
|
|
|
6567
7687
|
/**
|
|
6568
7688
|
* Sets up event listeners for package manager changes
|
|
6569
7689
|
*/
|
|
6570
|
-
function setupEventListeners(pkgManager, graphqlManager, reactorProcessorManager,
|
|
7690
|
+
function setupEventListeners(pkgManager, graphqlManager, reactorProcessorManager, moduleFor, documentModelRegistry) {
|
|
6571
7691
|
pkgManager.onDocumentModelsChange((packagedModels) => {
|
|
6572
7692
|
if (documentModelRegistry) {
|
|
6573
7693
|
const newModules = getUniqueDocumentModels(Object.values(packagedModels).flat());
|
|
@@ -6611,7 +7731,7 @@ function setupEventListeners(pkgManager, graphqlManager, reactorProcessorManager
|
|
|
6611
7731
|
knownProcessorPackages = new Set(processors.keys());
|
|
6612
7732
|
for (const [packageName, fns] of processors) {
|
|
6613
7733
|
await reactorProcessorManager.unregisterFactory(packageName);
|
|
6614
|
-
const validBuilders = fns.map((fn) => fn(
|
|
7734
|
+
const validBuilders = fns.map((fn) => fn(moduleFor(packageName))).filter((factory) => typeof factory === "function");
|
|
6615
7735
|
if (!validBuilders.length) continue;
|
|
6616
7736
|
await reactorProcessorManager.registerFactory(packageName, async (driveHeader) => (await Promise.all(validBuilders.map(async (driveFactory) => {
|
|
6617
7737
|
try {
|
|
@@ -6790,9 +7910,25 @@ async function _setupCommonInfrastructure(options) {
|
|
|
6790
7910
|
configFile: options.configFile,
|
|
6791
7911
|
packages: options.packages ?? []
|
|
6792
7912
|
});
|
|
7913
|
+
const publicUrl = resolvePublicOrigin(port);
|
|
7914
|
+
const webhooks = new WebhookService({
|
|
7915
|
+
store: new RelationalWebhookStore(relationalDb),
|
|
7916
|
+
basePath: config.basePath,
|
|
7917
|
+
publicUrl
|
|
7918
|
+
});
|
|
7919
|
+
const httpRoutes = new HttpRouteService({
|
|
7920
|
+
httpAdapter,
|
|
7921
|
+
basePath: config.basePath,
|
|
7922
|
+
authService,
|
|
7923
|
+
webhooks,
|
|
7924
|
+
publicUrl,
|
|
7925
|
+
trustProxy: true
|
|
7926
|
+
});
|
|
7927
|
+
webhooks.attach(httpRoutes.hostScope(CORE_PACKAGE_NAME, path.posix.join("/", config.basePath ?? "/", WEBHOOK_SEGMENT)));
|
|
6793
7928
|
return {
|
|
6794
7929
|
port,
|
|
6795
7930
|
httpAdapter,
|
|
7931
|
+
httpRoutes,
|
|
6796
7932
|
authFetchMiddleware,
|
|
6797
7933
|
requireAuthFetchMiddleware,
|
|
6798
7934
|
authService,
|
|
@@ -6810,8 +7946,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
6810
7946
|
/**
|
|
6811
7947
|
* Private helper function containing common setup logic for API initialization
|
|
6812
7948
|
*/
|
|
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
|
|
7949
|
+
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) {
|
|
7950
|
+
const hostModuleBase = {
|
|
6815
7951
|
...createReactorHostModuleBase({
|
|
6816
7952
|
client: reactorClient,
|
|
6817
7953
|
readModels,
|
|
@@ -6822,6 +7958,10 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6822
7958
|
}),
|
|
6823
7959
|
attachments: createAttachmentClient(attachments.service)
|
|
6824
7960
|
};
|
|
7961
|
+
const moduleFor = (packageName) => ({
|
|
7962
|
+
...hostModuleBase,
|
|
7963
|
+
http: httpRoutes?.scopeForOrNull(packageName)
|
|
7964
|
+
});
|
|
6825
7965
|
const mcpServerEnabled = options.mcp ?? true;
|
|
6826
7966
|
const logger = options.logger ?? defaultLogger;
|
|
6827
7967
|
const configuredProcessorEntries = Object.entries(options.processors ?? {});
|
|
@@ -6829,7 +7969,7 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6829
7969
|
for (const [packageName, fns] of processorEntries) {
|
|
6830
7970
|
const validFactories = (await Promise.allSettled(fns.map(async (fn) => {
|
|
6831
7971
|
try {
|
|
6832
|
-
return fn(
|
|
7972
|
+
return fn(moduleFor(packageName));
|
|
6833
7973
|
} catch (e) {
|
|
6834
7974
|
logger.error(`Error initializing processor factory for package ${packageName}:`, e);
|
|
6835
7975
|
return null;
|
|
@@ -6858,8 +7998,8 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6858
7998
|
const graphqlManager = await setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, reactorClient, relationalDb, analyticsStore, syncManager, {
|
|
6859
7999
|
extended: subgraphs,
|
|
6860
8000
|
core: coreSubgraphs
|
|
6861
|
-
}, logger.child(["graphql-manager"]), authorizationService, authService, documentPermissionService, options.enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate);
|
|
6862
|
-
setupEventListeners(packages, graphqlManager, reactorProcessorManager,
|
|
8001
|
+
}, logger.child(["graphql-manager"]), authorizationService, authService, documentPermissionService, options.enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes);
|
|
8002
|
+
setupEventListeners(packages, graphqlManager, reactorProcessorManager, moduleFor, documentModelRegistry);
|
|
6863
8003
|
if (mcpServerEnabled) {
|
|
6864
8004
|
await setupMcpServer({
|
|
6865
8005
|
client: reactorClient,
|
|
@@ -6868,21 +8008,24 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6868
8008
|
}, httpAdapter);
|
|
6869
8009
|
logger.info(`MCP server available at http://localhost:${port}/mcp`);
|
|
6870
8010
|
}
|
|
8011
|
+
const dispose = buildApiDispose({
|
|
8012
|
+
graphqlManager,
|
|
8013
|
+
httpRoutes,
|
|
8014
|
+
httpServer,
|
|
8015
|
+
wsServer,
|
|
8016
|
+
dbClosers,
|
|
8017
|
+
logger
|
|
8018
|
+
});
|
|
6871
8019
|
return {
|
|
6872
8020
|
httpAdapter,
|
|
8021
|
+
httpRoutes: httpRoutes ?? new HttpRouteService({ httpAdapter }),
|
|
6873
8022
|
graphqlManager,
|
|
6874
8023
|
packages,
|
|
6875
8024
|
attachments,
|
|
6876
8025
|
attachmentReferenceIndex,
|
|
6877
8026
|
attachmentAccess,
|
|
6878
8027
|
authService,
|
|
6879
|
-
dispose
|
|
6880
|
-
graphqlManager,
|
|
6881
|
-
httpServer,
|
|
6882
|
-
wsServer,
|
|
6883
|
-
dbClosers,
|
|
6884
|
-
logger
|
|
6885
|
-
})
|
|
8028
|
+
dispose
|
|
6886
8029
|
};
|
|
6887
8030
|
}
|
|
6888
8031
|
/**
|
|
@@ -6893,11 +8036,16 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
6893
8036
|
* since this runs on the way to process exit.
|
|
6894
8037
|
*/
|
|
6895
8038
|
function buildApiDispose(args) {
|
|
6896
|
-
const { graphqlManager, httpServer, wsServer, dbClosers, logger } = args;
|
|
8039
|
+
const { graphqlManager, httpRoutes, httpServer, wsServer, dbClosers, logger } = args;
|
|
6897
8040
|
let disposed = false;
|
|
6898
8041
|
return async () => {
|
|
6899
8042
|
if (disposed) return;
|
|
6900
8043
|
disposed = true;
|
|
8044
|
+
try {
|
|
8045
|
+
httpRoutes?.disposeAll();
|
|
8046
|
+
} catch (error) {
|
|
8047
|
+
logger.error("API dispose: releasing HTTP routes failed: @error", error);
|
|
8048
|
+
}
|
|
6901
8049
|
try {
|
|
6902
8050
|
await graphqlManager.shutdown();
|
|
6903
8051
|
} catch (error) {
|
|
@@ -6923,7 +8071,7 @@ function buildApiDispose(args) {
|
|
|
6923
8071
|
};
|
|
6924
8072
|
}
|
|
6925
8073
|
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);
|
|
8074
|
+
const { port, httpAdapter, httpRoutes, authFetchMiddleware, requireAuthFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness } = await _setupCommonInfrastructure(options);
|
|
6927
8075
|
const { documentModels, upgradeManifests, processors, subgraphs } = await packages.init();
|
|
6928
8076
|
const { module: reactorClientModule, reactorDriveClient, attachmentReferenceProjection = {
|
|
6929
8077
|
status: "unavailable",
|
|
@@ -6940,7 +8088,7 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
6940
8088
|
const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
|
|
6941
8089
|
if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from InProcessReactorClientModule");
|
|
6942
8090
|
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)),
|
|
8091
|
+
...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
8092
|
client: reactorClient,
|
|
6945
8093
|
syncManager,
|
|
6946
8094
|
documentModelRegistry,
|
|
@@ -6949,6 +8097,26 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
6949
8097
|
packageManager: packages
|
|
6950
8098
|
};
|
|
6951
8099
|
}
|
|
8100
|
+
/**
|
|
8101
|
+
* The origin to advertise in a webhook URL.
|
|
8102
|
+
*
|
|
8103
|
+
* A provider is a third party: it has to be given something it can resolve, so
|
|
8104
|
+
* a relative path is not an option. The platform variables come first, then a
|
|
8105
|
+
* bare deploy domain, and finally the local origin — which is right for `ph
|
|
8106
|
+
* dev` behind a tunnel and, at worst, obviously wrong rather than silently
|
|
8107
|
+
* unusable.
|
|
8108
|
+
*/
|
|
8109
|
+
function resolvePublicOrigin(port) {
|
|
8110
|
+
const explicit = process.env.PUBLIC_URL ?? process.env.RENDER_EXTERNAL_URL;
|
|
8111
|
+
if (explicit) return withScheme(explicit);
|
|
8112
|
+
const domain = process.env.HEROKU_APP_DEFAULT_DOMAIN_NAME;
|
|
8113
|
+
if (domain) return withScheme(domain);
|
|
8114
|
+
return `http://localhost:${port}`;
|
|
8115
|
+
}
|
|
8116
|
+
function withScheme(origin) {
|
|
8117
|
+
const trimmed = origin.replace(/\/+$/, "");
|
|
8118
|
+
return /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
8119
|
+
}
|
|
6952
8120
|
//#endregion
|
|
6953
8121
|
//#region src/services/package-storage.ts
|
|
6954
8122
|
var InMemoryPackageStorage = class {
|
|
@@ -7053,7 +8221,7 @@ var PackageManagementService = class {
|
|
|
7053
8221
|
}
|
|
7054
8222
|
};
|
|
7055
8223
|
//#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 };
|
|
8224
|
+
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
8225
|
|
|
7058
8226
|
//# sourceMappingURL=index.mjs.map
|
|
7059
|
-
//# debugId=
|
|
8227
|
+
//# debugId=ef62c13d-c8af-5733-beae-202ad8381942
|