@powerhousedao/reactor-api 6.2.3-dev.2 → 6.2.3-dev.21

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.
Files changed (30) hide show
  1. package/dist/{adapter-gateway-apollo-DdamNLPT.mjs → adapter-gateway-apollo-CxJNBVyD.mjs} +7 -8
  2. package/dist/adapter-gateway-apollo-CxJNBVyD.mjs.map +1 -0
  3. package/dist/adapter-gateway-apollo-DowrX6E_.mjs +4 -0
  4. package/dist/{adapter-gateway-mercurius-CLmTtNJC.mjs → adapter-gateway-mercurius-CZVy88Za.mjs} +6 -5
  5. package/dist/adapter-gateway-mercurius-CZVy88Za.mjs.map +1 -0
  6. package/dist/adapter-gateway-stitching-CaZjvRzx.mjs +316 -0
  7. package/dist/adapter-gateway-stitching-CaZjvRzx.mjs.map +1 -0
  8. package/dist/{adapter-http-express-54gdI9bu.mjs → adapter-http-express-B39rPhqH.mjs} +4 -4
  9. package/dist/{adapter-http-express-54gdI9bu.mjs.map → adapter-http-express-B39rPhqH.mjs.map} +1 -1
  10. package/dist/{adapter-http-fastify-DmccBVOe.mjs → adapter-http-fastify-BwXO6rqE.mjs} +4 -4
  11. package/dist/{adapter-http-fastify-DmccBVOe.mjs.map → adapter-http-fastify-BwXO6rqE.mjs.map} +1 -1
  12. package/dist/index.d.mts +303 -18
  13. package/dist/index.d.mts.map +1 -1
  14. package/dist/index.mjs +523 -60
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/{path-normalize-DWWFLiy6.mjs → path-normalize-BOQwY9wR.mjs} +3 -3
  17. package/dist/{path-normalize-DWWFLiy6.mjs.map → path-normalize-BOQwY9wR.mjs.map} +1 -1
  18. package/dist/{utils-XWVecaNn.mjs → pieces-eqj2RKgd.mjs} +95 -5
  19. package/dist/pieces-eqj2RKgd.mjs.map +1 -0
  20. package/dist/src/packages/vite-loader.d.mts +6 -1
  21. package/dist/src/packages/vite-loader.d.mts.map +1 -1
  22. package/dist/src/packages/vite-loader.mjs +65 -6
  23. package/dist/src/packages/vite-loader.mjs.map +1 -1
  24. package/dist/{types-Bz2niABC.d.mts → types-BXjLvTe3.d.mts} +25 -2
  25. package/dist/types-BXjLvTe3.d.mts.map +1 -0
  26. package/package.json +17 -14
  27. package/dist/adapter-gateway-apollo-DdamNLPT.mjs.map +0 -1
  28. package/dist/adapter-gateway-mercurius-CLmTtNJC.mjs.map +0 -1
  29. package/dist/types-Bz2niABC.d.mts.map +0 -1
  30. package/dist/utils-XWVecaNn.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,19 +1,20 @@
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]="46ad8545-02e9-5fb8-b003-7b9b93f82f75")}catch(e){}}();
3
- import { _ as createAuthorizationService, a as isSubgraphClass, b as MalformedStoredOperationError, d as loadSubgraphs, f as BaseSubgraph, g as AuthorizedDocumentHandle, h as AuthorizationPolicy, i as buildGraphqlOperations, l as loadDocumentModels, m as createCanonicalDocumentIdResolver, n as buildGraphQlDriveDocument, o as debounce, p as CanonicalDocumentIdResolutionError, r as buildGraphqlOperation, s as extractUpgradeManifests, t as buildGraphQlDocument, u as loadProcessors, v as AuthEvaluationUnsupportedError, y as ForbiddenError } from "./utils-XWVecaNn.mjs";
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]="cdb25466-37b1-55fd-be52-a82be4286288")}catch(e){}}();
3
+ import { C as createAuthorizationService, D as MalformedStoredOperationError, E as ForbiddenError, S as AuthorizedDocumentHandle, T as AuthenticationRequiredError, _ as loadSubgraphs, a as piecesFromCdnList, b as createCanonicalDocumentIdResolver, c as buildGraphQlDriveDocument, d as isSubgraphClass, f as debounce, g as loadProcessors, h as loadDocumentModels, i as pieceListLocation, l as buildGraphqlOperation, o as piecesFromListModule, p as extractUpgradeManifests, r as PIECE_SOURCE_LIST, s as buildGraphQlDocument, u as buildGraphqlOperations, v as BaseSubgraph, w as AuthEvaluationUnsupportedError, x as AuthorizationPolicy, y as CanonicalDocumentIdResolutionError } from "./pieces-eqj2RKgd.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, { join } from "node:path";
10
+ import path, { isAbsolute, join } from "node:path";
11
11
  import { match } from "path-to-regexp";
12
12
  import posix from "node:path/posix";
13
13
  import { Readable } from "node:stream";
14
- import fs, { readFileSync, watchFile } from "node:fs";
14
+ import fs, { existsSync, readFileSync, watchFile } from "node:fs";
15
15
  import { pipeline } from "node:stream/promises";
16
16
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
17
+ import { pathToFileURL } from "node:url";
17
18
  import { buildSubgraphSchema } from "@apollo/subgraph";
18
19
  import { typeDefs } from "@powerhousedao/document-engineering/graphql";
19
20
  import { camelCase, kebabCase, pascalCase } from "change-case";
@@ -311,28 +312,40 @@ function createAuthFetchMiddleware(authService) {
311
312
  async function createGatewayAdapter(type, logger) {
312
313
  switch (type) {
313
314
  case "apollo": {
314
- const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-DdamNLPT.mjs");
315
+ const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-DowrX6E_.mjs");
315
316
  return new ApolloGatewayAdapter(logger);
316
317
  }
317
318
  case "mercurius": {
318
- const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-CLmTtNJC.mjs");
319
+ const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-CZVy88Za.mjs");
319
320
  return new MercuriusGatewayAdapter(logger);
320
321
  }
322
+ case "stitching": {
323
+ const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-CaZjvRzx.mjs");
324
+ return new StitchingGatewayAdapter(logger);
325
+ }
321
326
  }
322
327
  }
323
328
  async function createHttpAdapter(type) {
324
329
  switch (type) {
325
330
  case "express": {
326
- const { createExpressHttpAdapter } = await import("./adapter-http-express-54gdI9bu.mjs");
331
+ const { createExpressHttpAdapter } = await import("./adapter-http-express-B39rPhqH.mjs");
327
332
  return createExpressHttpAdapter();
328
333
  }
329
334
  case "fastify": {
330
- const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-DmccBVOe.mjs");
335
+ const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-BwXO6rqE.mjs");
331
336
  return createFastifyHttpAdapter();
332
337
  }
333
338
  }
334
339
  }
335
340
  //#endregion
341
+ //#region src/graphql/gateway/types.ts
342
+ /** No authenticated caller resolved, and REQUIRE_AUTHENTICATED_CALLER wants one. */
343
+ const WS_CLOSE_REASON_AUTHENTICATION_REQUIRED = "authentication-required";
344
+ /** A bearer was sent and could not be verified. */
345
+ const WS_CLOSE_REASON_BEARER_REJECTED = "bearer-rejected";
346
+ /** Every reason a refusal closes with, for a client matching on the set. */
347
+ const WS_AUTH_CLOSE_REASONS = [WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE_REASON_BEARER_REJECTED];
348
+ //#endregion
336
349
  //#region src/http/namespace.ts
337
350
  /**
338
351
  * A package's namespace is its npm name, used verbatim as one or two path
@@ -2218,6 +2231,16 @@ const PhDocumentFieldsFragmentDoc = gql`
2218
2231
  lastModifiedAtUtcIso
2219
2232
  }
2220
2233
  `;
2234
+ const DocumentRelationshipFieldsFragmentDoc = gql`
2235
+ fragment DocumentRelationshipFields on DocumentRelationship {
2236
+ sourceId
2237
+ targetId
2238
+ relationshipType
2239
+ metadata
2240
+ createdAt
2241
+ updatedAt
2242
+ }
2243
+ `;
2221
2244
  const GetDocumentModelsDocument = gql`
2222
2245
  query GetDocumentModels($namespace: String, $paging: PagingInput) {
2223
2246
  documentModels(namespace: $namespace, paging: $paging) {
@@ -2346,6 +2369,54 @@ const GetDocumentIncomingRelationshipsDocument = gql`
2346
2369
  }
2347
2370
  ${PhDocumentFieldsFragmentDoc}
2348
2371
  `;
2372
+ const GetDocumentOutgoingRelationshipEdgesDocument = gql`
2373
+ query GetDocumentOutgoingRelationshipEdges(
2374
+ $sourceIdentifier: String!
2375
+ $relationshipType: String
2376
+ $view: ViewFilterInput
2377
+ $paging: PagingInput
2378
+ ) {
2379
+ documentOutgoingRelationshipEdges(
2380
+ sourceIdentifier: $sourceIdentifier
2381
+ relationshipType: $relationshipType
2382
+ view: $view
2383
+ paging: $paging
2384
+ ) {
2385
+ items {
2386
+ ...DocumentRelationshipFields
2387
+ }
2388
+ totalCount
2389
+ hasNextPage
2390
+ hasPreviousPage
2391
+ cursor
2392
+ }
2393
+ }
2394
+ ${DocumentRelationshipFieldsFragmentDoc}
2395
+ `;
2396
+ const GetDocumentIncomingRelationshipEdgesDocument = gql`
2397
+ query GetDocumentIncomingRelationshipEdges(
2398
+ $targetIdentifier: String!
2399
+ $relationshipType: String
2400
+ $view: ViewFilterInput
2401
+ $paging: PagingInput
2402
+ ) {
2403
+ documentIncomingRelationshipEdges(
2404
+ targetIdentifier: $targetIdentifier
2405
+ relationshipType: $relationshipType
2406
+ view: $view
2407
+ paging: $paging
2408
+ ) {
2409
+ items {
2410
+ ...DocumentRelationshipFields
2411
+ }
2412
+ totalCount
2413
+ hasNextPage
2414
+ hasPreviousPage
2415
+ cursor
2416
+ }
2417
+ }
2418
+ ${DocumentRelationshipFieldsFragmentDoc}
2419
+ `;
2349
2420
  const FindDocumentsDocument = gql`
2350
2421
  query FindDocuments(
2351
2422
  $search: SearchFilterInput
@@ -2536,12 +2607,34 @@ const AddRelationshipDocument = gql`
2536
2607
  $sourceIdentifier: String!
2537
2608
  $targetIdentifier: String!
2538
2609
  $relationshipType: String!
2610
+ $metadata: JSONObject
2539
2611
  $branch: String
2540
2612
  ) {
2541
2613
  addRelationship(
2542
2614
  sourceIdentifier: $sourceIdentifier
2543
2615
  targetIdentifier: $targetIdentifier
2544
2616
  relationshipType: $relationshipType
2617
+ metadata: $metadata
2618
+ branch: $branch
2619
+ ) {
2620
+ ...PHDocumentFields
2621
+ }
2622
+ }
2623
+ ${PhDocumentFieldsFragmentDoc}
2624
+ `;
2625
+ const UpdateRelationshipDocument = gql`
2626
+ mutation UpdateRelationship(
2627
+ $sourceIdentifier: String!
2628
+ $targetIdentifier: String!
2629
+ $relationshipType: String!
2630
+ $metadata: JSONObject
2631
+ $branch: String
2632
+ ) {
2633
+ updateRelationship(
2634
+ sourceIdentifier: $sourceIdentifier
2635
+ targetIdentifier: $targetIdentifier
2636
+ relationshipType: $relationshipType
2637
+ metadata: $metadata
2545
2638
  branch: $branch
2546
2639
  ) {
2547
2640
  ...PHDocumentFields
@@ -2735,6 +2828,12 @@ function getSdk(requester) {
2735
2828
  GetDocumentIncomingRelationships(variables, options) {
2736
2829
  return requester(GetDocumentIncomingRelationshipsDocument, variables, options);
2737
2830
  },
2831
+ GetDocumentOutgoingRelationshipEdges(variables, options) {
2832
+ return requester(GetDocumentOutgoingRelationshipEdgesDocument, variables, options);
2833
+ },
2834
+ GetDocumentIncomingRelationshipEdges(variables, options) {
2835
+ return requester(GetDocumentIncomingRelationshipEdgesDocument, variables, options);
2836
+ },
2738
2837
  FindDocuments(variables, options) {
2739
2838
  return requester(FindDocumentsDocument, variables, options);
2740
2839
  },
@@ -2768,6 +2867,9 @@ function getSdk(requester) {
2768
2867
  AddRelationship(variables, options) {
2769
2868
  return requester(AddRelationshipDocument, variables, options);
2770
2869
  },
2870
+ UpdateRelationship(variables, options) {
2871
+ return requester(UpdateRelationshipDocument, variables, options);
2872
+ },
2771
2873
  RemoveRelationship(variables, options) {
2772
2874
  return requester(RemoveRelationshipDocument, variables, options);
2773
2875
  },
@@ -2846,6 +2948,32 @@ function toPhDocumentResultPage(result) {
2846
2948
  };
2847
2949
  }
2848
2950
  /**
2951
+ * Converts a relationship edge from the indexer to GraphQL format.
2952
+ */
2953
+ function toGqlDocumentRelationship(relationship) {
2954
+ return {
2955
+ sourceId: relationship.sourceId,
2956
+ targetId: relationship.targetId,
2957
+ relationshipType: relationship.relationshipType,
2958
+ metadata: relationship.metadata ?? null,
2959
+ createdAt: relationship.createdAt,
2960
+ updatedAt: relationship.updatedAt
2961
+ };
2962
+ }
2963
+ /**
2964
+ * Converts a PagedResults of relationship edges to GraphQL
2965
+ * DocumentRelationshipResultPage format
2966
+ */
2967
+ function toDocumentRelationshipResultPage(result) {
2968
+ return {
2969
+ cursor: result.nextCursor ?? null,
2970
+ hasNextPage: !!result.nextCursor,
2971
+ hasPreviousPage: !!result.options.cursor,
2972
+ items: result.results.map(toGqlDocumentRelationship),
2973
+ totalCount: result.totalCount ?? result.results.length
2974
+ };
2975
+ }
2976
+ /**
2849
2977
  * Converts a PHDocument from ReactorClient to GraphQL PhDocument format
2850
2978
  */
2851
2979
  function toGqlPhDocument(doc) {
@@ -3291,6 +3419,54 @@ async function documentIncomingRelationships(reactorClient, args, subject) {
3291
3419
  throw new GraphQLError(`Failed to convert incoming relationships to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3292
3420
  }
3293
3421
  }
3422
+ async function documentOutgoingRelationshipEdges(reactorClient, args, subject) {
3423
+ const view = toRelationshipViewFilter(args.view, subject);
3424
+ const paging = toRelationshipPagingOptions(args.paging);
3425
+ let result;
3426
+ try {
3427
+ result = await reactorClient.getOutgoingRelationshipEdges(args.sourceIdentifier, fromInputMaybe(args.relationshipType), view, paging);
3428
+ } catch (error) {
3429
+ throw new GraphQLError(`Failed to fetch outgoing relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
3430
+ }
3431
+ try {
3432
+ return toDocumentRelationshipResultPage(result);
3433
+ } catch (error) {
3434
+ throw new GraphQLError(`Failed to convert outgoing relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3435
+ }
3436
+ }
3437
+ async function documentIncomingRelationshipEdges(reactorClient, args, subject) {
3438
+ const view = toRelationshipViewFilter(args.view, subject);
3439
+ const paging = toRelationshipPagingOptions(args.paging);
3440
+ let result;
3441
+ try {
3442
+ result = await reactorClient.getIncomingRelationshipEdges(args.targetIdentifier, fromInputMaybe(args.relationshipType), view, paging);
3443
+ } catch (error) {
3444
+ throw new GraphQLError(`Failed to fetch incoming relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
3445
+ }
3446
+ try {
3447
+ return toDocumentRelationshipResultPage(result);
3448
+ } catch (error) {
3449
+ throw new GraphQLError(`Failed to convert incoming relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3450
+ }
3451
+ }
3452
+ function toRelationshipViewFilter(input, subject) {
3453
+ if (!input) return subject ? { subject } : void 0;
3454
+ return {
3455
+ subject,
3456
+ branch: fromInputMaybe(input.branch),
3457
+ scopes: toMutableArray(fromInputMaybe(input.scopes))
3458
+ };
3459
+ }
3460
+ function toRelationshipPagingOptions(input) {
3461
+ if (!input) return;
3462
+ const cursor = fromInputMaybe(input.cursor);
3463
+ const limit = fromInputMaybe(input.limit);
3464
+ if (!cursor && !limit) return;
3465
+ return {
3466
+ cursor: cursor || "",
3467
+ limit: limit || 10
3468
+ };
3469
+ }
3294
3470
  async function findDocuments(reactorClient, args, subject) {
3295
3471
  let view = subject ? { subject } : void 0;
3296
3472
  if (args.view) view = {
@@ -3622,9 +3798,10 @@ async function setPreferredEditor(reactorClient, args, signal) {
3622
3798
  }
3623
3799
  async function addRelationship(reactorClient, args) {
3624
3800
  const branch = fromInputMaybe(args.branch);
3801
+ const metadata = fromInputMaybe(args.metadata);
3625
3802
  let result;
3626
3803
  try {
3627
- result = await reactorClient.addRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, branch);
3804
+ result = await reactorClient.addRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, metadata, branch);
3628
3805
  } catch (error) {
3629
3806
  throw new GraphQLError(`Failed to add relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
3630
3807
  }
@@ -3634,6 +3811,21 @@ async function addRelationship(reactorClient, args) {
3634
3811
  throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3635
3812
  }
3636
3813
  }
3814
+ async function updateRelationship(reactorClient, args) {
3815
+ const branch = fromInputMaybe(args.branch);
3816
+ const metadata = fromInputMaybe(args.metadata) ?? null;
3817
+ let result;
3818
+ try {
3819
+ result = await reactorClient.updateRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, metadata, branch);
3820
+ } catch (error) {
3821
+ throw new GraphQLError(`Failed to update relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
3822
+ }
3823
+ try {
3824
+ return toGqlPhDocument(result);
3825
+ } catch (error) {
3826
+ throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
3827
+ }
3828
+ }
3637
3829
  async function removeRelationship(reactorClient, args) {
3638
3830
  const branch = fromInputMaybe(args.branch);
3639
3831
  let result;
@@ -3774,14 +3966,16 @@ async function collectHeldSyncOperations(syncOps, gate, subject, signal) {
3774
3966
  * client ordinal the switchboard has successfully applied, so the client
3775
3967
  * can trim its own outbox)
3776
3968
  */
3777
- function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set()) {
3969
+ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set(), gatedOpIds) {
3970
+ const isHeld = (syncOp) => heldOpIds.has(syncOp.id) || gatedOpIds !== void 0 && !gatedOpIds.has(syncOp.id);
3778
3971
  let remote;
3779
3972
  try {
3780
3973
  remote = syncManager.getById(args.channelId);
3781
3974
  } catch (error) {
3782
3975
  throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
3783
3976
  }
3784
- const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId) && !heldOpIds.has(syncOp.id)).map((syncOp) => ({
3977
+ remote.channel.notePoll();
3978
+ const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId) && !isHeld(syncOp)).map((syncOp) => ({
3785
3979
  documentId: syncOp.documentId,
3786
3980
  error: syncOp.error?.message ?? "Unknown error",
3787
3981
  errorType: syncOperationErrorType(syncOp.error),
@@ -3830,7 +4024,7 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
3830
4024
  hasMore = true;
3831
4025
  break;
3832
4026
  }
3833
- if (heldOpIds.has(syncOp.id)) continue;
4027
+ if (isHeld(syncOp)) continue;
3834
4028
  syncOp.deliveredCount ??= 0;
3835
4029
  syncOp.emittedCount ??= 0;
3836
4030
  while (syncOp.deliveredCount < syncOp.emittedCount && syncOp.operations[syncOp.deliveredCount].context.ordinal <= args.outboxLatest) syncOp.deliveredCount += 1;
@@ -3905,6 +4099,7 @@ function pushSyncEnvelopes(syncManager, args) {
3905
4099
  throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
3906
4100
  }
3907
4101
  if (!envelope.operations || envelope.operations.length === 0) continue;
4102
+ remote.channel.notePoll();
3908
4103
  const syncOps = envelopesToSyncOperations(envelope, remote.meta.name);
3909
4104
  if (!remoteSyncOps.has(remote)) remoteSyncOps.set(remote, []);
3910
4105
  remoteSyncOps.get(remote).push(...syncOps);
@@ -4308,6 +4503,32 @@ const DefaultFeatureFlags = { enableDocumentModelSubgraphs: true };
4308
4503
  * regenerateDocumentModelSubgraphs).
4309
4504
  */
4310
4505
  const DOCUMENT_MODEL_SUBGRAPH_SOURCE = "document-models";
4506
+ function refuseConnection(connection, reason) {
4507
+ const { socket } = connection;
4508
+ socket?.close?.(4403, reason);
4509
+ return false;
4510
+ }
4511
+ function createWsAuthHandlers(opts) {
4512
+ const resolved = /* @__PURE__ */ new WeakMap();
4513
+ return {
4514
+ onConnect: async (connectionParams, connection) => {
4515
+ let user = null;
4516
+ if (opts.authService) try {
4517
+ user = await opts.authService.authenticateWebSocketConnection(connectionParams);
4518
+ } catch (error) {
4519
+ opts.logger.warn("Refusing WebSocket connection: @error", error instanceof Error ? error.message : error);
4520
+ return refuseConnection(connection, WS_CLOSE_REASON_BEARER_REJECTED);
4521
+ }
4522
+ if (user === null && opts.requireAuthenticatedCaller()) {
4523
+ opts.logger.warn("Refusing anonymous WebSocket connection: an authenticated caller is required");
4524
+ return refuseConnection(connection, WS_CLOSE_REASON_AUTHENTICATION_REQUIRED);
4525
+ }
4526
+ resolved.set(connection, user ?? void 0);
4527
+ return true;
4528
+ },
4529
+ context: (connectionParams, connection) => Promise.resolve(opts.buildContext(connectionParams, resolved.get(connection)))
4530
+ };
4531
+ }
4311
4532
  var GraphQLManager = class {
4312
4533
  initialized = false;
4313
4534
  coreSubgraphsMap = /* @__PURE__ */ new Map();
@@ -4380,13 +4601,17 @@ var GraphQLManager = class {
4380
4601
  this.httpAdapter.setupMiddleware({ bodyLimit: "50mb" });
4381
4602
  const driveRoutePath = path.posix.join(this.path, "d/:drive");
4382
4603
  const driveMatcher = match(driveRoutePath);
4383
- this.httpAdapter.mount(driveRoutePath, async (request) => {
4604
+ const driveInfoHandler = async (request) => {
4384
4605
  const url = new URL(request.url);
4385
4606
  const matched = driveMatcher(url.pathname);
4386
4607
  const driveIdOrSlug = matched ? matched.params.drive : void 0;
4387
4608
  if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
4388
4609
  try {
4389
4610
  const driveDoc = await this.reactorClient.get(driveIdOrSlug);
4611
+ if (!await this.authorizationService.canRead(driveDoc.header.id, getAuthContext(request)?.user?.address)) {
4612
+ this.logger.debug(`Drive read refused: ${driveIdOrSlug}`);
4613
+ return Response.json({ error: "Drive not found" }, { status: 404 });
4614
+ }
4390
4615
  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`;
4391
4616
  return Response.json({
4392
4617
  id: driveDoc.header.id,
@@ -4400,7 +4625,8 @@ var GraphQLManager = class {
4400
4625
  this.logger.debug(`Drive not found: ${driveIdOrSlug}`, error);
4401
4626
  return Response.json({ error: "Drive not found" }, { status: 404 });
4402
4627
  }
4403
- });
4628
+ };
4629
+ this.httpAdapter.mount(driveRoutePath, this.#authMiddleware ? this.#authMiddleware(driveInfoHandler) : driveInfoHandler);
4404
4630
  this.logger.info(`Registered REST endpoint: GET ${driveRoutePath}`);
4405
4631
  await this.#setupCoreSubgraphs("graphql", coreSubgraphs);
4406
4632
  if (this.featureFlags.enableDocumentModelSubgraphs) await this.#setupDocumentModelSubgraphs("graphql", models);
@@ -4691,17 +4917,6 @@ var GraphQLManager = class {
4691
4917
  ...fields
4692
4918
  };
4693
4919
  }
4694
- async #createWebSocketContext(connectionParams) {
4695
- let user = null;
4696
- if (this.authService) user = await this.authService.authenticateWebSocketConnection(connectionParams);
4697
- const context = {
4698
- headers: connectionParams,
4699
- db: this.relationalDb,
4700
- ...this.getAdditionalContextFields()
4701
- };
4702
- if (user) context.user = user;
4703
- return context;
4704
- }
4705
4920
  #makeContextFactory() {
4706
4921
  return (request) => {
4707
4922
  const authCtx = getAuthContext(request);
@@ -4720,7 +4935,20 @@ var GraphQLManager = class {
4720
4935
  };
4721
4936
  }
4722
4937
  #makeWsContextFactory() {
4723
- return (connectionParams) => this.#createWebSocketContext(connectionParams);
4938
+ return createWsAuthHandlers({
4939
+ authService: this.authService,
4940
+ requireAuthenticatedCaller: () => this.#requireAuthMiddleware !== void 0,
4941
+ logger: this.logger,
4942
+ buildContext: (connectionParams, user) => {
4943
+ const context = {
4944
+ headers: connectionParams,
4945
+ db: this.relationalDb,
4946
+ ...this.getAdditionalContextFields()
4947
+ };
4948
+ if (user) context.user = user;
4949
+ return context;
4950
+ }
4951
+ });
4724
4952
  }
4725
4953
  setSupergraph(supergraph, subgraphs) {
4726
4954
  const accepted = [];
@@ -4823,11 +5051,15 @@ var GraphQLManager = class {
4823
5051
  #getSubgraphDefinitions() {
4824
5052
  const subgraphs = this.#getAllSubgraphs();
4825
5053
  const herokuOrLocal = process.env.HEROKU_APP_DEFAULT_DOMAIN_NAME ? `https://${process.env.HEROKU_APP_DEFAULT_DOMAIN_NAME}` : `http://localhost:${this.port}`;
4826
- return Array.from(subgraphs.entries()).map(([subgraphPath, subgraph]) => ({
4827
- name: subgraphPath.replace("/", ":"),
4828
- typeDefs: this.#buildSubgraphSchemaModule(subgraph).typeDefs,
4829
- url: `${herokuOrLocal}${subgraphPath}`
4830
- }));
5054
+ return Array.from(subgraphs.entries()).map(([subgraphPath, subgraph]) => {
5055
+ const module = this.#buildSubgraphSchemaModule(subgraph);
5056
+ return {
5057
+ name: subgraphPath.replace("/", ":"),
5058
+ typeDefs: module.typeDefs,
5059
+ resolvers: module.resolvers,
5060
+ url: `${herokuOrLocal}${subgraphPath}`
5061
+ };
5062
+ });
4831
5063
  }
4832
5064
  async #createSupergraphGateway() {
4833
5065
  const superGraphPath = path.posix.join(this.path, "graphql");
@@ -5190,6 +5422,21 @@ const PHDocumentResultPageDTO = z.object({
5190
5422
  hasPreviousPage: z.boolean(),
5191
5423
  cursor: z.string().nullable().optional()
5192
5424
  }).strip();
5425
+ const DocumentRelationshipDTO = z.object({
5426
+ sourceId: z.string(),
5427
+ targetId: z.string(),
5428
+ relationshipType: z.string(),
5429
+ metadata: JSONObjectDTO.nullable().optional(),
5430
+ createdAt: DateTimeDTO,
5431
+ updatedAt: DateTimeDTO
5432
+ }).strip();
5433
+ const DocumentRelationshipResultPageDTO = z.object({
5434
+ items: z.array(DocumentRelationshipDTO),
5435
+ totalCount: z.number().int(),
5436
+ hasNextPage: z.boolean(),
5437
+ hasPreviousPage: z.boolean(),
5438
+ cursor: z.string().nullable().optional()
5439
+ }).strip();
5193
5440
  const DocumentWithChildrenDTO = z.object({
5194
5441
  document: PHDocumentDTO,
5195
5442
  childIds: z.array(z.string())
@@ -5293,6 +5540,12 @@ const operationValidators = {
5293
5540
  GetDocumentIncomingRelationships: (data) => {
5294
5541
  if (data.documentIncomingRelationships) PHDocumentResultPageDTO.parse(data.documentIncomingRelationships);
5295
5542
  },
5543
+ GetDocumentOutgoingRelationshipEdges: (data) => {
5544
+ if (data.documentOutgoingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentOutgoingRelationshipEdges);
5545
+ },
5546
+ GetDocumentIncomingRelationshipEdges: (data) => {
5547
+ if (data.documentIncomingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentIncomingRelationshipEdges);
5548
+ },
5296
5549
  FindDocuments: (data) => {
5297
5550
  if (data.findDocuments) PHDocumentResultPageDTO.parse(data.findDocuments);
5298
5551
  },
@@ -5326,6 +5579,9 @@ const operationValidators = {
5326
5579
  AddRelationship: (data) => {
5327
5580
  if (data.addRelationship) PHDocumentDTO.parse(data.addRelationship);
5328
5581
  },
5582
+ UpdateRelationship: (data) => {
5583
+ if (data.updateRelationship) PHDocumentDTO.parse(data.updateRelationship);
5584
+ },
5329
5585
  RemoveRelationship: (data) => {
5330
5586
  if (data.removeRelationship) PHDocumentDTO.parse(data.removeRelationship);
5331
5587
  },
@@ -5367,7 +5623,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
5367
5623
  }
5368
5624
  //#endregion
5369
5625
  //#region src/graphql/reactor/schema.graphql
5370
- var schema_default = "# Scalar types (for codegen - also defined in create-schema.ts)\nscalar JSONObject\nscalar DateTime\n\n# Input types\ninput PagingInput {\n limit: Int\n offset: Int\n cursor: String\n}\n\ninput ViewFilterInput {\n branch: String\n scopes: [String!]\n}\n\ninput SearchFilterInput {\n type: String\n parentId: String\n identifiers: [String!]\n}\n\ninput OperationsFilterInput {\n documentId: String!\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\ninput DocumentOperationsFilterInput {\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\n# Enums\nenum PropagationMode {\n CASCADE\n ORPHAN\n}\n\nenum DocumentChangeType {\n CREATED\n DELETED\n UPDATED\n PARENT_ADDED\n PARENT_REMOVED\n CHILD_ADDED\n CHILD_REMOVED\n}\n\n# Object types\ntype DocumentModelGlobalState {\n id: String!\n name: String!\n namespace: String\n version: String\n specification: JSONObject!\n}\n\ntype DocumentModelResultPage {\n items: [DocumentModelGlobalState!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype Revision {\n scope: String!\n revision: Int!\n}\n\ntype PHDocument {\n id: String!\n slug: String\n preferredEditor: String\n name: String!\n documentType: String!\n state: JSONObject!\n revisionsList: [Revision!]!\n createdAtUtcIso: DateTime!\n lastModifiedAtUtcIso: DateTime!\n operations(\n filter: DocumentOperationsFilterInput\n paging: PagingInput\n ): ReactorOperationResultPage\n}\n\ntype PHDocumentResultPage {\n items: [PHDocument!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype ReactorOperationResultPage {\n items: [ReactorOperation!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype DeadLetterInfo {\n documentId: String!\n error: String!\n \"\"\"\n How the origin classified the failure. A peer needs this to mirror the origin's\n quarantine decision; without it a held auth operation would freeze the document\n on the peer side while the origin kept syncing.\n \"\"\"\n errorType: String\n jobId: String!\n branch: String!\n scopes: [String!]!\n operationCount: Int!\n}\n\ntype PollSyncEnvelopesResult {\n envelopes: [SyncEnvelope!]!\n ackOrdinal: Int!\n deadLetters: [DeadLetterInfo!]!\n hasMore: Boolean!\n}\n\ntype DocumentWithChildren {\n document: PHDocument!\n childIds: [String!]!\n}\n\ntype MoveRelationshipResult {\n source: PHDocument!\n target: PHDocument!\n}\n\ntype JobInfo {\n id: String!\n status: String!\n\n \"\"\"\n What the job produced, once it has produced anything. Null until then, which\n is the state every job is in when it is handed back from a submission.\n \"\"\"\n result: JSONObject\n\n error: String\n createdAt: DateTime!\n completedAt: DateTime\n}\n\ntype DocumentChangeEvent {\n type: DocumentChangeType!\n documents: [PHDocument!]!\n context: DocumentChangeContext\n}\n\ntype DocumentChangeContext {\n parentId: String\n childId: String\n}\n\ntype JobChangeEvent {\n jobId: String!\n status: String!\n result: JSONObject!\n error: String\n}\n\ntype ReactorSignerUser {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ntype ReactorSignerApp {\n name: String!\n key: String!\n}\n\ntype ReactorSigner {\n user: ReactorSignerUser\n app: ReactorSignerApp\n signatures: [String!]!\n}\n\ntype ActionContext {\n signer: ReactorSigner\n}\n\ntype Action {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContext\n}\n\n\"\"\"\nOne operation an authorization preflight predicts a verdict for.\n\n`input` is what a conditional grant reads, so a candidate standing for a\nfilled-in form carries that form's input. Omitting it predicts the verdict an\nempty input earns, not the verdict the filled-in form will get.\n\"\"\"\ninput ActionCandidateInput {\n scope: String!\n type: String!\n input: JSONObject\n}\n\nenum AuthDecision {\n ALLOW\n DENY\n}\n\n\"\"\"\nOne candidate's predicted verdict. `reason` carries the refusal a DENY is\nrecorded with, and is null on an ALLOW.\n\"\"\"\ntype ActionEvaluation {\n decision: AuthDecision!\n reason: String\n}\n\n\"\"\"\nThe predicted verdicts for a set of candidates, in the order they were given,\nwith the aggregates a caller branches on.\n\nThe aggregates are redundant -- a verdict is binary -- and all four are returned\nso a caller reads the one its question is phrased in rather than negating\nanother. Over no candidates every aggregate is false: nothing is allowed and\nnothing is denied.\n\"\"\"\ntype ActionEvaluations {\n evaluations: [ActionEvaluation!]!\n allAllowed: Boolean!\n anyAllowed: Boolean!\n allDenied: Boolean!\n anyDenied: Boolean!\n}\n\n# Input types for sync operations\n\"\"\"\nAn action's context, as a caller submits it.\n\nDeclares every field `ActionContext` carries, because an input object rejects a\nfield it does not declare: an action stamped with the head it applies to - which\nis what a signing client sends - is refused outright if these are missing, and\nthat refusal is a whole-request one, not a per-field one.\n\n`prevOpHash` and `prevOpIndex` are the scope head the action was composed\nagainst. They are recorded rather than enforced; a signature is verified from\nthe params carried in its own tuple, which do not include them.\n\"\"\"\ninput ActionContextInput {\n \"\"\"The index of the last operation in the scope the action applies to.\"\"\"\n prevOpIndex: Int\n\n \"\"\"The hash of the state the action applies to.\"\"\"\n prevOpHash: String\n\n \"\"\"Covers replay of an operation that would otherwise be a no-op.\"\"\"\n nonce: String\n\n signer: ReactorSignerInput\n}\n\ninput ReactorSignerInput {\n user: ReactorSignerUserInput\n app: ReactorSignerAppInput\n signatures: [String!]!\n}\n\ninput ReactorSignerUserInput {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ninput ReactorSignerAppInput {\n name: String!\n key: String!\n}\n\ninput ActionInput {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContextInput\n}\n\n# Synchronization types\ntype ReactorOperation {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: Action!\n}\n\ninput OperationInput {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: ActionInput!\n}\n\ntype OperationContext {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ninput OperationContextInput {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ntype OperationWithContext {\n operation: ReactorOperation!\n context: OperationContext!\n}\n\ninput OperationWithContextInput {\n operation: OperationInput!\n context: OperationContextInput!\n}\n\ntype ChannelMeta {\n id: String!\n}\n\ninput ChannelMetaInput {\n id: String!\n}\n\ntype RemoteCursor {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\ninput RemoteCursorInput {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\nenum SyncEnvelopeType {\n OPERATIONS\n ACK\n}\n\ntype SyncEnvelope {\n type: SyncEnvelopeType!\n channelMeta: ChannelMeta!\n operations: [OperationWithContext!]\n cursor: RemoteCursor\n key: String\n dependsOn: [String!]\n}\n\ninput SyncEnvelopeInput {\n type: SyncEnvelopeType!\n channelMeta: ChannelMetaInput!\n operations: [OperationWithContextInput!]\n cursor: RemoteCursorInput\n key: String\n dependsOn: [String!]\n}\n\ninput RemoteFilterInput {\n documentId: [String!]!\n scope: [String!]!\n branch: String!\n}\n\ninput TouchChannelInput {\n id: String!\n name: String!\n collectionId: String!\n filter: RemoteFilterInput!\n sinceTimestampUtcMs: String!\n}\n\ntype TouchChannelResult {\n success: Boolean!\n ackOrdinal: Int!\n}\n\ntype Query {\n # Get document models for a namespace\n documentModels(\n namespace: String\n paging: PagingInput\n ): DocumentModelResultPage!\n\n # Get a specific document by ID or slug\n document(identifier: String!, view: ViewFilterInput): DocumentWithChildren\n\n # Get outgoing relationships of a given type from a source document\n documentOutgoingRelationships(\n sourceIdentifier: String!\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get incoming relationships of a given type to a target document\n documentIncomingRelationships(\n targetIdentifier: String!\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Find documents by search criteria\n findDocuments(\n search: SearchFilterInput\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get job status\n jobStatus(jobId: String!): JobInfo\n\n # Get operations for a document with filtering and pagination\n documentOperations(\n filter: OperationsFilterInput!\n paging: PagingInput\n ): ReactorOperationResultPage!\n\n \"\"\"\n Predicts whether the calling subject would be admitted to execute each of a\n set of candidate operations, without submitting any of them. A UI asks this to\n disable a control rather than offer an action that fails on submit.\n\n The answer is a prediction, not a promise:\n\n - Real admission compiles an append condition over everything it read and the\n store enforces it at write time. A preflight reads no future, so a policy\n change landing between this answer and the submit changes the verdict. The\n submit path stays the only authority.\n - The verdict is evaluated at the stream heads, so it is correct for a\n candidate about to be submitted. A backdated submission is out of contract;\n the reactor decides that one by position.\n - A candidate whose verdict depends on its input has to carry that input, since\n a conditional grant reads it.\n\n The subject is the authenticated caller and cannot be named in the request:\n answering for an arbitrary subject would disclose what a policy grants\n somebody else. It carries both the caller's address and the did:key of the app\n instance whose token authenticated the request, so a policy naming either\n matches the same principal the write path presents.\n\n Requires the reactor's authEnforcement feature flag. Without it the reactor\n holds no decision model, so this fails with extensions.code\n AUTH_EVALUATION_UNSUPPORTED rather than guessing.\n \"\"\"\n evaluateActions(\n documentIdentifier: String!\n branch: String\n candidates: [ActionCandidateInput!]!\n ): ActionEvaluations!\n\n \"\"\"\n Polls for sync envelopes from a channel.\n\n An operation stored before the API required an action id cannot be\n represented by this schema, and rather than serving a partial response beside\n a bare non-null violation, this fails with extensions.code\n MALFORMED_STORED_OPERATION naming the operation. That code means the failure\n is worth polling through: the document holding the operation needs repairing,\n but the channel serves every other document, and a peer that stopped polling\n would stop receiving those too.\n \"\"\"\n pollSyncEnvelopes(\n channelId: String!\n outboxAck: Int!\n outboxLatest: Int!\n ): PollSyncEnvelopesResult!\n}\n\ntype Mutation {\n # Create a new document\n createDocument(document: JSONObject!, parentIdentifier: String): PHDocument!\n\n # Create an empty document of specified type\n createEmptyDocument(\n documentType: String!\n parentIdentifier: String\n ): PHDocument!\n\n \"\"\"\n Applies actions to a document and waits for the result.\n\n Each action is coerced against `ActionInput`, so one missing a field the wire\n declares is refused before any work starts. The id in particular: it is hashed\n into the operation id and replay dedupes by it, so an action without one is\n stored under an operation id shared by every id-less operation on the same\n document, scope and branch.\n\n Named for `IReactorClient.execute`, which this is the wire form of. `branch`\n defaults to `main`.\n \"\"\"\n execute(\n documentIdentifier: String!\n actions: [ActionInput!]!\n branch: String\n ): PHDocument!\n\n \"\"\"\n Submits actions to a document and returns the job that will apply them.\n\n Coerced exactly as `execute` is, so a malformed action is refused here rather\n than surfacing later as a failed job. Returns the job rather than only its id,\n so a caller has the status and the creation time it would otherwise have to\n ask for; `result` is null until the job produces one.\n \"\"\"\n executeAsync(\n documentIdentifier: String!\n actions: [ActionInput!]!\n branch: String\n ): JobInfo!\n\n # Apply actions to a document (synchronous)\n mutateDocument(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): PHDocument!\n @deprecated(\n reason: \"Use execute. Actions here are untyped, so a malformed one is refused by a hand-written check rather than by the schema, and `view.scopes` is accepted but ignored.\"\n )\n\n # Submit actions to a document (asynchronous)\n mutateDocumentAsync(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): String!\n @deprecated(\n reason: \"Use executeAsync, which is typed and returns the job rather than only its id.\"\n )\n\n # Rename a document\n renameDocument(\n documentIdentifier: String!\n name: String!\n branch: String\n ): PHDocument!\n\n # Update the preferred editor recorded in the document header meta.\n # Pass null/omit to clear it.\n setPreferredEditor(\n documentIdentifier: String!\n preferredEditor: String\n branch: String\n ): PHDocument!\n\n # Add a relationship between two documents\n addRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): PHDocument!\n\n # Remove a relationship between two documents\n removeRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): PHDocument!\n\n # Move a relationship from one source to another\n moveRelationship(\n sourceParentIdentifier: String!\n targetParentIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): MoveRelationshipResult!\n\n # Delete a single document\n deleteDocument(identifier: String!, propagate: PropagationMode): Boolean!\n\n # Delete multiple documents\n deleteDocuments(identifiers: [String!]!, propagate: PropagationMode): Boolean!\n\n # Touch (create or update) a channel for sync\n touchChannel(input: TouchChannelInput!): TouchChannelResult!\n\n # Push sync envelopes to a channel\n pushSyncEnvelopes(envelopes: [SyncEnvelopeInput!]!): Boolean!\n}\n\ntype Subscription {\n # Subscribe to document changes\n documentChanges(\n search: SearchFilterInput\n view: ViewFilterInput\n ): DocumentChangeEvent!\n\n # Subscribe to job changes\n jobChanges(jobId: String!): JobChangeEvent!\n}\n";
5626
+ var schema_default = "# Scalar types (for codegen - also defined in create-schema.ts)\nscalar JSONObject\nscalar DateTime\n\n# Input types\ninput PagingInput {\n limit: Int\n offset: Int\n cursor: String\n}\n\ninput ViewFilterInput {\n branch: String\n scopes: [String!]\n}\n\ninput SearchFilterInput {\n type: String\n parentId: String\n identifiers: [String!]\n}\n\ninput OperationsFilterInput {\n documentId: String!\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\ninput DocumentOperationsFilterInput {\n branch: String\n scopes: [String!]\n actionTypes: [String!]\n sinceRevision: Int\n timestampFrom: String\n timestampTo: String\n}\n\n# Enums\nenum PropagationMode {\n CASCADE\n ORPHAN\n}\n\nenum DocumentChangeType {\n CREATED\n DELETED\n UPDATED\n PARENT_ADDED\n PARENT_REMOVED\n CHILD_ADDED\n CHILD_REMOVED\n}\n\n# Object types\ntype DocumentModelGlobalState {\n id: String!\n name: String!\n namespace: String\n version: String\n specification: JSONObject!\n}\n\ntype DocumentModelResultPage {\n items: [DocumentModelGlobalState!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype Revision {\n scope: String!\n revision: Int!\n}\n\ntype PHDocument {\n id: String!\n slug: String\n preferredEditor: String\n name: String!\n documentType: String!\n state: JSONObject!\n revisionsList: [Revision!]!\n createdAtUtcIso: DateTime!\n lastModifiedAtUtcIso: DateTime!\n operations(\n filter: DocumentOperationsFilterInput\n paging: PagingInput\n ): ReactorOperationResultPage\n}\n\ntype PHDocumentResultPage {\n items: [PHDocument!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\n# A directed edge between two documents, with the metadata recorded against it.\ntype DocumentRelationship {\n sourceId: String!\n targetId: String!\n relationshipType: String!\n metadata: JSONObject\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\ntype DocumentRelationshipResultPage {\n items: [DocumentRelationship!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype ReactorOperationResultPage {\n items: [ReactorOperation!]!\n totalCount: Int!\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype DeadLetterInfo {\n documentId: String!\n error: String!\n \"\"\"\n How the origin classified the failure. A peer needs this to mirror the origin's\n quarantine decision; without it a held auth operation would freeze the document\n on the peer side while the origin kept syncing.\n \"\"\"\n errorType: String\n jobId: String!\n branch: String!\n scopes: [String!]!\n operationCount: Int!\n}\n\ntype PollSyncEnvelopesResult {\n envelopes: [SyncEnvelope!]!\n ackOrdinal: Int!\n deadLetters: [DeadLetterInfo!]!\n hasMore: Boolean!\n}\n\ntype DocumentWithChildren {\n document: PHDocument!\n childIds: [String!]!\n}\n\ntype MoveRelationshipResult {\n source: PHDocument!\n target: PHDocument!\n}\n\ntype JobInfo {\n id: String!\n status: String!\n\n \"\"\"\n What the job produced, once it has produced anything. Null until then, which\n is the state every job is in when it is handed back from a submission.\n \"\"\"\n result: JSONObject\n\n error: String\n createdAt: DateTime!\n completedAt: DateTime\n}\n\ntype DocumentChangeEvent {\n type: DocumentChangeType!\n documents: [PHDocument!]!\n context: DocumentChangeContext\n}\n\ntype DocumentChangeContext {\n parentId: String\n childId: String\n}\n\ntype JobChangeEvent {\n jobId: String!\n status: String!\n\n \"\"\"\n What the job produced, once it has produced anything. Nullable for the same\n reason as `JobInfo.result`: a job that has not written yet has produced\n nothing, and an empty object would claim otherwise.\n \"\"\"\n result: JSONObject\n\n error: String\n}\n\ntype ReactorSignerUser {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ntype ReactorSignerApp {\n name: String!\n key: String!\n}\n\ntype ReactorSigner {\n user: ReactorSignerUser\n app: ReactorSignerApp\n signatures: [String!]!\n}\n\ntype ActionContext {\n signer: ReactorSigner\n}\n\ntype Action {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContext\n}\n\n\"\"\"\nOne operation an authorization preflight predicts a verdict for.\n\n`input` is what a conditional grant reads, so a candidate standing for a\nfilled-in form carries that form's input. Omitting it predicts the verdict an\nempty input earns, not the verdict the filled-in form will get.\n\"\"\"\ninput ActionCandidateInput {\n scope: String!\n type: String!\n input: JSONObject\n}\n\nenum AuthDecision {\n ALLOW\n DENY\n}\n\n\"\"\"\nOne candidate's predicted verdict. `reason` carries the refusal a DENY is\nrecorded with, and is null on an ALLOW.\n\"\"\"\ntype ActionEvaluation {\n decision: AuthDecision!\n reason: String\n}\n\n\"\"\"\nThe predicted verdicts for a set of candidates, in the order they were given,\nwith the aggregates a caller branches on.\n\nThe aggregates are redundant -- a verdict is binary -- and all four are returned\nso a caller reads the one its question is phrased in rather than negating\nanother. Over no candidates every aggregate is false: nothing is allowed and\nnothing is denied.\n\"\"\"\ntype ActionEvaluations {\n evaluations: [ActionEvaluation!]!\n allAllowed: Boolean!\n anyAllowed: Boolean!\n allDenied: Boolean!\n anyDenied: Boolean!\n}\n\n# Input types for sync operations\n\"\"\"\nAn action's context, as a caller submits it.\n\nDeclares every field `ActionContext` carries, because an input object rejects a\nfield it does not declare: an action stamped with the head it applies to - which\nis what a signing client sends - is refused outright if these are missing, and\nthat refusal is a whole-request one, not a per-field one.\n\n`prevOpHash` and `prevOpIndex` are the scope head the action was composed\nagainst. They are recorded rather than enforced; a signature is verified from\nthe params carried in its own tuple, which do not include them.\n\"\"\"\ninput ActionContextInput {\n \"\"\"The index of the last operation in the scope the action applies to.\"\"\"\n prevOpIndex: Int\n\n \"\"\"The hash of the state the action applies to.\"\"\"\n prevOpHash: String\n\n \"\"\"Covers replay of an operation that would otherwise be a no-op.\"\"\"\n nonce: String\n\n signer: ReactorSignerInput\n}\n\ninput ReactorSignerInput {\n user: ReactorSignerUserInput\n app: ReactorSignerAppInput\n signatures: [String!]!\n}\n\ninput ReactorSignerUserInput {\n address: String!\n networkId: String!\n chainId: Int!\n}\n\ninput ReactorSignerAppInput {\n name: String!\n key: String!\n}\n\ninput ActionInput {\n id: String!\n type: String!\n timestampUtcMs: String!\n input: JSONObject!\n scope: String!\n context: ActionContextInput\n}\n\n# Synchronization types\ntype ReactorOperation {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: Action!\n}\n\ninput OperationInput {\n index: Int!\n timestampUtcMs: String!\n hash: String!\n skip: Int!\n error: String\n deniedReason: String\n id: String\n action: ActionInput!\n}\n\ntype OperationContext {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ninput OperationContextInput {\n documentId: String!\n documentType: String!\n scope: String!\n branch: String!\n ordinal: Int!\n}\n\ntype OperationWithContext {\n operation: ReactorOperation!\n context: OperationContext!\n}\n\ninput OperationWithContextInput {\n operation: OperationInput!\n context: OperationContextInput!\n}\n\ntype ChannelMeta {\n id: String!\n}\n\ninput ChannelMetaInput {\n id: String!\n}\n\ntype RemoteCursor {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\ninput RemoteCursorInput {\n remoteName: String!\n cursorOrdinal: Int!\n lastSyncedAtUtcMs: String\n}\n\nenum SyncEnvelopeType {\n OPERATIONS\n ACK\n}\n\ntype SyncEnvelope {\n type: SyncEnvelopeType!\n channelMeta: ChannelMeta!\n operations: [OperationWithContext!]\n cursor: RemoteCursor\n key: String\n dependsOn: [String!]\n}\n\ninput SyncEnvelopeInput {\n type: SyncEnvelopeType!\n channelMeta: ChannelMetaInput!\n operations: [OperationWithContextInput!]\n cursor: RemoteCursorInput\n key: String\n dependsOn: [String!]\n}\n\ninput RemoteFilterInput {\n documentId: [String!]!\n scope: [String!]!\n branch: String!\n}\n\ninput TouchChannelInput {\n id: String!\n name: String!\n collectionId: String!\n filter: RemoteFilterInput!\n sinceTimestampUtcMs: String!\n}\n\ntype TouchChannelResult {\n success: Boolean!\n ackOrdinal: Int!\n}\n\ntype Query {\n # Get document models for a namespace\n documentModels(\n namespace: String\n paging: PagingInput\n ): DocumentModelResultPage!\n\n # Get a specific document by ID or slug\n document(identifier: String!, view: ViewFilterInput): DocumentWithChildren\n\n # Get outgoing relationships of a given type from a source document\n documentOutgoingRelationships(\n sourceIdentifier: String!\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get incoming relationships of a given type to a target document\n documentIncomingRelationships(\n targetIdentifier: String!\n relationshipType: String!\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get the outgoing relationship edges of a source document. Unlike\n # documentOutgoingRelationships, which returns the documents at the far end,\n # this returns the edges themselves, carrying their metadata and timestamps.\n # Omit relationshipType to return edges of every type.\n documentOutgoingRelationshipEdges(\n sourceIdentifier: String!\n relationshipType: String\n view: ViewFilterInput\n paging: PagingInput\n ): DocumentRelationshipResultPage!\n\n # Get the incoming relationship edges of a target document. Unlike\n # documentIncomingRelationships, which returns the documents at the far end,\n # this returns the edges themselves, carrying their metadata and timestamps.\n # Omit relationshipType to return edges of every type.\n documentIncomingRelationshipEdges(\n targetIdentifier: String!\n relationshipType: String\n view: ViewFilterInput\n paging: PagingInput\n ): DocumentRelationshipResultPage!\n\n # Find documents by search criteria\n findDocuments(\n search: SearchFilterInput\n view: ViewFilterInput\n paging: PagingInput\n ): PHDocumentResultPage!\n\n # Get job status\n jobStatus(jobId: String!): JobInfo\n\n # Get operations for a document with filtering and pagination\n documentOperations(\n filter: OperationsFilterInput!\n paging: PagingInput\n ): ReactorOperationResultPage!\n\n \"\"\"\n Predicts whether the calling subject would be admitted to execute each of a\n set of candidate operations, without submitting any of them. A UI asks this to\n disable a control rather than offer an action that fails on submit.\n\n The answer is a prediction, not a promise:\n\n - Real admission compiles an append condition over everything it read and the\n store enforces it at write time. A preflight reads no future, so a policy\n change landing between this answer and the submit changes the verdict. The\n submit path stays the only authority.\n - The verdict is evaluated at the stream heads, so it is correct for a\n candidate about to be submitted. A backdated submission is out of contract;\n the reactor decides that one by position.\n - A candidate whose verdict depends on its input has to carry that input, since\n a conditional grant reads it.\n\n The subject is the authenticated caller and cannot be named in the request:\n answering for an arbitrary subject would disclose what a policy grants\n somebody else. It carries both the caller's address and the did:key of the app\n instance whose token authenticated the request, so a policy naming either\n matches the same principal the write path presents.\n\n Requires the reactor's authEnforcement feature flag. Without it the reactor\n holds no decision model, so this fails with extensions.code\n AUTH_EVALUATION_UNSUPPORTED rather than guessing.\n \"\"\"\n evaluateActions(\n documentIdentifier: String!\n branch: String\n candidates: [ActionCandidateInput!]!\n ): ActionEvaluations!\n\n \"\"\"\n Polls for sync envelopes from a channel.\n\n An operation stored before the API required an action id cannot be\n represented by this schema, and rather than serving a partial response beside\n a bare non-null violation, this fails with extensions.code\n MALFORMED_STORED_OPERATION naming the operation. That code means the failure\n is worth polling through: the document holding the operation needs repairing,\n but the channel serves every other document, and a peer that stopped polling\n would stop receiving those too.\n \"\"\"\n pollSyncEnvelopes(\n channelId: String!\n outboxAck: Int!\n outboxLatest: Int!\n ): PollSyncEnvelopesResult!\n}\n\ntype Mutation {\n # Create a new document\n createDocument(document: JSONObject!, parentIdentifier: String): PHDocument!\n\n # Create an empty document of specified type\n createEmptyDocument(\n documentType: String!\n parentIdentifier: String\n ): PHDocument!\n\n \"\"\"\n Applies actions to a document and waits for the result.\n\n Each action is coerced against `ActionInput`, so one missing a field the wire\n declares is refused before any work starts. The id in particular: it is hashed\n into the operation id and replay dedupes by it, so an action without one is\n stored under an operation id shared by every id-less operation on the same\n document, scope and branch.\n\n Named for `IReactorClient.execute`, which this is the wire form of. `branch`\n defaults to `main`.\n \"\"\"\n execute(\n documentIdentifier: String!\n actions: [ActionInput!]!\n branch: String\n ): PHDocument!\n\n \"\"\"\n Submits actions to a document and returns the job that will apply them.\n\n Coerced exactly as `execute` is, so a malformed action is refused here rather\n than surfacing later as a failed job. Returns the job rather than only its id,\n so a caller has the status and the creation time it would otherwise have to\n ask for; `result` is null until the job produces one.\n \"\"\"\n executeAsync(\n documentIdentifier: String!\n actions: [ActionInput!]!\n branch: String\n ): JobInfo!\n\n # Apply actions to a document (synchronous)\n mutateDocument(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): PHDocument!\n @deprecated(\n reason: \"Use execute. Actions here are untyped, so a malformed one is refused by a hand-written check rather than by the schema, and `view.scopes` is accepted but ignored.\"\n )\n\n # Submit actions to a document (asynchronous)\n mutateDocumentAsync(\n documentIdentifier: String!\n actions: [JSONObject!]!\n view: ViewFilterInput\n ): String!\n @deprecated(\n reason: \"Use executeAsync, which is typed and returns the job rather than only its id.\"\n )\n\n # Rename a document\n renameDocument(\n documentIdentifier: String!\n name: String!\n branch: String\n ): PHDocument!\n\n # Update the preferred editor recorded in the document header meta.\n # Pass null/omit to clear it.\n setPreferredEditor(\n documentIdentifier: String!\n preferredEditor: String\n branch: String\n ): PHDocument!\n\n # Add a relationship between two documents. Adding one that already exists is\n # a no-op, metadata included -- use updateRelationship to change an edge.\n addRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n metadata: JSONObject\n branch: String\n ): PHDocument!\n\n # Replace the metadata of an existing relationship, preserving its createdAt.\n # Omitting metadata, or passing null, clears it.\n updateRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n metadata: JSONObject\n branch: String\n ): PHDocument!\n\n # Remove a relationship between two documents\n removeRelationship(\n sourceIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): PHDocument!\n\n # Move a relationship from one source to another\n moveRelationship(\n sourceParentIdentifier: String!\n targetParentIdentifier: String!\n targetIdentifier: String!\n relationshipType: String!\n branch: String\n ): MoveRelationshipResult!\n\n # Delete a single document\n deleteDocument(identifier: String!, propagate: PropagationMode): Boolean!\n\n # Delete multiple documents\n deleteDocuments(identifiers: [String!]!, propagate: PropagationMode): Boolean!\n\n # Touch (create or update) a channel for sync\n touchChannel(input: TouchChannelInput!): TouchChannelResult!\n\n # Push sync envelopes to a channel\n pushSyncEnvelopes(envelopes: [SyncEnvelopeInput!]!): Boolean!\n}\n\ntype Subscription {\n # Subscribe to document changes\n documentChanges(\n search: SearchFilterInput\n view: ViewFilterInput\n ): DocumentChangeEvent!\n\n # Subscribe to job changes\n jobChanges(jobId: String!): JobChangeEvent!\n}\n";
5371
5627
  //#endregion
5372
5628
  //#region src/graphql/reactor/pubsub.ts
5373
5629
  const pubSub = new PubSub();
@@ -5414,7 +5670,7 @@ function ensureJobSubscription(reactorClient, jobId) {
5414
5670
  createdAt: jobInfo.createdAtUtcIso,
5415
5671
  completedAt: jobInfo.completedAtUtcIso ?? null,
5416
5672
  error: jobInfo.error?.message ?? null,
5417
- result: jobInfo.result ?? {}
5673
+ result: jobInfo.result ?? null
5418
5674
  },
5419
5675
  jobId,
5420
5676
  documentId: jobInfo.documentId
@@ -5492,6 +5748,48 @@ var ReactorSubgraph = class extends BaseSubgraph {
5492
5748
  }
5493
5749
  }
5494
5750
  /**
5751
+ * Drops the relationship edges whose far-end document the caller cannot read.
5752
+ *
5753
+ * An edge names two documents, so returning it discloses the far end's id. The
5754
+ * document-shaped relationship queries already withhold far-end documents the
5755
+ * caller cannot read; the edge-shaped queries have to withhold the same ones,
5756
+ * or they become a way to enumerate around that check. Supreme admins read
5757
+ * everything, so the per-edge check is skipped for them.
5758
+ *
5759
+ * The counts describe what is served; the cursor describes where to resume in
5760
+ * the underlying unfiltered stream, so it is left as it came. A filtered page
5761
+ * is therefore often shorter than the requested limit, and that is the stream
5762
+ * continuing rather than a sign it ended.
5763
+ */
5764
+ async filterRelationshipEdges(page, farEnd, ctx) {
5765
+ if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
5766
+ const decisions = /* @__PURE__ */ new Map();
5767
+ const items = [];
5768
+ for (const edge of page.items) {
5769
+ const documentId = edge[farEnd];
5770
+ let canRead = decisions.get(documentId);
5771
+ if (canRead === void 0) {
5772
+ canRead = await this.canReadDocument(documentId, ctx);
5773
+ decisions.set(documentId, canRead);
5774
+ }
5775
+ if (canRead) items.push(edge);
5776
+ }
5777
+ return this.servedPage(page, items);
5778
+ }
5779
+ /**
5780
+ * Restates a page's totalCount over the items that survived a read filter, so
5781
+ * that the difference between the two cannot be read off as a count of what
5782
+ * the caller was not allowed to see.
5783
+ */
5784
+ servedPage(page, items) {
5785
+ const withheld = page.items.length - items.length;
5786
+ return {
5787
+ ...page,
5788
+ items,
5789
+ totalCount: Math.max(0, page.totalCount - withheld)
5790
+ };
5791
+ }
5792
+ /**
5495
5793
  * Adds to `forbidden` the canonical document ids in `syncOps` that the caller
5496
5794
  * cannot read, checking each distinct id once. Sync operation document ids are
5497
5795
  * canonical (never slugs), so no resolution is needed.
@@ -5519,14 +5817,18 @@ var ReactorSubgraph = class extends BaseSubgraph {
5519
5817
  * queued for the adopter.
5520
5818
  *
5521
5819
  * The refusal is the shape a read denial takes, so a puller treats it as a
5522
- * signal to authenticate again rather than as a transport failure.
5820
+ * signal to authenticate again rather than as a transport failure. Pushes are
5821
+ * refused the same way -- a push interacts with the same private queue, and
5822
+ * reports the liveness that keeps it from being reclaimed -- but they never
5823
+ * adopt, since the push path has no drive check to clear the claimant.
5523
5824
  *
5524
5825
  * Nothing is enforced or adopted without a serving gate. Below
5525
5826
  * `authEnforcement` there is no policy being enforced for the channel to
5526
5827
  * belong to, and refusing a poll there would break sync for no gain.
5527
5828
  */
5528
- async #bindOrRefuseChannel(channelId, ctx) {
5829
+ async #bindOrRefuseChannel(channelId, ctx, options = {}) {
5529
5830
  if (!this.syncServingGate) return;
5831
+ const { adopt = true, action = "poll" } = options;
5530
5832
  let remote;
5531
5833
  try {
5532
5834
  remote = this.syncManager.getById(channelId);
@@ -5536,10 +5838,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
5536
5838
  const bound = remote.meta.options.boundAddress;
5537
5839
  const address = ctx.user?.address;
5538
5840
  if (bound === void 0) {
5539
- if (address !== void 0) await this.syncManager.bindRemote(channelId, address);
5841
+ if (adopt && address !== void 0) await this.syncManager.bindRemote(channelId, address);
5540
5842
  return;
5541
5843
  }
5542
- if (bound !== address) throw new ForbiddenError("to poll this sync channel");
5844
+ if (bound !== address) throw new ForbiddenError(`to ${action} this sync channel`);
5543
5845
  }
5544
5846
  /** The address a channel created by this request belongs to, if any. */
5545
5847
  #creatorBinding(ctx) {
@@ -5616,10 +5918,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
5616
5918
  if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
5617
5919
  const filteredItems = [];
5618
5920
  for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
5619
- return {
5620
- ...result,
5621
- items: filteredItems
5622
- };
5921
+ return this.servedPage(result, filteredItems);
5623
5922
  }
5624
5923
  return result;
5625
5924
  } catch (error) {
@@ -5627,6 +5926,34 @@ var ReactorSubgraph = class extends BaseSubgraph {
5627
5926
  throw error;
5628
5927
  }
5629
5928
  },
5929
+ documentOutgoingRelationshipEdges: async (_parent, args, ctx) => {
5930
+ this.logger.debug("documentOutgoingRelationshipEdges(@args)", args);
5931
+ try {
5932
+ const handle = await this.assertCanRead(args.sourceIdentifier, ctx);
5933
+ const result = await documentOutgoingRelationshipEdges(this.reactorClient, {
5934
+ ...args,
5935
+ sourceIdentifier: handle.fetchIdentifier
5936
+ }, this.viewSubject(ctx));
5937
+ return await this.filterRelationshipEdges(result, "targetId", ctx);
5938
+ } catch (error) {
5939
+ this.logger.error("Error in documentOutgoingRelationshipEdges: @Error", error);
5940
+ throw error;
5941
+ }
5942
+ },
5943
+ documentIncomingRelationshipEdges: async (_parent, args, ctx) => {
5944
+ this.logger.debug("documentIncomingRelationshipEdges(@args)", args);
5945
+ try {
5946
+ const handle = await this.assertCanRead(args.targetIdentifier, ctx);
5947
+ const result = await documentIncomingRelationshipEdges(this.reactorClient, {
5948
+ ...args,
5949
+ targetIdentifier: handle.fetchIdentifier
5950
+ }, this.viewSubject(ctx));
5951
+ return await this.filterRelationshipEdges(result, "sourceId", ctx);
5952
+ } catch (error) {
5953
+ this.logger.error("Error in documentIncomingRelationshipEdges: @Error", error);
5954
+ throw error;
5955
+ }
5956
+ },
5630
5957
  findDocuments: async (_parent, args, ctx) => {
5631
5958
  this.logger.debug("findDocuments(@args)", args);
5632
5959
  try {
@@ -5637,10 +5964,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
5637
5964
  if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
5638
5965
  const filteredItems = [];
5639
5966
  for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
5640
- return {
5641
- ...result,
5642
- items: filteredItems
5643
- };
5967
+ return this.servedPage(result, filteredItems);
5644
5968
  }
5645
5969
  return result;
5646
5970
  } catch (error) {
@@ -5704,8 +6028,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
5704
6028
  if (await this.authorizationService.canRead(driveId, void 0)) await this.#collectForbiddenDocuments(remote.channel.outbox.items, forbiddenIds, ctx);
5705
6029
  await this.#collectForbiddenDocuments(remote.channel.deadLetter.items, forbiddenIds, ctx);
5706
6030
  }
5707
- const heldOpIds = this.syncServingGate ? await collectHeldSyncOperations([...remote.channel.outbox.items, ...remote.channel.deadLetter.items], this.syncServingGate, this.viewSubject(ctx)) : /* @__PURE__ */ new Set();
5708
- const { envelopes, ackOrdinal, deadLetters, hasMore } = pollSyncEnvelopes(this.syncManager, args, forbiddenIds, heldOpIds);
6031
+ const gate = this.syncServingGate;
6032
+ const gated = gate ? [...remote.channel.outbox.items, ...remote.channel.deadLetter.items] : void 0;
6033
+ const heldOpIds = gate && gated ? await collectHeldSyncOperations(gated, gate, this.viewSubject(ctx)) : /* @__PURE__ */ new Set();
6034
+ const { envelopes, ackOrdinal, deadLetters, hasMore } = pollSyncEnvelopes(this.syncManager, args, forbiddenIds, heldOpIds, gated && new Set(gated.map((syncOp) => syncOp.id)));
5709
6035
  return {
5710
6036
  envelopes,
5711
6037
  ackOrdinal,
@@ -5842,6 +6168,19 @@ var ReactorSubgraph = class extends BaseSubgraph {
5842
6168
  throw error;
5843
6169
  }
5844
6170
  },
6171
+ updateRelationship: async (_parent, args, ctx) => {
6172
+ this.logger.debug("updateRelationship(@args)", args);
6173
+ try {
6174
+ const handle = await this.assertCanWrite(args.sourceIdentifier, ctx);
6175
+ return await updateRelationship(this.reactorClient, {
6176
+ ...args,
6177
+ sourceIdentifier: handle.fetchIdentifier
6178
+ });
6179
+ } catch (error) {
6180
+ this.logger.error("Error in updateRelationship(@args): @Error", args, error);
6181
+ throw error;
6182
+ }
6183
+ },
5845
6184
  removeRelationship: async (_parent, args, ctx) => {
5846
6185
  this.logger.debug("removeRelationship(@args)", args);
5847
6186
  try {
@@ -5920,6 +6259,16 @@ var ReactorSubgraph = class extends BaseSubgraph {
5920
6259
  pushSyncEnvelopes: async (_parent, args, ctx) => {
5921
6260
  this.logger.debug("pushSyncEnvelopes(@args)", args);
5922
6261
  try {
6262
+ const boundChannelIds = /* @__PURE__ */ new Set();
6263
+ for (const envelope of args.envelopes) {
6264
+ const channelId = envelope.channelMeta.id;
6265
+ if (boundChannelIds.has(channelId)) continue;
6266
+ boundChannelIds.add(channelId);
6267
+ await this.#bindOrRefuseChannel(channelId, ctx, {
6268
+ adopt: false,
6269
+ action: "push to"
6270
+ });
6271
+ }
5923
6272
  const checkedOperations = /* @__PURE__ */ new Map();
5924
6273
  for (const envelope of args.envelopes) for (const op of envelope.operations ?? []) {
5925
6274
  const documentId = op.context.documentId;
@@ -6023,10 +6372,10 @@ const ADMIN_USERS = getAdminUsers();
6023
6372
  //#endregion
6024
6373
  //#region src/graphql/system/version.ts
6025
6374
  function getVersion() {
6026
- return "6.2.3-dev.2";
6375
+ return "6.2.3-dev.21";
6027
6376
  }
6028
6377
  function getGitHash() {
6029
- return "3540858558b669f520d0b49c63963572355aae2c";
6378
+ return "ae4d9b1ad416d695a1bc03fc68b017606b38f7e7";
6030
6379
  }
6031
6380
  function getGitUrl() {
6032
6381
  return buildTreeUrl(getGitHash());
@@ -6153,6 +6502,22 @@ var HttpPackageLoader = class {
6153
6502
  this.logger.verbose(`No processor factory found in ${packageName}`);
6154
6503
  return null;
6155
6504
  }
6505
+ async loadPieces(packageSpec) {
6506
+ const { name: packageName } = this.parsePackageSpec(packageSpec);
6507
+ if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
6508
+ const base = `${this.registryUrl}-/cdn/${packageSpec}/node/pieces/`;
6509
+ this.logger.verbose(`Importing pieces from: ${base}index.mjs`);
6510
+ let module;
6511
+ try {
6512
+ module = await import(`${base}index.mjs`);
6513
+ } catch (error) {
6514
+ this.logger.verbose(`No pieces found for: ${packageName}`, error);
6515
+ return [];
6516
+ }
6517
+ const pieces = piecesFromCdnList(module, base, packageName, this.logger);
6518
+ this.logger.verbose(`Loaded ${pieces.length} pieces from ${packageName}`);
6519
+ return pieces;
6520
+ }
6156
6521
  /**
6157
6522
  * Load document models from multiple packages.
6158
6523
  * Continues loading even if some packages fail.
@@ -6252,6 +6617,7 @@ var HttpDocumentModelLoader = class {
6252
6617
  var ImportPackageLoader = class {
6253
6618
  logger = childLogger(["reactor-api", "import-loader"]);
6254
6619
  name = "ImportPackageLoader";
6620
+ reportedUnbuilt = /* @__PURE__ */ new Set();
6255
6621
  async loadDocumentModels(identifier) {
6256
6622
  this.logger.verbose(`Loading document models from package: ${identifier}`);
6257
6623
  const pkgModule = await loadDocumentModels(identifier);
@@ -6294,6 +6660,30 @@ var ImportPackageLoader = class {
6294
6660
  this.logger.verbose(` ➜ No Processor Factory found: ${identifier}`);
6295
6661
  return null;
6296
6662
  }
6663
+ async loadPieces(identifier) {
6664
+ this.logger.verbose(`Loading pieces from package: ${identifier}`);
6665
+ const { root, listPath } = pieceListLocation(identifier);
6666
+ if (!existsSync(listPath)) {
6667
+ this.reportUnbuilt(identifier, root, listPath);
6668
+ return [];
6669
+ }
6670
+ const pieces = piecesFromListModule(await import(
6671
+ /* @vite-ignore */
6672
+ pathToFileURL(listPath).href
6673
+ ), {
6674
+ root,
6675
+ identifier,
6676
+ logger: this.logger
6677
+ });
6678
+ this.logger.verbose(` ➜ Loaded ${pieces.length} Pieces from: ${identifier}`);
6679
+ return pieces;
6680
+ }
6681
+ reportUnbuilt(identifier, root, listPath) {
6682
+ if (!isAbsolute(identifier) || this.reportedUnbuilt.has(identifier)) return;
6683
+ if (!existsSync(join(root, PIECE_SOURCE_LIST))) return;
6684
+ this.reportedUnbuilt.add(identifier);
6685
+ this.logger.warn("@pkg declares pieces but @list was never built; run `ph build`", identifier, listPath);
6686
+ }
6297
6687
  };
6298
6688
  //#endregion
6299
6689
  //#region src/packages/package-manager.ts
@@ -6309,10 +6699,12 @@ var ImportPackageLoader = class {
6309
6699
  * either bare (`'pkg'`) or as a subpath (`'pkg/subgraphs'`), since loaders
6310
6700
  * import sub-entries like `${pkg}/subgraphs` / `${pkg}/document-models`.
6311
6701
  */
6312
- function isExpectedLoaderMiss(error, pkg) {
6702
+ function isExpectedLoaderMiss(error, pkg, subPath) {
6313
6703
  if (!(error instanceof Error)) return false;
6314
6704
  const code = error.code;
6315
6705
  if (code === "ERR_UNSUPPORTED_DIR_IMPORT") return true;
6706
+ if (code === "ERR_PACKAGE_PATH_NOT_EXPORTED" && subPath !== void 0 && error.message.includes(`'./${subPath}'`)) return true;
6707
+ if (subPath !== void 0 && new RegExp(`^Failed to fetch \\S*/${subPath}/index\\.mjs: 404$`).test(error.message)) return true;
6316
6708
  if (error.message.startsWith("Invalid package name:")) return true;
6317
6709
  if (code === "ERR_MODULE_NOT_FOUND") return error.message.includes(`'${pkg}'`) || error.message.includes(`"${pkg}"`) || error.message.includes(`'${pkg}/`) || error.message.includes(`"${pkg}/`);
6318
6710
  return false;
@@ -6334,6 +6726,7 @@ var PackageManager = class {
6334
6726
  upgradeManifestsMap = /* @__PURE__ */ new Map();
6335
6727
  subgraphsMap = /* @__PURE__ */ new Map();
6336
6728
  processorMap = /* @__PURE__ */ new Map();
6729
+ piecesMap = /* @__PURE__ */ new Map();
6337
6730
  configWatcher;
6338
6731
  debouncedUpdateCallbacks = /* @__PURE__ */ new Map();
6339
6732
  eventEmitter = new EventEmitter();
@@ -6352,10 +6745,12 @@ var PackageManager = class {
6352
6745
  const upgradeManifestsMap = await this.loadUpgradeManifests(packages);
6353
6746
  const subgraphsMap = await this.loadSubgraphs(packages);
6354
6747
  const processorsMap = await this.loadProcessors(packages);
6748
+ const piecesMap = await this.loadPieces(packages);
6355
6749
  this.upgradeManifestsMap = upgradeManifestsMap;
6356
6750
  this.updatePackagesMap(documentModelsMap);
6357
6751
  this.updateSubgraphsMap(subgraphsMap);
6358
6752
  this.updateProcessorsMap(processorsMap);
6753
+ this.updatePiecesMap(piecesMap);
6359
6754
  try {
6360
6755
  this.subscribePackages(packages);
6361
6756
  } catch (error) {
@@ -6365,7 +6760,8 @@ var PackageManager = class {
6365
6760
  documentModels: getUniqueDocumentModels(...Array.from(documentModelsMap.values())),
6366
6761
  upgradeManifests: this.getUniqueUpgradeManifests(),
6367
6762
  subgraphs: subgraphsMap,
6368
- processors: processorsMap
6763
+ processors: processorsMap,
6764
+ pieces: piecesMap
6369
6765
  };
6370
6766
  }
6371
6767
  async loadDocumentModels(packages) {
@@ -6465,9 +6861,34 @@ var PackageManager = class {
6465
6861
  }
6466
6862
  return processorsMap;
6467
6863
  }
6864
+ async loadPieces(packages) {
6865
+ this.logger.debug(`Loading pieces from packages: ${packages.join(", ")}`);
6866
+ const piecesMap = /* @__PURE__ */ new Map();
6867
+ for (const pkg of packages) {
6868
+ const allPieces = [];
6869
+ const failures = [];
6870
+ let succeeded = false;
6871
+ for (const loader of this.loaders) try {
6872
+ allPieces.push(...await loader.loadPieces(pkg));
6873
+ succeeded = true;
6874
+ break;
6875
+ } catch (error) {
6876
+ failures.push({
6877
+ loader: loader.name,
6878
+ error
6879
+ });
6880
+ this.logger.debug(`[${loader.name}] Failed to load pieces from package ${pkg}`, error);
6881
+ }
6882
+ this.maybeWarnAllLoadersFailed("pieces", pkg, succeeded, failures);
6883
+ if (allPieces.length > 0) this.logger.info("Loaded @count piece(s) from package @pkg: @pieces", allPieces.length, pkg, allPieces.map((piece) => piece.name).join(", "));
6884
+ piecesMap.set(pkg, allPieces);
6885
+ }
6886
+ return piecesMap;
6887
+ }
6468
6888
  maybeWarnAllLoadersFailed(kind, pkg, succeeded, failures) {
6469
6889
  if (succeeded || failures.length === 0) return;
6470
- const realFailures = failures.filter(({ error }) => !isExpectedLoaderMiss(error, pkg));
6890
+ const subPath = kind === "document models" ? "document-models" : kind;
6891
+ const realFailures = failures.filter(({ error }) => !isExpectedLoaderMiss(error, pkg, subPath));
6471
6892
  if (realFailures.length === 0) return;
6472
6893
  const details = realFailures.map(({ loader, error }) => {
6473
6894
  return ` [${loader}] ${error instanceof Error ? error.message : String(error)}`;
@@ -6507,6 +6928,15 @@ var PackageManager = class {
6507
6928
  else processorsMap.set(pkg, pkgProcessors);
6508
6929
  this.updateProcessorsMap(processorsMap);
6509
6930
  }
6931
+ async updatePiecesForPackage(pkg) {
6932
+ this.logger.debug(`Updating pieces for package: ${pkg}`);
6933
+ const pieces = await this.loadPieces([pkg]);
6934
+ const piecesMap = new Map(this.piecesMap);
6935
+ const pkgPieces = pieces.get(pkg) ?? [];
6936
+ if (pkgPieces.length === 0) piecesMap.delete(pkg);
6937
+ else piecesMap.set(pkg, pkgPieces);
6938
+ this.updatePiecesMap(piecesMap);
6939
+ }
6510
6940
  /** Debounced per key so repeated subscribePackages calls reuse one timer. */
6511
6941
  getDebouncedUpdateCallback(key, update) {
6512
6942
  if (!this.debouncedUpdateCallbacks.has(key)) {
@@ -6525,10 +6955,12 @@ var PackageManager = class {
6525
6955
  const onDocumentModels = this.getDebouncedUpdateCallback(`${pkg}:document-models`, () => this.updateDocumentModelsForPackage(pkg));
6526
6956
  const onSubgraphs = this.getDebouncedUpdateCallback(`${pkg}:subgraphs`, () => this.updateSubgraphsForPackage(pkg));
6527
6957
  const onProcessors = this.getDebouncedUpdateCallback(`${pkg}:processors`, () => this.updateProcessorsForPackage(pkg));
6958
+ const onPieces = this.getDebouncedUpdateCallback(`${pkg}:pieces`, () => this.updatePiecesForPackage(pkg));
6528
6959
  for (const loader of this.loaders) {
6529
6960
  if (loader.onDocumentModelsChange) unsubs.push(loader.onDocumentModelsChange(pkg, onDocumentModels));
6530
6961
  if (loader.onSubgraphsChange) unsubs.push(loader.onSubgraphsChange(pkg, onSubgraphs));
6531
6962
  if (loader.onProcessorsChange) unsubs.push(loader.onProcessorsChange(pkg, onProcessors));
6963
+ if (loader.onPiecesChange) unsubs.push(loader.onPiecesChange(pkg, onPieces));
6532
6964
  }
6533
6965
  }
6534
6966
  return () => {
@@ -6576,6 +7008,19 @@ var PackageManager = class {
6576
7008
  this.processorMap = processorsMap;
6577
7009
  this.eventEmitter.emit("processorsChange", processorsMap);
6578
7010
  }
7011
+ updatePiecesMap(piecesMap) {
7012
+ const oldPackages = Array.from(this.piecesMap.keys());
7013
+ const newPackages = Array.from(piecesMap.keys());
7014
+ oldPackages.filter((pkg) => !newPackages.includes(pkg)).forEach((pkg) => {
7015
+ this.logger.info("Removed pieces from: @pkg", pkg);
7016
+ });
7017
+ this.piecesMap = piecesMap;
7018
+ this.eventEmitter.emit("piecesChange", piecesMap);
7019
+ }
7020
+ /** The pieces every loaded package ships, keyed by the package. */
7021
+ getPieces() {
7022
+ return new Map(this.piecesMap);
7023
+ }
6579
7024
  /**
6580
7025
  * Remove `pkg` from all package maps and emit the change events, tearing
6581
7026
  * down everything the package registered (subgraphs, processors, document
@@ -6594,9 +7039,11 @@ var PackageManager = class {
6594
7039
  this.upgradeManifestsMap = dropKey(this.upgradeManifestsMap);
6595
7040
  this.subgraphsMap = dropKey(this.subgraphsMap);
6596
7041
  this.processorMap = dropKey(this.processorMap);
7042
+ this.piecesMap = dropKey(this.piecesMap);
6597
7043
  this.updatePackagesMap(this.docModelsMap);
6598
7044
  this.updateSubgraphsMap(this.subgraphsMap);
6599
7045
  this.updateProcessorsMap(this.processorMap);
7046
+ this.updatePiecesMap(this.piecesMap);
6600
7047
  }
6601
7048
  onDocumentModelsChange(handler) {
6602
7049
  this.eventEmitter.on("documentModelsChange", handler);
@@ -6607,6 +7054,9 @@ var PackageManager = class {
6607
7054
  onProcessorsChange(handler) {
6608
7055
  this.eventEmitter.on("processorsChange", handler);
6609
7056
  }
7057
+ onPiecesChange(handler) {
7058
+ this.eventEmitter.on("piecesChange", handler);
7059
+ }
6610
7060
  };
6611
7061
  //#endregion
6612
7062
  //#region src/config.ts
@@ -6984,13 +7434,11 @@ var AuthService = class {
6984
7434
  return new Response(JSON.stringify({ error: "Authentication failed" }), { status: 401 });
6985
7435
  }
6986
7436
  }
7437
+ /** Throws only for a bearer that is present and unusable, as HTTP 401s it. */
6987
7438
  async authenticateWebSocketConnection(connectionParams) {
6988
7439
  if (!this.resolvesIdentity) return null;
6989
7440
  const authHeader = connectionParams.authorization;
6990
- if (!authHeader) {
6991
- if (!this.config.enabled) return null;
6992
- throw new Error("Missing authorization in connection parameters");
6993
- }
7441
+ if (!authHeader) return null;
6994
7442
  const token = authHeader.split(" ")[1];
6995
7443
  if (!token) throw new Error("Invalid authorization format");
6996
7444
  const verified = await this.verifyToken(token);
@@ -7676,10 +8124,23 @@ function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
7676
8124
  return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
7677
8125
  }
7678
8126
  /**
8127
+ * Resolves the gateway adapter type from the `GATEWAY_ADAPTER` env var.
8128
+ * Defaults to "apollo" (the federation gateway, production behavior).
8129
+ * "stitching" selects the in-process graphql-tools merge gateway (#1565
8130
+ * prototype); "mercurius" the Fastify federation gateway.
8131
+ */
8132
+ function resolveGatewayAdapterType(logger) {
8133
+ const configured = process.env.GATEWAY_ADAPTER;
8134
+ if (configured === void 0) return "apollo";
8135
+ if (configured === "apollo" || configured === "mercurius" || configured === "stitching") return configured;
8136
+ logger.warn(`Unknown GATEWAY_ADAPTER="${configured}"; falling back to "apollo"`);
8137
+ return "apollo";
8138
+ }
8139
+ /**
7679
8140
  * Sets up the subgraph manager and registers subgraphs
7680
8141
  */
7681
8142
  async function setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes) {
7682
- const graphqlManager = new GraphQLManager(config.basePath, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, logger, httpAdapter, await createGatewayAdapter("apollo", logger), authService, documentPermissionService, { enableDocumentModelSubgraphs }, port, authorizationService, reactorDriveClient, syncServingGate, httpRoutes);
8143
+ const graphqlManager = new GraphQLManager(config.basePath, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, logger, httpAdapter, await createGatewayAdapter(resolveGatewayAdapterType(logger), logger), authService, documentPermissionService, { enableDocumentModelSubgraphs }, port, authorizationService, reactorDriveClient, syncServingGate, httpRoutes);
7683
8144
  await graphqlManager.init(subgraphs.core, authFetchMiddleware, requireAuthFetchMiddleware);
7684
8145
  for (const [packageName, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql", false, packageName);
7685
8146
  await graphqlManager.updateRouter();
@@ -7872,7 +8333,7 @@ async function _setupCommonInfrastructure(options) {
7872
8333
  if (requireAuthenticatedCaller) {
7873
8334
  requireAuthFetchMiddleware = createRequireAuthFetchMiddleware();
7874
8335
  logger.info("Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph");
7875
- }
8336
+ } else logger.warn("Anonymous callers are admitted on every transport, including WebSocket subscriptions: REQUIRE_AUTHENTICATED_CALLER is not set. The authorization policy gates the reactor's own documents, but a package-provided subgraph authorizes nothing on its own. Set REQUIRE_AUTHENTICATED_CALLER=true to refuse anonymous callers before any subgraph sees them.");
7876
8337
  }
7877
8338
  const dbClosers = [];
7878
8339
  const { relationalDb, analyticsStore, closers: analyticsClosers } = await initializeDatabaseAndAnalytics(options.dbPath, options.pgliteFactory);
@@ -8026,6 +8487,8 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
8026
8487
  attachmentReferenceIndex,
8027
8488
  attachmentAccess,
8028
8489
  authService,
8490
+ authorizationService,
8491
+ relationalDb,
8029
8492
  dispose
8030
8493
  };
8031
8494
  }
@@ -8222,7 +8685,7 @@ var PackageManagementService = class {
8222
8685
  }
8223
8686
  };
8224
8687
  //#endregion
8225
- export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessService, AuthDecision, AuthDecisionSchema, AuthService, AuthSubgraph, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, CORE_PACKAGE_NAME, CanonicalDocumentIdResolutionError, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, EvaluateActionsDocument, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, HttpRouteService, ImportPackageLoader, InMemoryPackageStorage, InvalidNamespaceError, JobChangesDocument, MemoryWebhookStore, MoveRelationshipDocument, MutateDocumentAsyncDocument, MutateDocumentDocument, OperationContextInputSchema, OperationInputSchema, OperationWithContextInputSchema, OperationsFilterInputSchema, PACKAGE_ROUTE_SEGMENT, PGLITE_UTC_PARSERS, PackageManagementService, PackageManager, PackagesSubgraph, PagingInputSchema, PhDocumentFieldsFragmentDoc, PollSyncEnvelopesDocument, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RelationalWebhookStore, RemoteCursorInputSchema, RemoteFilterInputSchema, RemoveRelationshipDocument, RenameDocumentDocument, SearchFilterInputSchema, SetPreferredEditorDocument, SyncEnvelopeInputSchema, SyncEnvelopeType, SyncEnvelopeTypeSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, ViewFilterInputSchema, WEBHOOK_SEGMENT, WebhookService, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertHostMountPath, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createCanonicalDocumentIdResolver, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createRenownCredentialVerifier, createSchema, decodeExplorerUrlState, definedNonNullAnySchema, driveIdFromUrl, extractSubgraphsFromModule, generateDocumentModelSchema, getAuthContext, getDbClient, getDocumentModelSchemaName, getDocumentModelTypeDefs, getExplorerPrefix, getGitHash, getGitUrl, getSdk, getUniqueDocumentModels, getUniqueUpgradeManifests, getVersion, initAnalyticsStoreSql, initializeAndStartAPI, isDefinedNonNullAny, isExpectedLoaderMiss, isSubgraphClass, namespacePath, namespaceSegments, newWebhookToken, parseDriveUrl, parseWebhookBody, redactHeaders, renderGraphqlPlayground, resolvePackageName, resolveRenownConfig, verifyWebhook };
8688
+ export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessService, AuthDecision, AuthDecisionSchema, AuthEvaluationUnsupportedError, AuthService, AuthSubgraph, AuthenticationRequiredError, AuthorizationPolicy, AuthorizedDocumentHandle, BaseSubgraph, CORE_PACKAGE_NAME, CanonicalDocumentIdResolutionError, ChannelMetaInputSchema, CreateDocumentDocument, CreateEmptyDocumentDocument, DeleteDocumentDocument, DeleteDocumentsDocument, DocumentChangeType, DocumentChangeTypeSchema, DocumentChangesDocument, DocumentOperationsFilterInputSchema, DocumentPermissionService, DocumentRelationshipFieldsFragmentDoc, EvaluateActionsDocument, FindDocumentsDocument, ForbiddenError, GetDocumentDocument, GetDocumentIncomingRelationshipEdgesDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipEdgesDocument, GetDocumentOutgoingRelationshipsDocument, GetDocumentWithOperationsDocument, GetJobStatusDocument, GraphQLManager, HttpDocumentModelLoader, HttpPackageLoader, HttpRouteService, ImportPackageLoader, InMemoryPackageStorage, InvalidNamespaceError, JobChangesDocument, MalformedStoredOperationError, 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, UpdateRelationshipDocument, ViewFilterInputSchema, WEBHOOK_SEGMENT, WS_AUTH_CLOSE_REASONS, WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE_REASON_BEARER_REJECTED, WebhookService, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertHostMountPath, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, createAuthFetchMiddleware, createCanonicalDocumentIdResolver, createGatewayAdapter, createHttpAdapter, createMergedSchema, createReactorGraphQLClient, createRenownCredentialVerifier, createSchema, createWsAuthHandlers, 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 };
8226
8689
 
8227
8690
  //# sourceMappingURL=index.mjs.map
8228
- //# debugId=46ad8545-02e9-5fb8-b003-7b9b93f82f75
8691
+ //# debugId=cdb25466-37b1-55fd-be52-a82be4286288