@powerhousedao/reactor-api 6.0.2-staging.5 → 6.0.2-staging.7

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/index.mjs CHANGED
@@ -1,4 +1,6 @@
1
- import { a as isSubgraphClass, c as loadDocumentModels, d as BaseSubgraph, i as buildGraphqlOperations, l as loadProcessors, n as buildGraphQlDriveDocument, o as debounce, r as buildGraphqlOperation, t as buildGraphQlDocument, u as loadSubgraphs } from "./utils-CVrD_vPF.mjs";
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]="81df4612-dfa1-5785-acec-bca9f4770f2d")}catch(e){}}();
3
+ import { a as isSubgraphClass, c as loadDocumentModels, d as BaseSubgraph, i as buildGraphqlOperations, l as loadProcessors, n as buildGraphQlDriveDocument, o as debounce, r as buildGraphqlOperation, t as buildGraphQlDocument, u as loadSubgraphs } from "./utils-CHCRSWig.mjs";
2
4
  import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
3
5
  import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
4
6
  import { gql } from "graphql-tag";
@@ -25,19 +27,19 @@ import { match } from "path-to-regexp";
25
27
  import fastifyCors from "@fastify/cors";
26
28
  import fastifyFormbody from "@fastify/formbody";
27
29
  import fastifyMiddie from "@fastify/middie";
28
- import { execSync } from "node:child_process";
29
30
  import { verifyAuthBearerToken } from "@renown/sdk";
30
31
  import { buildSubgraphSchema } from "@apollo/subgraph";
31
32
  import { typeDefs } from "@powerhousedao/document-engineering/graphql";
32
33
  import { camelCase, kebabCase, pascalCase } from "change-case";
33
34
  import { GraphQLJSONObject } from "graphql-type-json";
34
35
  import { setName } from "@powerhousedao/shared/document-model";
35
- import { PropagationMode as PropagationMode$1, consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, sortEnvelopesByFirstOperationTimestamp, trimMailboxFromAckOrdinal } from "@powerhousedao/reactor";
36
+ import { PropagationMode as PropagationMode$1, consolidateSyncOperations, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl } from "@powerhousedao/reactor";
36
37
  import * as z$1 from "zod";
37
38
  import { z } from "zod";
38
39
  import { createHandler } from "graphql-sse/lib/use/fetch";
39
40
  import { PubSub, withFilter } from "graphql-subscriptions";
40
41
  import dotenv from "dotenv";
42
+ import { buildTreeUrl } from "@powerhousedao/shared";
41
43
  import { getConfig } from "@powerhousedao/config/node";
42
44
  import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
43
45
  import EventEmitter from "node:events";
@@ -49,15 +51,6 @@ import { tmpdir } from "node:os";
49
51
  import { WebSocketServer } from "ws";
50
52
  import { createRelationalDb } from "@powerhousedao/shared/processors";
51
53
  import { Kysely, Migrator, sql } from "kysely";
52
- import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
53
- import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
54
- import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
55
- import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
56
- import { Resource } from "@opentelemetry/resources";
57
- import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
58
- import { NodeSDK } from "@opentelemetry/sdk-node";
59
- import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
60
- import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
61
54
  import { PGlite } from "@electric-sql/pglite";
62
55
  import knex from "knex";
63
56
  import ClientPgLite from "knex-pglite";
@@ -563,6 +556,7 @@ var ApolloGatewayAdapter = class {
563
556
  schema,
564
557
  logger: this.#logger,
565
558
  introspection: true,
559
+ stopOnTerminationSignals: false,
566
560
  plugins: [ApolloServerPluginInlineTraceDisabled(), ApolloServerPluginLandingPageLocalDefault()]
567
561
  });
568
562
  await server.start();
@@ -582,6 +576,7 @@ var ApolloGatewayAdapter = class {
582
576
  }),
583
577
  logger: this.#logger,
584
578
  introspection: true,
579
+ stopOnTerminationSignals: false,
585
580
  plugins: [
586
581
  ApolloServerPluginDrainHttpServer({ httpServer }),
587
582
  ApolloServerPluginInlineTraceDisabled(),
@@ -926,7 +921,7 @@ function parseBodyLimit(limit) {
926
921
  var FastifyHttpAdapter = class {
927
922
  #fetchRoutes = [];
928
923
  #getRoutes = /* @__PURE__ */ new Map();
929
- #nodeRoutes = /* @__PURE__ */ new Map();
924
+ #nodeRoutes = [];
930
925
  #setupOps = [];
931
926
  #instance;
932
927
  get handle() {
@@ -953,8 +948,11 @@ var FastifyHttpAdapter = class {
953
948
  });
954
949
  }
955
950
  mountNodeRoute(method, path, handler) {
956
- if (!this.#nodeRoutes.has(path)) this.#nodeRoutes.set(path, /* @__PURE__ */ new Map());
957
- this.#nodeRoutes.get(path).set(method, handler);
951
+ this.#nodeRoutes.push({
952
+ method,
953
+ matcher: match(normalizePath(path)),
954
+ handler
955
+ });
958
956
  }
959
957
  mountRawMiddleware(middleware) {
960
958
  if (this.#instance) this.#instance.use(middleware);
@@ -1024,14 +1022,14 @@ var FastifyHttpAdapter = class {
1024
1022
  #dispatch(req, reply) {
1025
1023
  const pathname = new URL(req.url, "http://localhost").pathname;
1026
1024
  const method = req.method.toUpperCase();
1027
- const nodeHandlers = this.#nodeRoutes.get(pathname);
1028
- if (nodeHandlers) {
1029
- const handler = nodeHandlers.get(method);
1030
- if (handler) {
1031
- reply.hijack();
1032
- handler(req.raw, reply.raw, req.body);
1033
- return;
1034
- }
1025
+ for (const entry of this.#nodeRoutes) {
1026
+ if (entry.method !== method) continue;
1027
+ const result = entry.matcher(pathname);
1028
+ if (!result) continue;
1029
+ req.raw.params = result.params;
1030
+ reply.hijack();
1031
+ entry.handler(req.raw, reply.raw, req.body);
1032
+ return;
1035
1033
  }
1036
1034
  if (method === "GET") {
1037
1035
  for (const entry of this.#getRoutes.values()) if (entry.matcher(pathname)) return this.#serveGetEntry(entry, req, reply);
@@ -2719,9 +2717,13 @@ function matchesJobFilter(payload, args) {
2719
2717
  return payload.jobId === args.jobId;
2720
2718
  }
2721
2719
  //#endregion
2722
- //#region src/graphql/reactor/resolvers.ts
2720
+ //#region src/graphql/reactor/constants.ts
2721
+ /**
2722
+ * Document-type sentinel for drive documents. Drives are the unit
2723
+ * the LB shards on (via the `Drive-Id` request header) and the unit
2724
+ * the drive-ownership cache tracks on each switchboard instance.
2725
+ */
2723
2726
  const DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive";
2724
- const POLL_SYNC_ENVELOPES_MAX_LIMIT = 100;
2725
2727
  async function documentModels(reactorClient, args) {
2726
2728
  const namespace = fromInputMaybe(args.namespace);
2727
2729
  let paging;
@@ -2917,7 +2919,7 @@ async function createDocument(reactorClient, args) {
2917
2919
  const parentIdentifier = fromInputMaybe(args.parentIdentifier);
2918
2920
  let result;
2919
2921
  try {
2920
- if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === DRIVE_DOCUMENT_TYPE) result = await reactorClient.drives.addFile(parentIdentifier, document);
2922
+ if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === "powerhouse/document-drive") result = await reactorClient.drives.addFile(parentIdentifier, document);
2921
2923
  else result = await reactorClient.create(document, parentIdentifier);
2922
2924
  else result = await reactorClient.create(document);
2923
2925
  } catch (error) {
@@ -2934,7 +2936,7 @@ async function createEmptyDocument(reactorClient, args) {
2934
2936
  const name = fromInputMaybe(args.name);
2935
2937
  let result;
2936
2938
  try {
2937
- if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === DRIVE_DOCUMENT_TYPE) {
2939
+ if (parentIdentifier) if ((await reactorClient.get(parentIdentifier)).header.documentType === "powerhouse/document-drive") {
2938
2940
  const document = (await reactorClient.getDocumentModelModule(args.documentType)).utils.createDocument();
2939
2941
  if (name) document.header.name = name;
2940
2942
  result = await reactorClient.drives.addFile(parentIdentifier, document);
@@ -2981,7 +2983,7 @@ async function createDocumentWithInitialState(reactorClient, args) {
2981
2983
  } catch (error) {
2982
2984
  throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
2983
2985
  }
2984
- if (parent.header.documentType === DRIVE_DOCUMENT_TYPE) try {
2986
+ if (parent.header.documentType === "powerhouse/document-drive") try {
2985
2987
  result = await reactorClient.drives.addFile(parentIdentifier, document);
2986
2988
  } catch (error) {
2987
2989
  throw new GraphQLError(`Failed to create document in drive: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -3172,14 +3174,22 @@ function pollSyncEnvelopes(syncManager, args) {
3172
3174
  scopes: syncOp.scopes,
3173
3175
  operationCount: syncOp.operations.length
3174
3176
  }));
3175
- if (args.outboxAck > 0) trimMailboxFromAckOrdinal(remote.channel.outbox, args.outboxAck);
3176
- let operations = remote.channel.outbox.items;
3177
- operations = operations.filter((syncOp) => {
3178
- let maxOrdinal = 0;
3179
- for (const op of syncOp.operations) maxOrdinal = Math.max(maxOrdinal, op.context.ordinal);
3180
- if (maxOrdinal > args.outboxLatest) return true;
3181
- return false;
3182
- });
3177
+ if (args.outboxAck > 0) {
3178
+ const ackOrdinal = args.outboxAck;
3179
+ const outbox = remote.channel.outbox;
3180
+ const toRemove = [];
3181
+ for (const syncOp of outbox.items) {
3182
+ if (!((syncOp.emittedCount ?? 0) >= syncOp.operations.length)) continue;
3183
+ let maxOrdinal = 0;
3184
+ for (const op of syncOp.operations) if (op.context.ordinal > maxOrdinal) maxOrdinal = op.context.ordinal;
3185
+ if (maxOrdinal <= ackOrdinal) toRemove.push(syncOp);
3186
+ }
3187
+ if (toRemove.length > 0) {
3188
+ for (const syncOp of toRemove) syncOp.executed();
3189
+ outbox.remove(...toRemove);
3190
+ }
3191
+ }
3192
+ const operations = remote.channel.outbox.items;
3183
3193
  if (operations.length === 0) return {
3184
3194
  envelopes: [],
3185
3195
  ackOrdinal: remote.channel.inbox.ackOrdinal,
@@ -3189,29 +3199,66 @@ function pollSyncEnvelopes(syncManager, args) {
3189
3199
  const sorted = [...operations].sort((a, b) => {
3190
3200
  return (a.operations[0]?.context.ordinal ?? 0) - (b.operations[0]?.context.ordinal ?? 0);
3191
3201
  });
3192
- const hasMore = sorted.length > POLL_SYNC_ENVELOPES_MAX_LIMIT;
3193
- const pageOperations = sorted.slice(0, POLL_SYNC_ENVELOPES_MAX_LIMIT);
3202
+ const envelopes = [];
3203
+ let pageOps = 0;
3204
+ let hasMore = false;
3194
3205
  let maxOrdinal = args.outboxLatest;
3195
- for (const syncOp of pageOperations) for (const op of syncOp.operations) {
3196
- const opOrdinal = op.context.ordinal;
3197
- if (opOrdinal > maxOrdinal) maxOrdinal = opOrdinal;
3206
+ outer: for (const syncOp of sorted) {
3207
+ if (pageOps >= 100) {
3208
+ hasMore = true;
3209
+ break;
3210
+ }
3211
+ syncOp.deliveredCount ??= 0;
3212
+ syncOp.emittedCount ??= 0;
3213
+ while (syncOp.deliveredCount < syncOp.emittedCount && syncOp.operations[syncOp.deliveredCount].context.ordinal <= args.outboxLatest) syncOp.deliveredCount += 1;
3214
+ const remaining = syncOp.operations.slice(syncOp.deliveredCount);
3215
+ if (remaining.length === 0) continue;
3216
+ let prevPartKey;
3217
+ let partIdx = 0;
3218
+ let i = 0;
3219
+ while (i < remaining.length) {
3220
+ if (pageOps >= 100) {
3221
+ hasMore = true;
3222
+ break outer;
3223
+ }
3224
+ const remainingPage = 100 - pageOps;
3225
+ const chunkSize = Math.max(1, Math.min(25, remainingPage, remaining.length - i));
3226
+ const slice = remaining.slice(i, i + chunkSize);
3227
+ const isOnly = remaining.length <= 25 && remaining.length <= remainingPage;
3228
+ const baseKey = syncOp.jobId || void 0;
3229
+ const partKey = isOnly ? baseKey : baseKey ? `${baseKey}__p${partIdx}` : void 0;
3230
+ const partDeps = partIdx === 0 ? syncOp.jobDependencies.filter(Boolean) : prevPartKey ? [prevPartKey] : [];
3231
+ for (const op of slice) if (op.context.ordinal > maxOrdinal) maxOrdinal = op.context.ordinal;
3232
+ envelopes.push({
3233
+ type: "OPERATIONS",
3234
+ channelMeta: { id: args.channelId },
3235
+ operations: slice.map((op) => ({
3236
+ operation: serializeOperationForGraphQL(op.operation),
3237
+ context: op.context
3238
+ })),
3239
+ cursor: {
3240
+ remoteName: remote.name,
3241
+ cursorOrdinal: 0,
3242
+ lastSyncedAtUtcMs: Date.now().toString()
3243
+ },
3244
+ key: partKey,
3245
+ dependsOn: partDeps.length > 0 ? partDeps : void 0
3246
+ });
3247
+ pageOps += slice.length;
3248
+ i += slice.length;
3249
+ prevPartKey = partKey;
3250
+ partIdx++;
3251
+ const emittedThrough = syncOp.deliveredCount + i;
3252
+ if (emittedThrough > syncOp.emittedCount) syncOp.emittedCount = emittedThrough;
3253
+ if (i < remaining.length && pageOps >= 100) {
3254
+ hasMore = true;
3255
+ break outer;
3256
+ }
3257
+ }
3198
3258
  }
3259
+ for (const envelope of envelopes) if (envelope.cursor) envelope.cursor.cursorOrdinal = maxOrdinal;
3199
3260
  return {
3200
- envelopes: sortEnvelopesByFirstOperationTimestamp(pageOperations.map((syncOp) => ({
3201
- type: "OPERATIONS",
3202
- channelMeta: { id: args.channelId },
3203
- operations: syncOp.operations.map((op) => ({
3204
- operation: serializeOperationForGraphQL(op.operation),
3205
- context: op.context
3206
- })),
3207
- cursor: {
3208
- remoteName: remote.name,
3209
- cursorOrdinal: maxOrdinal,
3210
- lastSyncedAtUtcMs: Date.now().toString()
3211
- },
3212
- key: syncOp.jobId || void 0,
3213
- dependsOn: syncOp.jobDependencies.filter(Boolean).length > 0 ? syncOp.jobDependencies.filter(Boolean) : void 0
3214
- }))),
3261
+ envelopes,
3215
3262
  ackOrdinal: remote.channel.inbox.ackOrdinal,
3216
3263
  deadLetters,
3217
3264
  hasMore
@@ -3226,9 +3273,8 @@ function pollSyncEnvelopes(syncManager, args) {
3226
3273
  * and returns the highest one as `ackOrdinal` in pollSyncEnvelopes.
3227
3274
  */
3228
3275
  function pushSyncEnvelopes(syncManager, args) {
3229
- const sortedEnvelopes = sortEnvelopesByFirstOperationTimestamp(args.envelopes);
3230
3276
  const remoteSyncOps = /* @__PURE__ */ new Map();
3231
- for (const envelope of sortedEnvelopes) {
3277
+ for (const envelope of args.envelopes) {
3232
3278
  let remote;
3233
3279
  try {
3234
3280
  remote = syncManager.getById(envelope.channelMeta.id);
@@ -3476,6 +3522,110 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
3476
3522
  }
3477
3523
  };
3478
3524
  //#endregion
3525
+ //#region src/graphql/gateway/drive-middleware.ts
3526
+ const DRIVE_ID_HEADER = "drive-id";
3527
+ /**
3528
+ * Operations that legitimately run before the target drive exists in the
3529
+ * cache. Drive creation is the obvious case (a brand-new drive cannot be
3530
+ * in the ownership set yet); other create-shaped operations that may
3531
+ * synthesize a drive must be added here too.
3532
+ */
3533
+ const CACHE_BYPASS_OPERATIONS = new Set(["createDocument", "createEmptyDocument"]);
3534
+ const driveIdMap = /* @__PURE__ */ new WeakMap();
3535
+ /** Internal — only `graphql-manager.ts` should call this. */
3536
+ function getRequestDriveId(request) {
3537
+ return driveIdMap.get(request);
3538
+ }
3539
+ /**
3540
+ * Returns a fetch middleware that validates the `Drive-Id` header against
3541
+ * the in-memory ownership cache. Layout:
3542
+ *
3543
+ * - No header → pass through. The LB has already round-robined; nothing
3544
+ * to validate here.
3545
+ * - Header present and drive in cache → record on the request map (for
3546
+ * the context factory to read into `context.driveId`) and pass through.
3547
+ * - Header present, drive missing, but the operation is `createDocument`
3548
+ * or `createEmptyDocument` → pass through. The drive may be in the
3549
+ * process of being created.
3550
+ * - Otherwise → return `421 Misdirected Request` with a structured body.
3551
+ * The client (or LB) can surface this as a wrong-shard signal.
3552
+ */
3553
+ function createDriveFetchMiddleware(cache) {
3554
+ return (next) => async (request) => {
3555
+ const driveId = request.headers.get(DRIVE_ID_HEADER) ?? "";
3556
+ if (driveId === "") return next(request);
3557
+ if (cache.has(driveId)) {
3558
+ driveIdMap.set(request, driveId);
3559
+ return next(request);
3560
+ }
3561
+ if (await isCacheBypassOperation(request)) return next(request);
3562
+ return wrongShardResponse(driveId);
3563
+ };
3564
+ }
3565
+ async function isCacheBypassOperation(request) {
3566
+ if (request.method !== "POST") return false;
3567
+ try {
3568
+ const body = await request.clone().json();
3569
+ if (typeof body.operationName === "string") return CACHE_BYPASS_OPERATIONS.has(body.operationName);
3570
+ if (typeof body.query === "string") return CACHE_BYPASS_OPERATIONS.has(extractOperationName(body.query));
3571
+ return false;
3572
+ } catch {
3573
+ return false;
3574
+ }
3575
+ }
3576
+ const OPERATION_NAME_PATTERN = /\b(?:mutation|query|subscription)\s+(\w+)/;
3577
+ function extractOperationName(query) {
3578
+ const match = OPERATION_NAME_PATTERN.exec(query);
3579
+ return match ? match[1] : "";
3580
+ }
3581
+ function wrongShardResponse(driveId) {
3582
+ return new globalThis.Response(JSON.stringify({
3583
+ error: "wrong-shard",
3584
+ driveId
3585
+ }), {
3586
+ status: 421,
3587
+ headers: { "content-type": "application/json" }
3588
+ });
3589
+ }
3590
+ //#endregion
3591
+ //#region src/graphql/gateway/drive-ownership-cache.ts
3592
+ /**
3593
+ * In-memory record of which drives this switchboard instance owns.
3594
+ *
3595
+ * Populated at startup by walking the reactor for documents of type
3596
+ * `powerhouse/document-drive`. Mutated explicitly by resolver hooks
3597
+ * after successful drive create / delete operations. Read by the
3598
+ * drive-validation fetch middleware to short-circuit wrong-shard
3599
+ * requests with a structured 421 response.
3600
+ */
3601
+ var DriveOwnershipCache = class {
3602
+ drives = /* @__PURE__ */ new Set();
3603
+ constructor(reactorClient) {
3604
+ this.reactorClient = reactorClient;
3605
+ }
3606
+ async init() {
3607
+ this.drives.clear();
3608
+ let page = await this.reactorClient.find({ type: DRIVE_DOCUMENT_TYPE });
3609
+ while (true) {
3610
+ for (const drive of page.results) this.drives.add(drive.header.id);
3611
+ if (!page.next) return;
3612
+ page = await page.next();
3613
+ }
3614
+ }
3615
+ has(driveId) {
3616
+ return this.drives.has(driveId);
3617
+ }
3618
+ add(driveId) {
3619
+ this.drives.add(driveId);
3620
+ }
3621
+ remove(driveId) {
3622
+ this.drives.delete(driveId);
3623
+ }
3624
+ size() {
3625
+ return this.drives.size;
3626
+ }
3627
+ };
3628
+ //#endregion
3479
3629
  //#region src/graphql/sse.ts
3480
3630
  /**
3481
3631
  * Create a Fetch-API-compatible SSE handler for GraphQL subscriptions
@@ -3539,6 +3689,8 @@ var GraphQLManager = class {
3539
3689
  authService = null;
3540
3690
  subgraphWsDisposers = /* @__PURE__ */ new Map();
3541
3691
  #authMiddleware;
3692
+ #driveMiddleware;
3693
+ driveOwnershipCache;
3542
3694
  /** Cached document models for schema generation - updated on init and regenerate */
3543
3695
  cachedDocumentModels = [];
3544
3696
  subgraphHandlerCache = /* @__PURE__ */ new Map();
@@ -3559,11 +3711,15 @@ var GraphQLManager = class {
3559
3711
  this.port = port;
3560
3712
  this.authorizationService = authorizationService;
3561
3713
  if (this.authConfig) this.authService = new AuthService(this.authConfig);
3714
+ this.driveOwnershipCache = new DriveOwnershipCache(this.reactorClient);
3562
3715
  this.wsServer.setMaxListeners(0);
3563
3716
  }
3564
3717
  async init(coreSubgraphs, authMiddleware) {
3565
3718
  this.#authMiddleware = authMiddleware;
3566
3719
  this.logger.debug(`Initializing Subgraph Manager...`);
3720
+ await this.driveOwnershipCache.init();
3721
+ this.#driveMiddleware = createDriveFetchMiddleware(this.driveOwnershipCache);
3722
+ this.logger.debug(`Drive ownership cache populated with ${this.driveOwnershipCache.size()} drives`);
3567
3723
  const models = (await this.reactorClient.getDocumentModelModules()).results;
3568
3724
  this.cachedDocumentModels = models;
3569
3725
  if (!models.find((it) => it.documentModel.global.name === "DocumentDrive")) throw new Error("DocumentDrive model required");
@@ -3578,12 +3734,12 @@ var GraphQLManager = class {
3578
3734
  if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
3579
3735
  try {
3580
3736
  const driveDoc = await this.reactorClient.get(driveIdOrSlug);
3581
- const graphqlEndpoint = `${(request.headers.get("x-forwarded-proto") ?? url.protocol.replace(":", "")) + ":"}//${request.headers.get("host") ?? ""}${this.path === "/" ? "" : this.path}/graphql/r`;
3737
+ const graphqlEndpoint = `${(request.headers.get("x-forwarded-proto")?.split(",")[0].trim() ?? url.protocol.replace(":", "")) + ":"}//${request.headers.get("x-forwarded-host")?.split(",")[0].trim() ?? request.headers.get("host") ?? ""}${(request.headers.get("x-forwarded-prefix")?.split(",")[0].trim().replace(/\/$/, "") ?? "") + (this.path === "/" ? "" : this.path)}/graphql/r`;
3582
3738
  return Response.json({
3583
3739
  id: driveDoc.header.id,
3584
3740
  slug: driveDoc.header.slug,
3585
3741
  meta: driveDoc.header.meta,
3586
- name: driveDoc.state.global.name,
3742
+ name: driveDoc.state.global.name || driveDoc.header.name,
3587
3743
  icon: driveDoc.state.global.icon ?? void 0,
3588
3744
  ...graphqlEndpoint && { graphqlEndpoint }
3589
3745
  });
@@ -3723,6 +3879,7 @@ var GraphQLManager = class {
3723
3879
  #makeContextFactory() {
3724
3880
  return (request) => {
3725
3881
  const authCtx = getAuthContext(request);
3882
+ const driveId = getRequestDriveId(request);
3726
3883
  const headers = {};
3727
3884
  request.headers.forEach((v, k) => {
3728
3885
  headers[k] = v;
@@ -3731,6 +3888,7 @@ var GraphQLManager = class {
3731
3888
  headers,
3732
3889
  db: this.relationalDb,
3733
3890
  ...this.getAdditionalContextFields(),
3891
+ driveId,
3734
3892
  user: authCtx?.user,
3735
3893
  isAdmin: authCtx ? (addr) => !authCtx.auth_enabled ? true : authCtx.admins.includes(addr.toLowerCase()) : () => true
3736
3894
  });
@@ -3769,7 +3927,7 @@ var GraphQLManager = class {
3769
3927
  if (this.subgraphHandlerCache.has(subgraphPath)) continue;
3770
3928
  const schema = createSchema(this.cachedDocumentModels, subgraph.resolvers, subgraph.typeDefs);
3771
3929
  const rawHandler = await this.gatewayAdapter.createHandler(schema, this.#makeContextFactory());
3772
- const fetchHandler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3930
+ const fetchHandler = this.#composeFetchMiddleware(rawHandler);
3773
3931
  this.subgraphHandlerCache.set(subgraphPath, fetchHandler);
3774
3932
  this.httpAdapter.mount(subgraphPath, fetchHandler);
3775
3933
  if (subgraph.hasSubscriptions) {
@@ -3819,7 +3977,7 @@ var GraphQLManager = class {
3819
3977
  async #createSupergraphGateway() {
3820
3978
  const superGraphPath = path.join(this.path, "graphql");
3821
3979
  const rawHandler = await this.gatewayAdapter.createSupergraphHandler(() => this.#getSubgraphDefinitions(), this.httpServer, this.#makeContextFactory());
3822
- const fetchHandler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3980
+ const fetchHandler = this.#composeFetchMiddleware(rawHandler);
3823
3981
  this.httpAdapter.mount(superGraphPath, fetchHandler);
3824
3982
  this.#setupSupergraphSSE(superGraphPath);
3825
3983
  if (!this.initialized) {
@@ -3859,9 +4017,20 @@ var GraphQLManager = class {
3859
4017
  schema,
3860
4018
  contextFactory: this.#makeContextFactory()
3861
4019
  });
3862
- const handler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
4020
+ const handler = this.#composeFetchMiddleware(rawHandler);
3863
4021
  this.httpAdapter.mount(ssePath, handler, { exact: true });
3864
4022
  }
4023
+ /**
4024
+ * Compose the request-level fetch middleware chain. Auth runs first
4025
+ * (so we don't validate shard before knowing the request is even
4026
+ * authorized), drive-ownership validation runs after.
4027
+ */
4028
+ #composeFetchMiddleware(rawHandler) {
4029
+ let handler = rawHandler;
4030
+ if (this.#driveMiddleware) handler = this.#driveMiddleware(handler);
4031
+ if (this.#authMiddleware) handler = this.#authMiddleware(handler);
4032
+ return handler;
4033
+ }
3865
4034
  };
3866
4035
  //#endregion
3867
4036
  //#region src/graphql/packages/resolvers.ts
@@ -4377,6 +4546,21 @@ var ReactorSubgraph = class extends BaseSubgraph {
4377
4546
  await this.assertCanExecuteOperation(documentId, operationType, ctx);
4378
4547
  }
4379
4548
  }
4549
+ /**
4550
+ * Returns the drive id when the given identifier (id or slug) refers
4551
+ * to a drive document, otherwise undefined. Used by deleteDocument to
4552
+ * decide whether to invalidate the drive-ownership cache after a
4553
+ * successful delete.
4554
+ */
4555
+ async #resolveDriveId(identifier) {
4556
+ try {
4557
+ const doc = await this.reactorClient.get(identifier);
4558
+ if (doc.header.documentType === "powerhouse/document-drive") return doc.header.id;
4559
+ return;
4560
+ } catch {
4561
+ return;
4562
+ }
4563
+ }
4380
4564
  typeDefs = gql(schema_default);
4381
4565
  resolvers = {
4382
4566
  PHDocument: { operations: async (parent, args, ctx) => {
@@ -4517,6 +4701,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
4517
4701
  if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
4518
4702
  } else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
4519
4703
  const result = await createDocument(this.reactorClient, args);
4704
+ if (result?.id && result.documentType === "powerhouse/document-drive") this.graphqlManager.driveOwnershipCache.add(result.id);
4520
4705
  if (this.authorizationService && ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
4521
4706
  return result;
4522
4707
  } catch (error) {
@@ -4534,6 +4719,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
4534
4719
  if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
4535
4720
  } else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
4536
4721
  const result = await createEmptyDocument(this.reactorClient, args);
4722
+ if (result?.id && result.documentType === "powerhouse/document-drive") this.graphqlManager.driveOwnershipCache.add(result.id);
4537
4723
  if (this.authorizationService && ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
4538
4724
  return result;
4539
4725
  } catch (error) {
@@ -4608,7 +4794,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
4608
4794
  this.logger.debug("deleteDocument(@args)", args);
4609
4795
  try {
4610
4796
  await this.assertCanWrite(args.identifier, ctx);
4611
- return await deleteDocument(this.reactorClient, args);
4797
+ const driveIdToInvalidate = await this.#resolveDriveId(args.identifier);
4798
+ const result = await deleteDocument(this.reactorClient, args);
4799
+ if (result && driveIdToInvalidate) this.graphqlManager.driveOwnershipCache.remove(driveIdToInvalidate);
4800
+ return result;
4612
4801
  } catch (error) {
4613
4802
  this.logger.error("Error in deleteDocument(@args): @Error", error);
4614
4803
  throw error;
@@ -4713,29 +4902,14 @@ dotenv.config();
4713
4902
  const ADMIN_USERS = getAdminUsers();
4714
4903
  //#endregion
4715
4904
  //#region src/graphql/system/version.ts
4716
- let cachedVersion;
4717
- let cachedGitHash;
4718
4905
  function getVersion() {
4719
- if (cachedVersion !== void 0) return cachedVersion;
4720
- cachedVersion = process.env.PH_VERSION ?? process.env.npm_package_version ?? "unknown";
4721
- return cachedVersion;
4906
+ return "6.0.2-staging.7";
4722
4907
  }
4723
4908
  function getGitHash() {
4724
- if (cachedGitHash !== void 0) return cachedGitHash;
4725
- if (process.env.PH_GIT_SHA) {
4726
- cachedGitHash = process.env.PH_GIT_SHA;
4727
- return cachedGitHash;
4728
- }
4729
- try {
4730
- cachedGitHash = execSync("git rev-parse HEAD", { stdio: [
4731
- "ignore",
4732
- "pipe",
4733
- "ignore"
4734
- ] }).toString().trim();
4735
- } catch {
4736
- cachedGitHash = "unknown";
4737
- }
4738
- return cachedGitHash;
4909
+ return "6b981a6f1934d98ad53dde1f68a46d9758d7c962";
4910
+ }
4911
+ function getGitUrl() {
4912
+ return buildTreeUrl(getGitHash());
4739
4913
  }
4740
4914
  //#endregion
4741
4915
  //#region src/graphql/system/subgraph.ts
@@ -4746,6 +4920,7 @@ var SystemSubgraph = class extends BaseSubgraph {
4746
4920
  type SystemInfo {
4747
4921
  version: String!
4748
4922
  gitHash: String!
4923
+ gitUrl: String
4749
4924
  }
4750
4925
 
4751
4926
  type Query {
@@ -4754,7 +4929,8 @@ var SystemSubgraph = class extends BaseSubgraph {
4754
4929
  `;
4755
4930
  resolvers = { Query: { system: () => ({
4756
4931
  version: getVersion(),
4757
- gitHash: getGitHash()
4932
+ gitHash: getGitHash(),
4933
+ gitUrl: getGitUrl()
4758
4934
  }) } };
4759
4935
  };
4760
4936
  //#endregion
@@ -5895,102 +6071,6 @@ var DocumentPermissionService = class {
5895
6071
  }
5896
6072
  };
5897
6073
  //#endregion
5898
- //#region src/tracing.ts
5899
- const TEMPO_ENDPOINT = process.env.TEMPO_ENDPOINT || "http://tempo.monitoring.svc.cluster.local:4318/v1/traces";
5900
- const SERVICE_NAME = process.env.OTEL_SERVICE_NAME || "reactor-api";
5901
- const SERVICE_VERSION = process.env.npm_package_version || "unknown";
5902
- const TENANT_ID = process.env.TENANT_ID || "default";
5903
- const METRICS_ENDPOINT = process.env.METRICS_ENDPOINT;
5904
- const METRICS_EXPORT_INTERVAL_MS = parseInt(process.env.METRICS_EXPORT_INTERVAL_MS || "30000", 10);
5905
- const TRACING_ENABLED = process.env.ENABLE_TRACING === "true" || process.env.NODE_ENV === "production";
5906
- if (TRACING_ENABLED) {
5907
- console.log(`Initializing OpenTelemetry tracing for ${SERVICE_NAME}...`);
5908
- console.log(` Tempo endpoint: ${TEMPO_ENDPOINT}`);
5909
- console.log(` Service: ${SERVICE_NAME}`);
5910
- console.log(` Tenant: ${TENANT_ID}`);
5911
- const traceExporter = new OTLPTraceExporter({
5912
- url: TEMPO_ENDPOINT,
5913
- headers: {}
5914
- });
5915
- const resource = new Resource({
5916
- [ATTR_SERVICE_NAME]: SERVICE_NAME,
5917
- [ATTR_SERVICE_VERSION]: SERVICE_VERSION,
5918
- "tenant.id": TENANT_ID,
5919
- "deployment.environment": process.env.NODE_ENV || "development"
5920
- });
5921
- const PROMETHEUS_METRICS_PORT = process.env.PROMETHEUS_METRICS_PORT;
5922
- let metricReader;
5923
- if (PROMETHEUS_METRICS_PORT) {
5924
- const port = parseInt(PROMETHEUS_METRICS_PORT, 10);
5925
- metricReader = new PrometheusExporter({
5926
- port,
5927
- preventServerStart: false
5928
- });
5929
- console.log(` Prometheus /metrics on port ${port}`);
5930
- } else if (METRICS_ENDPOINT) {
5931
- metricReader = new PeriodicExportingMetricReader({
5932
- exporter: new OTLPMetricExporter({ url: METRICS_ENDPOINT }),
5933
- exportIntervalMillis: METRICS_EXPORT_INTERVAL_MS
5934
- });
5935
- console.log(` Metrics OTLP endpoint: ${METRICS_ENDPOINT}`);
5936
- console.log(` Metrics interval: ${METRICS_EXPORT_INTERVAL_MS}ms`);
5937
- }
5938
- const sdk = new NodeSDK({
5939
- resource,
5940
- spanProcessors: [new BatchSpanProcessor(traceExporter)],
5941
- metricReader,
5942
- instrumentations: [getNodeAutoInstrumentations({
5943
- "@opentelemetry/instrumentation-http": {
5944
- enabled: true,
5945
- ignoreIncomingRequestHook: (req) => {
5946
- return req.url === "/health" || req.url === "/ready";
5947
- },
5948
- requireParentforIncomingSpans: false,
5949
- requireParentforOutgoingSpans: false,
5950
- requestHook: (span, request) => {
5951
- span.setAttribute("http.route", request.url || "");
5952
- },
5953
- responseHook: (span, response) => {
5954
- if (response.statusCode) span.setAttribute("http.status_code", response.statusCode);
5955
- }
5956
- },
5957
- "@opentelemetry/instrumentation-express": {
5958
- enabled: true,
5959
- requestHook: (span, info) => {
5960
- if (info.route) span.setAttribute("http.route", info.route);
5961
- }
5962
- },
5963
- "@opentelemetry/instrumentation-graphql": {
5964
- enabled: true,
5965
- mergeItems: true,
5966
- allowValues: true
5967
- },
5968
- "@opentelemetry/instrumentation-pg": {
5969
- enabled: true,
5970
- enhancedDatabaseReporting: true
5971
- },
5972
- "@opentelemetry/instrumentation-redis-4": {
5973
- enabled: true,
5974
- dbStatementSerializer: (cmdName, cmdArgs) => {
5975
- return cmdName;
5976
- }
5977
- }
5978
- })]
5979
- });
5980
- sdk.start();
5981
- console.log("✓ OpenTelemetry tracing initialized");
5982
- process.on("SIGTERM", () => {
5983
- sdk.shutdown().then(() => console.log("Tracing terminated")).catch((error) => console.log("Error terminating tracing", error)).finally(() => process.exit(0));
5984
- });
5985
- }
5986
- async function initTracing() {}
5987
- function isTracingEnabled() {
5988
- return TRACING_ENABLED;
5989
- }
5990
- async function trace(_name, _options, fn) {
5991
- return fn();
5992
- }
5993
- //#endregion
5994
6074
  //#region src/utils/db.ts
5995
6075
  function isPG(connectionString) {
5996
6076
  if (connectionString.startsWith("postgresql://") || connectionString.startsWith("postgres://")) return true;
@@ -5999,7 +6079,8 @@ function isPG(connectionString) {
5999
6079
  function getDbClient(connectionString = void 0, pgliteFactory) {
6000
6080
  const isPg = connectionString && isPG(connectionString);
6001
6081
  const client = isPg ? "pg" : ClientPgLite;
6002
- const connection = isPg ? { connectionString } : { pglite: pgliteFactory ? pgliteFactory(connectionString) : new PGlite(connectionString) };
6082
+ const pgliteInstance = isPg ? void 0 : pgliteFactory ? pgliteFactory(connectionString) : new PGlite(connectionString);
6083
+ const connection = isPg ? { connectionString } : { pglite: pgliteInstance };
6003
6084
  if (connectionString && !isPg) {
6004
6085
  const dirPath = path.resolve(connectionString, "..");
6005
6086
  if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });
@@ -6013,7 +6094,8 @@ function getDbClient(connectionString = void 0, pgliteFactory) {
6013
6094
  knex: knexInstance,
6014
6095
  kyselySubDialect: new PGColdDialect()
6015
6096
  }) }),
6016
- knex: knexInstance
6097
+ knex: knexInstance,
6098
+ pglite: pgliteInstance
6017
6099
  };
6018
6100
  }
6019
6101
  const initAnalyticsStoreSql = [
@@ -6086,19 +6168,36 @@ function resolveAttachmentStoragePath(options) {
6086
6168
  return path.join(tmpdir(), "reactor-attachments");
6087
6169
  }
6088
6170
  /**
6089
- * Initializes the database and analytics store
6171
+ * Initializes the database and analytics store. The returned `closers` are
6172
+ * idempotent thunks that release the underlying knex pool and PGlite instance
6173
+ * (when on-disk PGlite is in use); callers are expected to run them as part
6174
+ * of API teardown so PGlite WAL is flushed and the data-dir lock is released.
6090
6175
  */
6091
6176
  async function initializeDatabaseAndAnalytics(dbPath, pgliteFactory) {
6092
- const { db, knex } = getDbClient(dbPath, pgliteFactory);
6177
+ const { db, knex, pglite } = getDbClient(dbPath, pgliteFactory);
6093
6178
  const relationalDb = createRelationalDb(db);
6094
6179
  const analyticsStore = new PostgresAnalyticsStore({ knex });
6095
6180
  for (const sql of initAnalyticsStoreSql) await knex.raw(sql);
6096
6181
  return {
6097
6182
  relationalDb,
6098
- analyticsStore
6183
+ analyticsStore,
6184
+ closers: makeDbClosers(knex, pglite)
6099
6185
  };
6100
6186
  }
6101
6187
  /**
6188
+ * Builds best-effort closers for a knex/PGlite pair returned by
6189
+ * {@link getDbClient}. Order is significant: knex first releases its pool
6190
+ * (which is what the application talks to), then PGlite flushes WAL and
6191
+ * unlocks the data dir.
6192
+ */
6193
+ function makeDbClosers(knexInstance, pglite) {
6194
+ const closers = [() => knexInstance.destroy()];
6195
+ if (pglite) closers.push(async () => {
6196
+ if (!pglite.closed) await pglite.close();
6197
+ });
6198
+ return closers;
6199
+ }
6200
+ /**
6102
6201
  * Sets up the subgraph manager and registers subgraphs
6103
6202
  */
6104
6203
  async function setupGraphQLManager(httpAdapter, authFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, auth, documentPermissionService, enableDocumentModelSubgraphs, port, authorizationService) {
@@ -6173,7 +6272,6 @@ async function startServer(httpAdapter, port, httpsOptions, logger) {
6173
6272
  * This includes auth configuration, database setup, and package manager initialization.
6174
6273
  */
6175
6274
  async function _setupCommonInfrastructure(options) {
6176
- if (isTracingEnabled()) await initTracing();
6177
6275
  const port = options.port ?? DEFAULT_PORT;
6178
6276
  const { adapter: httpAdapter } = createHttpAdapter("express");
6179
6277
  let admins = [];
@@ -6216,10 +6314,13 @@ async function _setupCommonInfrastructure(options) {
6216
6314
  });
6217
6315
  authFetchMiddleware = createAuthFetchMiddleware(authService);
6218
6316
  }
6219
- const { relationalDb, analyticsStore } = await trace("reactor-api.init.database", { tags: { "resource.name": "database" } }, () => initializeDatabaseAndAnalytics(options.dbPath, options.pgliteFactory));
6317
+ const dbClosers = [];
6318
+ const { relationalDb, analyticsStore, closers: analyticsClosers } = await initializeDatabaseAndAnalytics(options.dbPath, options.pgliteFactory);
6319
+ dbClosers.push(...analyticsClosers);
6220
6320
  let documentPermissionService = options.documentPermissionService;
6221
6321
  if (!documentPermissionService && DOCUMENT_PERMISSIONS_ENABLED === "true") {
6222
- const { db } = getDbClient(options.dbPath, options.pgliteFactory);
6322
+ const { db, knex, pglite } = getDbClient(options.dbPath, options.pgliteFactory);
6323
+ dbClosers.push(...makeDbClosers(knex, pglite));
6223
6324
  await runMigrations(db);
6224
6325
  logger.info("Document permission migrations completed");
6225
6326
  documentPermissionService = new DocumentPermissionService(db, { defaultProtection });
@@ -6235,7 +6336,8 @@ async function _setupCommonInfrastructure(options) {
6235
6336
  }
6236
6337
  const attachmentStoragePath = resolveAttachmentStoragePath(options);
6237
6338
  await mkdir(attachmentStoragePath, { recursive: true });
6238
- const { db: attachmentDb } = getDbClient(options.dbPath, options.pgliteFactory);
6339
+ const { db: attachmentDb, knex: attachmentKnex, pglite: attachmentPglite } = getDbClient(options.dbPath, options.pgliteFactory);
6340
+ dbClosers.push(...makeDbClosers(attachmentKnex, attachmentPglite));
6239
6341
  const attachments = await new AttachmentBuilder(attachmentDb, attachmentStoragePath).build();
6240
6342
  logger.info("Attachment service initialized");
6241
6343
  const packages = new PackageManager(options.packageLoaders ?? [new ImportPackageLoader()], {
@@ -6256,13 +6358,14 @@ async function _setupCommonInfrastructure(options) {
6256
6358
  documentPermissionService,
6257
6359
  authorizationService,
6258
6360
  attachments,
6259
- packages
6361
+ packages,
6362
+ dbClosers
6260
6363
  };
6261
6364
  }
6262
6365
  /**
6263
6366
  * Private helper function containing common setup logic for API initialization
6264
6367
  */
6265
- async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, auth, processorApp, readModels, attachments, authorizationService, documentModelRegistry) {
6368
+ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, auth, processorApp, readModels, attachments, authorizationService, documentModelRegistry, dbClosers = []) {
6266
6369
  const hostModule = {
6267
6370
  relationalDb,
6268
6371
  analyticsStore,
@@ -6328,7 +6431,51 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
6328
6431
  graphqlManager,
6329
6432
  packages,
6330
6433
  attachments,
6331
- authService
6434
+ authService,
6435
+ dispose: buildApiDispose({
6436
+ graphqlManager,
6437
+ httpServer,
6438
+ wsServer,
6439
+ dbClosers,
6440
+ logger
6441
+ })
6442
+ };
6443
+ }
6444
+ /**
6445
+ * Composes the lifecycle teardown for an API instance. Steps run in
6446
+ * dependency order so that draining HTTP/GraphQL surfaces happens before the
6447
+ * underlying knex pool and PGlite WAL are released. Each step is wrapped in
6448
+ * its own try/catch — one failure must not strand the rest of the chain,
6449
+ * since this runs on the way to process exit.
6450
+ */
6451
+ function buildApiDispose(args) {
6452
+ const { graphqlManager, httpServer, wsServer, dbClosers, logger } = args;
6453
+ let disposed = false;
6454
+ return async () => {
6455
+ if (disposed) return;
6456
+ disposed = true;
6457
+ try {
6458
+ await graphqlManager.shutdown();
6459
+ } catch (error) {
6460
+ logger.error("API dispose: graphqlManager.shutdown failed:", error);
6461
+ }
6462
+ try {
6463
+ for (const client of wsServer.clients) client.terminate();
6464
+ await new Promise((resolve) => wsServer.close(() => resolve()));
6465
+ } catch (error) {
6466
+ logger.error("API dispose: wsServer.close failed:", error);
6467
+ }
6468
+ if (httpServer.listening) try {
6469
+ httpServer.closeAllConnections();
6470
+ await new Promise((resolve, reject) => httpServer.close((err) => err ? reject(err) : resolve()));
6471
+ } catch (error) {
6472
+ logger.error("API dispose: httpServer.close failed:", error);
6473
+ }
6474
+ for (const close of dbClosers) try {
6475
+ await close();
6476
+ } catch (error) {
6477
+ logger.error("API dispose: db closer failed:", error);
6478
+ }
6332
6479
  };
6333
6480
  }
6334
6481
  /**
@@ -6342,9 +6489,9 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
6342
6489
  * @returns The API server components along with the created client instances.
6343
6490
  */
6344
6491
  async function initializeAndStartAPI(clientInitializer, options, processorApp) {
6345
- const { port, httpAdapter, authFetchMiddleware, authService, auth, relationalDb, analyticsStore, documentPermissionService, authorizationService, attachments, packages } = await trace("reactor-api.setup.infrastructure", { tags: { "resource.name": "infrastructure" } }, () => _setupCommonInfrastructure(options));
6346
- const { documentModels, processors, subgraphs } = await trace("reactor-api.packages.init", { tags: { "resource.name": "packages" } }, () => packages.init());
6347
- const reactorClientModule = await trace("reactor-api.reactor-client.init", { tags: { "resource.name": "reactor-client" } }, () => clientInitializer(documentModels));
6492
+ const { port, httpAdapter, authFetchMiddleware, authService, auth, relationalDb, analyticsStore, documentPermissionService, authorizationService, attachments, packages, dbClosers } = await _setupCommonInfrastructure(options);
6493
+ const { documentModels, processors, subgraphs } = await packages.init();
6494
+ const reactorClientModule = await clientInitializer(documentModels);
6348
6495
  const reactorClient = reactorClientModule.client;
6349
6496
  const syncManager = reactorClientModule.reactorModule?.syncModule?.syncManager;
6350
6497
  if (!syncManager) throw new Error("SyncManager not available from ReactorClientModule");
@@ -6353,7 +6500,7 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
6353
6500
  const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
6354
6501
  if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from ReactorClientModule");
6355
6502
  return {
6356
- ...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, auth, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, authorizationService, documentModelRegistry),
6503
+ ...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, auth, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, authorizationService, documentModelRegistry, dbClosers),
6357
6504
  client: reactorClient,
6358
6505
  syncManager,
6359
6506
  documentModelRegistry
@@ -6459,6 +6606,7 @@ var PackageManagementService = class {
6459
6606
  }
6460
6607
  };
6461
6608
  //#endregion
6462
- export { ADMIN_USERS, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentInputSchema, AuthService, AuthSubgraph, BaseSubgraph, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, ImportPackageLoader, InMemoryPackageStorage, JobChangesDocument, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createSchema, definedNonNullAnySchema, driveIdFromUrl, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getGitHash, getSdk, getUniqueDocumentModels, getVersion, initAnalyticsStoreSql, initTracing, initializeAndStartAPI, isDefinedNonNullAny, isSubgraphClass, isTracingEnabled, parseDriveUrl, renderGraphqlPlayground, trace };
6609
+ export { ADMIN_USERS, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentInputSchema, AuthService, AuthSubgraph, BaseSubgraph, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, ImportPackageLoader, InMemoryPackageStorage, JobChangesDocument, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createSchema, definedNonNullAnySchema, driveIdFromUrl, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isSubgraphClass, parseDriveUrl, renderGraphqlPlayground };
6463
6610
 
6464
- //# sourceMappingURL=index.mjs.map
6611
+ //# sourceMappingURL=index.mjs.map
6612
+ //# debugId=81df4612-dfa1-5785-acec-bca9f4770f2d