@powerhousedao/reactor-api 6.0.2-staging.6 → 6.0.2-staging.8

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";
@@ -928,7 +921,7 @@ function parseBodyLimit(limit) {
928
921
  var FastifyHttpAdapter = class {
929
922
  #fetchRoutes = [];
930
923
  #getRoutes = /* @__PURE__ */ new Map();
931
- #nodeRoutes = /* @__PURE__ */ new Map();
924
+ #nodeRoutes = [];
932
925
  #setupOps = [];
933
926
  #instance;
934
927
  get handle() {
@@ -955,8 +948,11 @@ var FastifyHttpAdapter = class {
955
948
  });
956
949
  }
957
950
  mountNodeRoute(method, path, handler) {
958
- if (!this.#nodeRoutes.has(path)) this.#nodeRoutes.set(path, /* @__PURE__ */ new Map());
959
- this.#nodeRoutes.get(path).set(method, handler);
951
+ this.#nodeRoutes.push({
952
+ method,
953
+ matcher: match(normalizePath(path)),
954
+ handler
955
+ });
960
956
  }
961
957
  mountRawMiddleware(middleware) {
962
958
  if (this.#instance) this.#instance.use(middleware);
@@ -1026,14 +1022,14 @@ var FastifyHttpAdapter = class {
1026
1022
  #dispatch(req, reply) {
1027
1023
  const pathname = new URL(req.url, "http://localhost").pathname;
1028
1024
  const method = req.method.toUpperCase();
1029
- const nodeHandlers = this.#nodeRoutes.get(pathname);
1030
- if (nodeHandlers) {
1031
- const handler = nodeHandlers.get(method);
1032
- if (handler) {
1033
- reply.hijack();
1034
- handler(req.raw, reply.raw, req.body);
1035
- return;
1036
- }
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;
1037
1033
  }
1038
1034
  if (method === "GET") {
1039
1035
  for (const entry of this.#getRoutes.values()) if (entry.matcher(pathname)) return this.#serveGetEntry(entry, req, reply);
@@ -2721,9 +2717,13 @@ function matchesJobFilter(payload, args) {
2721
2717
  return payload.jobId === args.jobId;
2722
2718
  }
2723
2719
  //#endregion
2724
- //#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
+ */
2725
2726
  const DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive";
2726
- const POLL_SYNC_ENVELOPES_MAX_LIMIT = 100;
2727
2727
  async function documentModels(reactorClient, args) {
2728
2728
  const namespace = fromInputMaybe(args.namespace);
2729
2729
  let paging;
@@ -2919,7 +2919,7 @@ async function createDocument(reactorClient, args) {
2919
2919
  const parentIdentifier = fromInputMaybe(args.parentIdentifier);
2920
2920
  let result;
2921
2921
  try {
2922
- 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);
2923
2923
  else result = await reactorClient.create(document, parentIdentifier);
2924
2924
  else result = await reactorClient.create(document);
2925
2925
  } catch (error) {
@@ -2936,7 +2936,7 @@ async function createEmptyDocument(reactorClient, args) {
2936
2936
  const name = fromInputMaybe(args.name);
2937
2937
  let result;
2938
2938
  try {
2939
- 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") {
2940
2940
  const document = (await reactorClient.getDocumentModelModule(args.documentType)).utils.createDocument();
2941
2941
  if (name) document.header.name = name;
2942
2942
  result = await reactorClient.drives.addFile(parentIdentifier, document);
@@ -2983,7 +2983,7 @@ async function createDocumentWithInitialState(reactorClient, args) {
2983
2983
  } catch (error) {
2984
2984
  throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
2985
2985
  }
2986
- if (parent.header.documentType === DRIVE_DOCUMENT_TYPE) try {
2986
+ if (parent.header.documentType === "powerhouse/document-drive") try {
2987
2987
  result = await reactorClient.drives.addFile(parentIdentifier, document);
2988
2988
  } catch (error) {
2989
2989
  throw new GraphQLError(`Failed to create document in drive: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -3174,14 +3174,22 @@ function pollSyncEnvelopes(syncManager, args) {
3174
3174
  scopes: syncOp.scopes,
3175
3175
  operationCount: syncOp.operations.length
3176
3176
  }));
3177
- if (args.outboxAck > 0) trimMailboxFromAckOrdinal(remote.channel.outbox, args.outboxAck);
3178
- let operations = remote.channel.outbox.items;
3179
- operations = operations.filter((syncOp) => {
3180
- let maxOrdinal = 0;
3181
- for (const op of syncOp.operations) maxOrdinal = Math.max(maxOrdinal, op.context.ordinal);
3182
- if (maxOrdinal > args.outboxLatest) return true;
3183
- return false;
3184
- });
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;
3185
3193
  if (operations.length === 0) return {
3186
3194
  envelopes: [],
3187
3195
  ackOrdinal: remote.channel.inbox.ackOrdinal,
@@ -3191,29 +3199,66 @@ function pollSyncEnvelopes(syncManager, args) {
3191
3199
  const sorted = [...operations].sort((a, b) => {
3192
3200
  return (a.operations[0]?.context.ordinal ?? 0) - (b.operations[0]?.context.ordinal ?? 0);
3193
3201
  });
3194
- const hasMore = sorted.length > POLL_SYNC_ENVELOPES_MAX_LIMIT;
3195
- const pageOperations = sorted.slice(0, POLL_SYNC_ENVELOPES_MAX_LIMIT);
3202
+ const envelopes = [];
3203
+ let pageOps = 0;
3204
+ let hasMore = false;
3196
3205
  let maxOrdinal = args.outboxLatest;
3197
- for (const syncOp of pageOperations) for (const op of syncOp.operations) {
3198
- const opOrdinal = op.context.ordinal;
3199
- 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
+ }
3200
3258
  }
3259
+ for (const envelope of envelopes) if (envelope.cursor) envelope.cursor.cursorOrdinal = maxOrdinal;
3201
3260
  return {
3202
- envelopes: sortEnvelopesByFirstOperationTimestamp(pageOperations.map((syncOp) => ({
3203
- type: "OPERATIONS",
3204
- channelMeta: { id: args.channelId },
3205
- operations: syncOp.operations.map((op) => ({
3206
- operation: serializeOperationForGraphQL(op.operation),
3207
- context: op.context
3208
- })),
3209
- cursor: {
3210
- remoteName: remote.name,
3211
- cursorOrdinal: maxOrdinal,
3212
- lastSyncedAtUtcMs: Date.now().toString()
3213
- },
3214
- key: syncOp.jobId || void 0,
3215
- dependsOn: syncOp.jobDependencies.filter(Boolean).length > 0 ? syncOp.jobDependencies.filter(Boolean) : void 0
3216
- }))),
3261
+ envelopes,
3217
3262
  ackOrdinal: remote.channel.inbox.ackOrdinal,
3218
3263
  deadLetters,
3219
3264
  hasMore
@@ -3228,9 +3273,8 @@ function pollSyncEnvelopes(syncManager, args) {
3228
3273
  * and returns the highest one as `ackOrdinal` in pollSyncEnvelopes.
3229
3274
  */
3230
3275
  function pushSyncEnvelopes(syncManager, args) {
3231
- const sortedEnvelopes = sortEnvelopesByFirstOperationTimestamp(args.envelopes);
3232
3276
  const remoteSyncOps = /* @__PURE__ */ new Map();
3233
- for (const envelope of sortedEnvelopes) {
3277
+ for (const envelope of args.envelopes) {
3234
3278
  let remote;
3235
3279
  try {
3236
3280
  remote = syncManager.getById(envelope.channelMeta.id);
@@ -3478,6 +3522,110 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
3478
3522
  }
3479
3523
  };
3480
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
3481
3629
  //#region src/graphql/sse.ts
3482
3630
  /**
3483
3631
  * Create a Fetch-API-compatible SSE handler for GraphQL subscriptions
@@ -3541,6 +3689,8 @@ var GraphQLManager = class {
3541
3689
  authService = null;
3542
3690
  subgraphWsDisposers = /* @__PURE__ */ new Map();
3543
3691
  #authMiddleware;
3692
+ #driveMiddleware;
3693
+ driveOwnershipCache;
3544
3694
  /** Cached document models for schema generation - updated on init and regenerate */
3545
3695
  cachedDocumentModels = [];
3546
3696
  subgraphHandlerCache = /* @__PURE__ */ new Map();
@@ -3561,11 +3711,15 @@ var GraphQLManager = class {
3561
3711
  this.port = port;
3562
3712
  this.authorizationService = authorizationService;
3563
3713
  if (this.authConfig) this.authService = new AuthService(this.authConfig);
3714
+ this.driveOwnershipCache = new DriveOwnershipCache(this.reactorClient);
3564
3715
  this.wsServer.setMaxListeners(0);
3565
3716
  }
3566
3717
  async init(coreSubgraphs, authMiddleware) {
3567
3718
  this.#authMiddleware = authMiddleware;
3568
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`);
3569
3723
  const models = (await this.reactorClient.getDocumentModelModules()).results;
3570
3724
  this.cachedDocumentModels = models;
3571
3725
  if (!models.find((it) => it.documentModel.global.name === "DocumentDrive")) throw new Error("DocumentDrive model required");
@@ -3580,12 +3734,12 @@ var GraphQLManager = class {
3580
3734
  if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
3581
3735
  try {
3582
3736
  const driveDoc = await this.reactorClient.get(driveIdOrSlug);
3583
- 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`;
3584
3738
  return Response.json({
3585
3739
  id: driveDoc.header.id,
3586
3740
  slug: driveDoc.header.slug,
3587
3741
  meta: driveDoc.header.meta,
3588
- name: driveDoc.state.global.name,
3742
+ name: driveDoc.state.global.name || driveDoc.header.name,
3589
3743
  icon: driveDoc.state.global.icon ?? void 0,
3590
3744
  ...graphqlEndpoint && { graphqlEndpoint }
3591
3745
  });
@@ -3725,6 +3879,7 @@ var GraphQLManager = class {
3725
3879
  #makeContextFactory() {
3726
3880
  return (request) => {
3727
3881
  const authCtx = getAuthContext(request);
3882
+ const driveId = getRequestDriveId(request);
3728
3883
  const headers = {};
3729
3884
  request.headers.forEach((v, k) => {
3730
3885
  headers[k] = v;
@@ -3733,6 +3888,7 @@ var GraphQLManager = class {
3733
3888
  headers,
3734
3889
  db: this.relationalDb,
3735
3890
  ...this.getAdditionalContextFields(),
3891
+ driveId,
3736
3892
  user: authCtx?.user,
3737
3893
  isAdmin: authCtx ? (addr) => !authCtx.auth_enabled ? true : authCtx.admins.includes(addr.toLowerCase()) : () => true
3738
3894
  });
@@ -3771,7 +3927,7 @@ var GraphQLManager = class {
3771
3927
  if (this.subgraphHandlerCache.has(subgraphPath)) continue;
3772
3928
  const schema = createSchema(this.cachedDocumentModels, subgraph.resolvers, subgraph.typeDefs);
3773
3929
  const rawHandler = await this.gatewayAdapter.createHandler(schema, this.#makeContextFactory());
3774
- const fetchHandler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3930
+ const fetchHandler = this.#composeFetchMiddleware(rawHandler);
3775
3931
  this.subgraphHandlerCache.set(subgraphPath, fetchHandler);
3776
3932
  this.httpAdapter.mount(subgraphPath, fetchHandler);
3777
3933
  if (subgraph.hasSubscriptions) {
@@ -3821,7 +3977,7 @@ var GraphQLManager = class {
3821
3977
  async #createSupergraphGateway() {
3822
3978
  const superGraphPath = path.join(this.path, "graphql");
3823
3979
  const rawHandler = await this.gatewayAdapter.createSupergraphHandler(() => this.#getSubgraphDefinitions(), this.httpServer, this.#makeContextFactory());
3824
- const fetchHandler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
3980
+ const fetchHandler = this.#composeFetchMiddleware(rawHandler);
3825
3981
  this.httpAdapter.mount(superGraphPath, fetchHandler);
3826
3982
  this.#setupSupergraphSSE(superGraphPath);
3827
3983
  if (!this.initialized) {
@@ -3861,9 +4017,20 @@ var GraphQLManager = class {
3861
4017
  schema,
3862
4018
  contextFactory: this.#makeContextFactory()
3863
4019
  });
3864
- const handler = this.#authMiddleware ? this.#authMiddleware(rawHandler) : rawHandler;
4020
+ const handler = this.#composeFetchMiddleware(rawHandler);
3865
4021
  this.httpAdapter.mount(ssePath, handler, { exact: true });
3866
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
+ }
3867
4034
  };
3868
4035
  //#endregion
3869
4036
  //#region src/graphql/packages/resolvers.ts
@@ -4379,6 +4546,21 @@ var ReactorSubgraph = class extends BaseSubgraph {
4379
4546
  await this.assertCanExecuteOperation(documentId, operationType, ctx);
4380
4547
  }
4381
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
+ }
4382
4564
  typeDefs = gql(schema_default);
4383
4565
  resolvers = {
4384
4566
  PHDocument: { operations: async (parent, args, ctx) => {
@@ -4519,6 +4701,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
4519
4701
  if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
4520
4702
  } else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
4521
4703
  const result = await createDocument(this.reactorClient, args);
4704
+ if (result?.id && result.documentType === "powerhouse/document-drive") this.graphqlManager.driveOwnershipCache.add(result.id);
4522
4705
  if (this.authorizationService && ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
4523
4706
  return result;
4524
4707
  } catch (error) {
@@ -4536,6 +4719,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
4536
4719
  if (!ctx.user?.address) throw new GraphQLError("Forbidden: authentication required to create documents");
4537
4720
  } else if (!this.hasGlobalAdminAccess(ctx)) throw new GraphQLError("Forbidden: insufficient permissions to create documents");
4538
4721
  const result = await createEmptyDocument(this.reactorClient, args);
4722
+ if (result?.id && result.documentType === "powerhouse/document-drive") this.graphqlManager.driveOwnershipCache.add(result.id);
4539
4723
  if (this.authorizationService && ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
4540
4724
  return result;
4541
4725
  } catch (error) {
@@ -4610,7 +4794,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
4610
4794
  this.logger.debug("deleteDocument(@args)", args);
4611
4795
  try {
4612
4796
  await this.assertCanWrite(args.identifier, ctx);
4613
- 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;
4614
4801
  } catch (error) {
4615
4802
  this.logger.error("Error in deleteDocument(@args): @Error", error);
4616
4803
  throw error;
@@ -4715,29 +4902,14 @@ dotenv.config();
4715
4902
  const ADMIN_USERS = getAdminUsers();
4716
4903
  //#endregion
4717
4904
  //#region src/graphql/system/version.ts
4718
- let cachedVersion;
4719
- let cachedGitHash;
4720
4905
  function getVersion() {
4721
- if (cachedVersion !== void 0) return cachedVersion;
4722
- cachedVersion = process.env.PH_VERSION ?? process.env.npm_package_version ?? "unknown";
4723
- return cachedVersion;
4906
+ return "6.0.2-staging.8";
4724
4907
  }
4725
4908
  function getGitHash() {
4726
- if (cachedGitHash !== void 0) return cachedGitHash;
4727
- if (process.env.PH_GIT_SHA) {
4728
- cachedGitHash = process.env.PH_GIT_SHA;
4729
- return cachedGitHash;
4730
- }
4731
- try {
4732
- cachedGitHash = execSync("git rev-parse HEAD", { stdio: [
4733
- "ignore",
4734
- "pipe",
4735
- "ignore"
4736
- ] }).toString().trim();
4737
- } catch {
4738
- cachedGitHash = "unknown";
4739
- }
4740
- return cachedGitHash;
4909
+ return "73b61d635a96ec28c98d43b5de72b09fcc9fb0d4";
4910
+ }
4911
+ function getGitUrl() {
4912
+ return buildTreeUrl(getGitHash());
4741
4913
  }
4742
4914
  //#endregion
4743
4915
  //#region src/graphql/system/subgraph.ts
@@ -4748,6 +4920,7 @@ var SystemSubgraph = class extends BaseSubgraph {
4748
4920
  type SystemInfo {
4749
4921
  version: String!
4750
4922
  gitHash: String!
4923
+ gitUrl: String
4751
4924
  }
4752
4925
 
4753
4926
  type Query {
@@ -4756,7 +4929,8 @@ var SystemSubgraph = class extends BaseSubgraph {
4756
4929
  `;
4757
4930
  resolvers = { Query: { system: () => ({
4758
4931
  version: getVersion(),
4759
- gitHash: getGitHash()
4932
+ gitHash: getGitHash(),
4933
+ gitUrl: getGitUrl()
4760
4934
  }) } };
4761
4935
  };
4762
4936
  //#endregion
@@ -5897,102 +6071,6 @@ var DocumentPermissionService = class {
5897
6071
  }
5898
6072
  };
5899
6073
  //#endregion
5900
- //#region src/tracing.ts
5901
- const TEMPO_ENDPOINT = process.env.TEMPO_ENDPOINT || "http://tempo.monitoring.svc.cluster.local:4318/v1/traces";
5902
- const SERVICE_NAME = process.env.OTEL_SERVICE_NAME || "reactor-api";
5903
- const SERVICE_VERSION = process.env.npm_package_version || "unknown";
5904
- const TENANT_ID = process.env.TENANT_ID || "default";
5905
- const METRICS_ENDPOINT = process.env.METRICS_ENDPOINT;
5906
- const METRICS_EXPORT_INTERVAL_MS = parseInt(process.env.METRICS_EXPORT_INTERVAL_MS || "30000", 10);
5907
- const TRACING_ENABLED = process.env.ENABLE_TRACING === "true" || process.env.NODE_ENV === "production";
5908
- if (TRACING_ENABLED) {
5909
- console.log(`Initializing OpenTelemetry tracing for ${SERVICE_NAME}...`);
5910
- console.log(` Tempo endpoint: ${TEMPO_ENDPOINT}`);
5911
- console.log(` Service: ${SERVICE_NAME}`);
5912
- console.log(` Tenant: ${TENANT_ID}`);
5913
- const traceExporter = new OTLPTraceExporter({
5914
- url: TEMPO_ENDPOINT,
5915
- headers: {}
5916
- });
5917
- const resource = new Resource({
5918
- [ATTR_SERVICE_NAME]: SERVICE_NAME,
5919
- [ATTR_SERVICE_VERSION]: SERVICE_VERSION,
5920
- "tenant.id": TENANT_ID,
5921
- "deployment.environment": process.env.NODE_ENV || "development"
5922
- });
5923
- const PROMETHEUS_METRICS_PORT = process.env.PROMETHEUS_METRICS_PORT;
5924
- let metricReader;
5925
- if (PROMETHEUS_METRICS_PORT) {
5926
- const port = parseInt(PROMETHEUS_METRICS_PORT, 10);
5927
- metricReader = new PrometheusExporter({
5928
- port,
5929
- preventServerStart: false
5930
- });
5931
- console.log(` Prometheus /metrics on port ${port}`);
5932
- } else if (METRICS_ENDPOINT) {
5933
- metricReader = new PeriodicExportingMetricReader({
5934
- exporter: new OTLPMetricExporter({ url: METRICS_ENDPOINT }),
5935
- exportIntervalMillis: METRICS_EXPORT_INTERVAL_MS
5936
- });
5937
- console.log(` Metrics OTLP endpoint: ${METRICS_ENDPOINT}`);
5938
- console.log(` Metrics interval: ${METRICS_EXPORT_INTERVAL_MS}ms`);
5939
- }
5940
- const sdk = new NodeSDK({
5941
- resource,
5942
- spanProcessors: [new BatchSpanProcessor(traceExporter)],
5943
- metricReader,
5944
- instrumentations: [getNodeAutoInstrumentations({
5945
- "@opentelemetry/instrumentation-http": {
5946
- enabled: true,
5947
- ignoreIncomingRequestHook: (req) => {
5948
- return req.url === "/health" || req.url === "/ready";
5949
- },
5950
- requireParentforIncomingSpans: false,
5951
- requireParentforOutgoingSpans: false,
5952
- requestHook: (span, request) => {
5953
- span.setAttribute("http.route", request.url || "");
5954
- },
5955
- responseHook: (span, response) => {
5956
- if (response.statusCode) span.setAttribute("http.status_code", response.statusCode);
5957
- }
5958
- },
5959
- "@opentelemetry/instrumentation-express": {
5960
- enabled: true,
5961
- requestHook: (span, info) => {
5962
- if (info.route) span.setAttribute("http.route", info.route);
5963
- }
5964
- },
5965
- "@opentelemetry/instrumentation-graphql": {
5966
- enabled: true,
5967
- mergeItems: true,
5968
- allowValues: true
5969
- },
5970
- "@opentelemetry/instrumentation-pg": {
5971
- enabled: true,
5972
- enhancedDatabaseReporting: true
5973
- },
5974
- "@opentelemetry/instrumentation-redis-4": {
5975
- enabled: true,
5976
- dbStatementSerializer: (cmdName, cmdArgs) => {
5977
- return cmdName;
5978
- }
5979
- }
5980
- })]
5981
- });
5982
- sdk.start();
5983
- console.log("✓ OpenTelemetry tracing initialized");
5984
- process.on("SIGTERM", () => {
5985
- sdk.shutdown().then(() => console.log("Tracing terminated")).catch((error) => console.log("Error terminating tracing", error)).finally(() => process.exit(0));
5986
- });
5987
- }
5988
- async function initTracing() {}
5989
- function isTracingEnabled() {
5990
- return TRACING_ENABLED;
5991
- }
5992
- async function trace(_name, _options, fn) {
5993
- return fn();
5994
- }
5995
- //#endregion
5996
6074
  //#region src/utils/db.ts
5997
6075
  function isPG(connectionString) {
5998
6076
  if (connectionString.startsWith("postgresql://") || connectionString.startsWith("postgres://")) return true;
@@ -6194,7 +6272,6 @@ async function startServer(httpAdapter, port, httpsOptions, logger) {
6194
6272
  * This includes auth configuration, database setup, and package manager initialization.
6195
6273
  */
6196
6274
  async function _setupCommonInfrastructure(options) {
6197
- if (isTracingEnabled()) await initTracing();
6198
6275
  const port = options.port ?? DEFAULT_PORT;
6199
6276
  const { adapter: httpAdapter } = createHttpAdapter("express");
6200
6277
  let admins = [];
@@ -6238,7 +6315,7 @@ async function _setupCommonInfrastructure(options) {
6238
6315
  authFetchMiddleware = createAuthFetchMiddleware(authService);
6239
6316
  }
6240
6317
  const dbClosers = [];
6241
- const { relationalDb, analyticsStore, closers: analyticsClosers } = await trace("reactor-api.init.database", { tags: { "resource.name": "database" } }, () => initializeDatabaseAndAnalytics(options.dbPath, options.pgliteFactory));
6318
+ const { relationalDb, analyticsStore, closers: analyticsClosers } = await initializeDatabaseAndAnalytics(options.dbPath, options.pgliteFactory);
6242
6319
  dbClosers.push(...analyticsClosers);
6243
6320
  let documentPermissionService = options.documentPermissionService;
6244
6321
  if (!documentPermissionService && DOCUMENT_PERMISSIONS_ENABLED === "true") {
@@ -6412,9 +6489,9 @@ function buildApiDispose(args) {
6412
6489
  * @returns The API server components along with the created client instances.
6413
6490
  */
6414
6491
  async function initializeAndStartAPI(clientInitializer, options, processorApp) {
6415
- const { port, httpAdapter, authFetchMiddleware, authService, auth, relationalDb, analyticsStore, documentPermissionService, authorizationService, attachments, packages, dbClosers } = await trace("reactor-api.setup.infrastructure", { tags: { "resource.name": "infrastructure" } }, () => _setupCommonInfrastructure(options));
6416
- const { documentModels, processors, subgraphs } = await trace("reactor-api.packages.init", { tags: { "resource.name": "packages" } }, () => packages.init());
6417
- 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);
6418
6495
  const reactorClient = reactorClientModule.client;
6419
6496
  const syncManager = reactorClientModule.reactorModule?.syncModule?.syncManager;
6420
6497
  if (!syncManager) throw new Error("SyncManager not available from ReactorClientModule");
@@ -6529,6 +6606,7 @@ var PackageManagementService = class {
6529
6606
  }
6530
6607
  };
6531
6608
  //#endregion
6532
- 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 };
6533
6610
 
6534
- //# sourceMappingURL=index.mjs.map
6611
+ //# sourceMappingURL=index.mjs.map
6612
+ //# debugId=81df4612-dfa1-5785-acec-bca9f4770f2d