@powerhousedao/reactor-api 6.2.3-dev.1 → 6.2.3-dev.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-gateway-apollo-DdamNLPT.mjs → adapter-gateway-apollo-CxJNBVyD.mjs} +7 -8
- package/dist/adapter-gateway-apollo-CxJNBVyD.mjs.map +1 -0
- package/dist/adapter-gateway-apollo-DowrX6E_.mjs +4 -0
- package/dist/{adapter-gateway-mercurius-CLmTtNJC.mjs → adapter-gateway-mercurius-CZVy88Za.mjs} +6 -5
- package/dist/adapter-gateway-mercurius-CZVy88Za.mjs.map +1 -0
- package/dist/adapter-gateway-stitching-CaZjvRzx.mjs +316 -0
- package/dist/adapter-gateway-stitching-CaZjvRzx.mjs.map +1 -0
- package/dist/{adapter-http-express-54gdI9bu.mjs → adapter-http-express-B39rPhqH.mjs} +4 -4
- package/dist/{adapter-http-express-54gdI9bu.mjs.map → adapter-http-express-B39rPhqH.mjs.map} +1 -1
- package/dist/{adapter-http-fastify-DmccBVOe.mjs → adapter-http-fastify-BwXO6rqE.mjs} +4 -4
- package/dist/{adapter-http-fastify-DmccBVOe.mjs.map → adapter-http-fastify-BwXO6rqE.mjs.map} +1 -1
- package/dist/index.d.mts +239 -16
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +405 -49
- package/dist/index.mjs.map +1 -1
- package/dist/{path-normalize-DWWFLiy6.mjs → path-normalize-BOQwY9wR.mjs} +3 -3
- package/dist/{path-normalize-DWWFLiy6.mjs.map → path-normalize-BOQwY9wR.mjs.map} +1 -1
- package/dist/src/packages/vite-loader.d.mts +1 -1
- package/dist/src/packages/vite-loader.d.mts.map +1 -1
- package/dist/src/packages/vite-loader.mjs +5 -5
- package/dist/src/packages/vite-loader.mjs.map +1 -1
- package/dist/{types-Bz2niABC.d.mts → types-DcYyNcHJ.d.mts} +1 -1
- package/dist/{types-Bz2niABC.d.mts.map → types-DcYyNcHJ.d.mts.map} +1 -1
- package/package.json +17 -14
- package/dist/adapter-gateway-apollo-DdamNLPT.mjs.map +0 -1
- package/dist/adapter-gateway-mercurius-CLmTtNJC.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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]="
|
|
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]="2b9e4602-aa9c-53a6-ad69-f3f871c0ba28")}catch(e){}}();
|
|
3
3
|
import { _ as createAuthorizationService, a as isSubgraphClass, b as MalformedStoredOperationError, d as loadSubgraphs, f as BaseSubgraph, g as AuthorizedDocumentHandle, h as AuthorizationPolicy, i as buildGraphqlOperations, l as loadDocumentModels, m as createCanonicalDocumentIdResolver, n as buildGraphQlDriveDocument, o as debounce, p as CanonicalDocumentIdResolutionError, r as buildGraphqlOperation, s as extractUpgradeManifests, t as buildGraphQlDocument, u as loadProcessors, v as AuthEvaluationUnsupportedError, y as ForbiddenError } from "./utils-XWVecaNn.mjs";
|
|
4
4
|
import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
|
|
5
5
|
import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
|
|
@@ -311,28 +311,40 @@ function createAuthFetchMiddleware(authService) {
|
|
|
311
311
|
async function createGatewayAdapter(type, logger) {
|
|
312
312
|
switch (type) {
|
|
313
313
|
case "apollo": {
|
|
314
|
-
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-
|
|
314
|
+
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-DowrX6E_.mjs");
|
|
315
315
|
return new ApolloGatewayAdapter(logger);
|
|
316
316
|
}
|
|
317
317
|
case "mercurius": {
|
|
318
|
-
const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-
|
|
318
|
+
const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-CZVy88Za.mjs");
|
|
319
319
|
return new MercuriusGatewayAdapter(logger);
|
|
320
320
|
}
|
|
321
|
+
case "stitching": {
|
|
322
|
+
const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-CaZjvRzx.mjs");
|
|
323
|
+
return new StitchingGatewayAdapter(logger);
|
|
324
|
+
}
|
|
321
325
|
}
|
|
322
326
|
}
|
|
323
327
|
async function createHttpAdapter(type) {
|
|
324
328
|
switch (type) {
|
|
325
329
|
case "express": {
|
|
326
|
-
const { createExpressHttpAdapter } = await import("./adapter-http-express-
|
|
330
|
+
const { createExpressHttpAdapter } = await import("./adapter-http-express-B39rPhqH.mjs");
|
|
327
331
|
return createExpressHttpAdapter();
|
|
328
332
|
}
|
|
329
333
|
case "fastify": {
|
|
330
|
-
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-
|
|
334
|
+
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-BwXO6rqE.mjs");
|
|
331
335
|
return createFastifyHttpAdapter();
|
|
332
336
|
}
|
|
333
337
|
}
|
|
334
338
|
}
|
|
335
339
|
//#endregion
|
|
340
|
+
//#region src/graphql/gateway/types.ts
|
|
341
|
+
/** No authenticated caller resolved, and REQUIRE_AUTHENTICATED_CALLER wants one. */
|
|
342
|
+
const WS_CLOSE_REASON_AUTHENTICATION_REQUIRED = "authentication-required";
|
|
343
|
+
/** A bearer was sent and could not be verified. */
|
|
344
|
+
const WS_CLOSE_REASON_BEARER_REJECTED = "bearer-rejected";
|
|
345
|
+
/** Every reason a refusal closes with, for a client matching on the set. */
|
|
346
|
+
const WS_AUTH_CLOSE_REASONS = [WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE_REASON_BEARER_REJECTED];
|
|
347
|
+
//#endregion
|
|
336
348
|
//#region src/http/namespace.ts
|
|
337
349
|
/**
|
|
338
350
|
* A package's namespace is its npm name, used verbatim as one or two path
|
|
@@ -2218,6 +2230,16 @@ const PhDocumentFieldsFragmentDoc = gql`
|
|
|
2218
2230
|
lastModifiedAtUtcIso
|
|
2219
2231
|
}
|
|
2220
2232
|
`;
|
|
2233
|
+
const DocumentRelationshipFieldsFragmentDoc = gql`
|
|
2234
|
+
fragment DocumentRelationshipFields on DocumentRelationship {
|
|
2235
|
+
sourceId
|
|
2236
|
+
targetId
|
|
2237
|
+
relationshipType
|
|
2238
|
+
metadata
|
|
2239
|
+
createdAt
|
|
2240
|
+
updatedAt
|
|
2241
|
+
}
|
|
2242
|
+
`;
|
|
2221
2243
|
const GetDocumentModelsDocument = gql`
|
|
2222
2244
|
query GetDocumentModels($namespace: String, $paging: PagingInput) {
|
|
2223
2245
|
documentModels(namespace: $namespace, paging: $paging) {
|
|
@@ -2346,6 +2368,54 @@ const GetDocumentIncomingRelationshipsDocument = gql`
|
|
|
2346
2368
|
}
|
|
2347
2369
|
${PhDocumentFieldsFragmentDoc}
|
|
2348
2370
|
`;
|
|
2371
|
+
const GetDocumentOutgoingRelationshipEdgesDocument = gql`
|
|
2372
|
+
query GetDocumentOutgoingRelationshipEdges(
|
|
2373
|
+
$sourceIdentifier: String!
|
|
2374
|
+
$relationshipType: String
|
|
2375
|
+
$view: ViewFilterInput
|
|
2376
|
+
$paging: PagingInput
|
|
2377
|
+
) {
|
|
2378
|
+
documentOutgoingRelationshipEdges(
|
|
2379
|
+
sourceIdentifier: $sourceIdentifier
|
|
2380
|
+
relationshipType: $relationshipType
|
|
2381
|
+
view: $view
|
|
2382
|
+
paging: $paging
|
|
2383
|
+
) {
|
|
2384
|
+
items {
|
|
2385
|
+
...DocumentRelationshipFields
|
|
2386
|
+
}
|
|
2387
|
+
totalCount
|
|
2388
|
+
hasNextPage
|
|
2389
|
+
hasPreviousPage
|
|
2390
|
+
cursor
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
${DocumentRelationshipFieldsFragmentDoc}
|
|
2394
|
+
`;
|
|
2395
|
+
const GetDocumentIncomingRelationshipEdgesDocument = gql`
|
|
2396
|
+
query GetDocumentIncomingRelationshipEdges(
|
|
2397
|
+
$targetIdentifier: String!
|
|
2398
|
+
$relationshipType: String
|
|
2399
|
+
$view: ViewFilterInput
|
|
2400
|
+
$paging: PagingInput
|
|
2401
|
+
) {
|
|
2402
|
+
documentIncomingRelationshipEdges(
|
|
2403
|
+
targetIdentifier: $targetIdentifier
|
|
2404
|
+
relationshipType: $relationshipType
|
|
2405
|
+
view: $view
|
|
2406
|
+
paging: $paging
|
|
2407
|
+
) {
|
|
2408
|
+
items {
|
|
2409
|
+
...DocumentRelationshipFields
|
|
2410
|
+
}
|
|
2411
|
+
totalCount
|
|
2412
|
+
hasNextPage
|
|
2413
|
+
hasPreviousPage
|
|
2414
|
+
cursor
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
${DocumentRelationshipFieldsFragmentDoc}
|
|
2418
|
+
`;
|
|
2349
2419
|
const FindDocumentsDocument = gql`
|
|
2350
2420
|
query FindDocuments(
|
|
2351
2421
|
$search: SearchFilterInput
|
|
@@ -2536,12 +2606,34 @@ const AddRelationshipDocument = gql`
|
|
|
2536
2606
|
$sourceIdentifier: String!
|
|
2537
2607
|
$targetIdentifier: String!
|
|
2538
2608
|
$relationshipType: String!
|
|
2609
|
+
$metadata: JSONObject
|
|
2539
2610
|
$branch: String
|
|
2540
2611
|
) {
|
|
2541
2612
|
addRelationship(
|
|
2542
2613
|
sourceIdentifier: $sourceIdentifier
|
|
2543
2614
|
targetIdentifier: $targetIdentifier
|
|
2544
2615
|
relationshipType: $relationshipType
|
|
2616
|
+
metadata: $metadata
|
|
2617
|
+
branch: $branch
|
|
2618
|
+
) {
|
|
2619
|
+
...PHDocumentFields
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
${PhDocumentFieldsFragmentDoc}
|
|
2623
|
+
`;
|
|
2624
|
+
const UpdateRelationshipDocument = gql`
|
|
2625
|
+
mutation UpdateRelationship(
|
|
2626
|
+
$sourceIdentifier: String!
|
|
2627
|
+
$targetIdentifier: String!
|
|
2628
|
+
$relationshipType: String!
|
|
2629
|
+
$metadata: JSONObject
|
|
2630
|
+
$branch: String
|
|
2631
|
+
) {
|
|
2632
|
+
updateRelationship(
|
|
2633
|
+
sourceIdentifier: $sourceIdentifier
|
|
2634
|
+
targetIdentifier: $targetIdentifier
|
|
2635
|
+
relationshipType: $relationshipType
|
|
2636
|
+
metadata: $metadata
|
|
2545
2637
|
branch: $branch
|
|
2546
2638
|
) {
|
|
2547
2639
|
...PHDocumentFields
|
|
@@ -2735,6 +2827,12 @@ function getSdk(requester) {
|
|
|
2735
2827
|
GetDocumentIncomingRelationships(variables, options) {
|
|
2736
2828
|
return requester(GetDocumentIncomingRelationshipsDocument, variables, options);
|
|
2737
2829
|
},
|
|
2830
|
+
GetDocumentOutgoingRelationshipEdges(variables, options) {
|
|
2831
|
+
return requester(GetDocumentOutgoingRelationshipEdgesDocument, variables, options);
|
|
2832
|
+
},
|
|
2833
|
+
GetDocumentIncomingRelationshipEdges(variables, options) {
|
|
2834
|
+
return requester(GetDocumentIncomingRelationshipEdgesDocument, variables, options);
|
|
2835
|
+
},
|
|
2738
2836
|
FindDocuments(variables, options) {
|
|
2739
2837
|
return requester(FindDocumentsDocument, variables, options);
|
|
2740
2838
|
},
|
|
@@ -2768,6 +2866,9 @@ function getSdk(requester) {
|
|
|
2768
2866
|
AddRelationship(variables, options) {
|
|
2769
2867
|
return requester(AddRelationshipDocument, variables, options);
|
|
2770
2868
|
},
|
|
2869
|
+
UpdateRelationship(variables, options) {
|
|
2870
|
+
return requester(UpdateRelationshipDocument, variables, options);
|
|
2871
|
+
},
|
|
2771
2872
|
RemoveRelationship(variables, options) {
|
|
2772
2873
|
return requester(RemoveRelationshipDocument, variables, options);
|
|
2773
2874
|
},
|
|
@@ -2846,6 +2947,32 @@ function toPhDocumentResultPage(result) {
|
|
|
2846
2947
|
};
|
|
2847
2948
|
}
|
|
2848
2949
|
/**
|
|
2950
|
+
* Converts a relationship edge from the indexer to GraphQL format.
|
|
2951
|
+
*/
|
|
2952
|
+
function toGqlDocumentRelationship(relationship) {
|
|
2953
|
+
return {
|
|
2954
|
+
sourceId: relationship.sourceId,
|
|
2955
|
+
targetId: relationship.targetId,
|
|
2956
|
+
relationshipType: relationship.relationshipType,
|
|
2957
|
+
metadata: relationship.metadata ?? null,
|
|
2958
|
+
createdAt: relationship.createdAt,
|
|
2959
|
+
updatedAt: relationship.updatedAt
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
/**
|
|
2963
|
+
* Converts a PagedResults of relationship edges to GraphQL
|
|
2964
|
+
* DocumentRelationshipResultPage format
|
|
2965
|
+
*/
|
|
2966
|
+
function toDocumentRelationshipResultPage(result) {
|
|
2967
|
+
return {
|
|
2968
|
+
cursor: result.nextCursor ?? null,
|
|
2969
|
+
hasNextPage: !!result.nextCursor,
|
|
2970
|
+
hasPreviousPage: !!result.options.cursor,
|
|
2971
|
+
items: result.results.map(toGqlDocumentRelationship),
|
|
2972
|
+
totalCount: result.totalCount ?? result.results.length
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
/**
|
|
2849
2976
|
* Converts a PHDocument from ReactorClient to GraphQL PhDocument format
|
|
2850
2977
|
*/
|
|
2851
2978
|
function toGqlPhDocument(doc) {
|
|
@@ -3291,6 +3418,54 @@ async function documentIncomingRelationships(reactorClient, args, subject) {
|
|
|
3291
3418
|
throw new GraphQLError(`Failed to convert incoming relationships to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3292
3419
|
}
|
|
3293
3420
|
}
|
|
3421
|
+
async function documentOutgoingRelationshipEdges(reactorClient, args, subject) {
|
|
3422
|
+
const view = toRelationshipViewFilter(args.view, subject);
|
|
3423
|
+
const paging = toRelationshipPagingOptions(args.paging);
|
|
3424
|
+
let result;
|
|
3425
|
+
try {
|
|
3426
|
+
result = await reactorClient.getOutgoingRelationshipEdges(args.sourceIdentifier, fromInputMaybe(args.relationshipType), view, paging);
|
|
3427
|
+
} catch (error) {
|
|
3428
|
+
throw new GraphQLError(`Failed to fetch outgoing relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3429
|
+
}
|
|
3430
|
+
try {
|
|
3431
|
+
return toDocumentRelationshipResultPage(result);
|
|
3432
|
+
} catch (error) {
|
|
3433
|
+
throw new GraphQLError(`Failed to convert outgoing relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
async function documentIncomingRelationshipEdges(reactorClient, args, subject) {
|
|
3437
|
+
const view = toRelationshipViewFilter(args.view, subject);
|
|
3438
|
+
const paging = toRelationshipPagingOptions(args.paging);
|
|
3439
|
+
let result;
|
|
3440
|
+
try {
|
|
3441
|
+
result = await reactorClient.getIncomingRelationshipEdges(args.targetIdentifier, fromInputMaybe(args.relationshipType), view, paging);
|
|
3442
|
+
} catch (error) {
|
|
3443
|
+
throw new GraphQLError(`Failed to fetch incoming relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3444
|
+
}
|
|
3445
|
+
try {
|
|
3446
|
+
return toDocumentRelationshipResultPage(result);
|
|
3447
|
+
} catch (error) {
|
|
3448
|
+
throw new GraphQLError(`Failed to convert incoming relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
function toRelationshipViewFilter(input, subject) {
|
|
3452
|
+
if (!input) return subject ? { subject } : void 0;
|
|
3453
|
+
return {
|
|
3454
|
+
subject,
|
|
3455
|
+
branch: fromInputMaybe(input.branch),
|
|
3456
|
+
scopes: toMutableArray(fromInputMaybe(input.scopes))
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
function toRelationshipPagingOptions(input) {
|
|
3460
|
+
if (!input) return;
|
|
3461
|
+
const cursor = fromInputMaybe(input.cursor);
|
|
3462
|
+
const limit = fromInputMaybe(input.limit);
|
|
3463
|
+
if (!cursor && !limit) return;
|
|
3464
|
+
return {
|
|
3465
|
+
cursor: cursor || "",
|
|
3466
|
+
limit: limit || 10
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3294
3469
|
async function findDocuments(reactorClient, args, subject) {
|
|
3295
3470
|
let view = subject ? { subject } : void 0;
|
|
3296
3471
|
if (args.view) view = {
|
|
@@ -3622,9 +3797,10 @@ async function setPreferredEditor(reactorClient, args, signal) {
|
|
|
3622
3797
|
}
|
|
3623
3798
|
async function addRelationship(reactorClient, args) {
|
|
3624
3799
|
const branch = fromInputMaybe(args.branch);
|
|
3800
|
+
const metadata = fromInputMaybe(args.metadata);
|
|
3625
3801
|
let result;
|
|
3626
3802
|
try {
|
|
3627
|
-
result = await reactorClient.addRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, branch);
|
|
3803
|
+
result = await reactorClient.addRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, metadata, branch);
|
|
3628
3804
|
} catch (error) {
|
|
3629
3805
|
throw new GraphQLError(`Failed to add relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3630
3806
|
}
|
|
@@ -3634,6 +3810,21 @@ async function addRelationship(reactorClient, args) {
|
|
|
3634
3810
|
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3635
3811
|
}
|
|
3636
3812
|
}
|
|
3813
|
+
async function updateRelationship(reactorClient, args) {
|
|
3814
|
+
const branch = fromInputMaybe(args.branch);
|
|
3815
|
+
const metadata = fromInputMaybe(args.metadata) ?? null;
|
|
3816
|
+
let result;
|
|
3817
|
+
try {
|
|
3818
|
+
result = await reactorClient.updateRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, metadata, branch);
|
|
3819
|
+
} catch (error) {
|
|
3820
|
+
throw new GraphQLError(`Failed to update relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3821
|
+
}
|
|
3822
|
+
try {
|
|
3823
|
+
return toGqlPhDocument(result);
|
|
3824
|
+
} catch (error) {
|
|
3825
|
+
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3637
3828
|
async function removeRelationship(reactorClient, args) {
|
|
3638
3829
|
const branch = fromInputMaybe(args.branch);
|
|
3639
3830
|
let result;
|
|
@@ -3781,6 +3972,7 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3781
3972
|
} catch (error) {
|
|
3782
3973
|
throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3783
3974
|
}
|
|
3975
|
+
remote.channel.notePoll();
|
|
3784
3976
|
const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId) && !heldOpIds.has(syncOp.id)).map((syncOp) => ({
|
|
3785
3977
|
documentId: syncOp.documentId,
|
|
3786
3978
|
error: syncOp.error?.message ?? "Unknown error",
|
|
@@ -3905,6 +4097,7 @@ function pushSyncEnvelopes(syncManager, args) {
|
|
|
3905
4097
|
throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3906
4098
|
}
|
|
3907
4099
|
if (!envelope.operations || envelope.operations.length === 0) continue;
|
|
4100
|
+
remote.channel.notePoll();
|
|
3908
4101
|
const syncOps = envelopesToSyncOperations(envelope, remote.meta.name);
|
|
3909
4102
|
if (!remoteSyncOps.has(remote)) remoteSyncOps.set(remote, []);
|
|
3910
4103
|
remoteSyncOps.get(remote).push(...syncOps);
|
|
@@ -4308,6 +4501,32 @@ const DefaultFeatureFlags = { enableDocumentModelSubgraphs: true };
|
|
|
4308
4501
|
* regenerateDocumentModelSubgraphs).
|
|
4309
4502
|
*/
|
|
4310
4503
|
const DOCUMENT_MODEL_SUBGRAPH_SOURCE = "document-models";
|
|
4504
|
+
function refuseConnection(connection, reason) {
|
|
4505
|
+
const { socket } = connection;
|
|
4506
|
+
socket?.close?.(4403, reason);
|
|
4507
|
+
return false;
|
|
4508
|
+
}
|
|
4509
|
+
function createWsAuthHandlers(opts) {
|
|
4510
|
+
const resolved = /* @__PURE__ */ new WeakMap();
|
|
4511
|
+
return {
|
|
4512
|
+
onConnect: async (connectionParams, connection) => {
|
|
4513
|
+
let user = null;
|
|
4514
|
+
if (opts.authService) try {
|
|
4515
|
+
user = await opts.authService.authenticateWebSocketConnection(connectionParams);
|
|
4516
|
+
} catch (error) {
|
|
4517
|
+
opts.logger.warn("Refusing WebSocket connection: @error", error instanceof Error ? error.message : error);
|
|
4518
|
+
return refuseConnection(connection, WS_CLOSE_REASON_BEARER_REJECTED);
|
|
4519
|
+
}
|
|
4520
|
+
if (user === null && opts.requireAuthenticatedCaller()) {
|
|
4521
|
+
opts.logger.warn("Refusing anonymous WebSocket connection: an authenticated caller is required");
|
|
4522
|
+
return refuseConnection(connection, WS_CLOSE_REASON_AUTHENTICATION_REQUIRED);
|
|
4523
|
+
}
|
|
4524
|
+
resolved.set(connection, user ?? void 0);
|
|
4525
|
+
return true;
|
|
4526
|
+
},
|
|
4527
|
+
context: (connectionParams, connection) => Promise.resolve(opts.buildContext(connectionParams, resolved.get(connection)))
|
|
4528
|
+
};
|
|
4529
|
+
}
|
|
4311
4530
|
var GraphQLManager = class {
|
|
4312
4531
|
initialized = false;
|
|
4313
4532
|
coreSubgraphsMap = /* @__PURE__ */ new Map();
|
|
@@ -4380,13 +4599,17 @@ var GraphQLManager = class {
|
|
|
4380
4599
|
this.httpAdapter.setupMiddleware({ bodyLimit: "50mb" });
|
|
4381
4600
|
const driveRoutePath = path.posix.join(this.path, "d/:drive");
|
|
4382
4601
|
const driveMatcher = match(driveRoutePath);
|
|
4383
|
-
|
|
4602
|
+
const driveInfoHandler = async (request) => {
|
|
4384
4603
|
const url = new URL(request.url);
|
|
4385
4604
|
const matched = driveMatcher(url.pathname);
|
|
4386
4605
|
const driveIdOrSlug = matched ? matched.params.drive : void 0;
|
|
4387
4606
|
if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
|
|
4388
4607
|
try {
|
|
4389
4608
|
const driveDoc = await this.reactorClient.get(driveIdOrSlug);
|
|
4609
|
+
if (!await this.authorizationService.canRead(driveDoc.header.id, getAuthContext(request)?.user?.address)) {
|
|
4610
|
+
this.logger.debug(`Drive read refused: ${driveIdOrSlug}`);
|
|
4611
|
+
return Response.json({ error: "Drive not found" }, { status: 404 });
|
|
4612
|
+
}
|
|
4390
4613
|
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
4614
|
return Response.json({
|
|
4392
4615
|
id: driveDoc.header.id,
|
|
@@ -4400,7 +4623,8 @@ var GraphQLManager = class {
|
|
|
4400
4623
|
this.logger.debug(`Drive not found: ${driveIdOrSlug}`, error);
|
|
4401
4624
|
return Response.json({ error: "Drive not found" }, { status: 404 });
|
|
4402
4625
|
}
|
|
4403
|
-
}
|
|
4626
|
+
};
|
|
4627
|
+
this.httpAdapter.mount(driveRoutePath, this.#authMiddleware ? this.#authMiddleware(driveInfoHandler) : driveInfoHandler);
|
|
4404
4628
|
this.logger.info(`Registered REST endpoint: GET ${driveRoutePath}`);
|
|
4405
4629
|
await this.#setupCoreSubgraphs("graphql", coreSubgraphs);
|
|
4406
4630
|
if (this.featureFlags.enableDocumentModelSubgraphs) await this.#setupDocumentModelSubgraphs("graphql", models);
|
|
@@ -4691,17 +4915,6 @@ var GraphQLManager = class {
|
|
|
4691
4915
|
...fields
|
|
4692
4916
|
};
|
|
4693
4917
|
}
|
|
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
4918
|
#makeContextFactory() {
|
|
4706
4919
|
return (request) => {
|
|
4707
4920
|
const authCtx = getAuthContext(request);
|
|
@@ -4720,7 +4933,20 @@ var GraphQLManager = class {
|
|
|
4720
4933
|
};
|
|
4721
4934
|
}
|
|
4722
4935
|
#makeWsContextFactory() {
|
|
4723
|
-
return (
|
|
4936
|
+
return createWsAuthHandlers({
|
|
4937
|
+
authService: this.authService,
|
|
4938
|
+
requireAuthenticatedCaller: () => this.#requireAuthMiddleware !== void 0,
|
|
4939
|
+
logger: this.logger,
|
|
4940
|
+
buildContext: (connectionParams, user) => {
|
|
4941
|
+
const context = {
|
|
4942
|
+
headers: connectionParams,
|
|
4943
|
+
db: this.relationalDb,
|
|
4944
|
+
...this.getAdditionalContextFields()
|
|
4945
|
+
};
|
|
4946
|
+
if (user) context.user = user;
|
|
4947
|
+
return context;
|
|
4948
|
+
}
|
|
4949
|
+
});
|
|
4724
4950
|
}
|
|
4725
4951
|
setSupergraph(supergraph, subgraphs) {
|
|
4726
4952
|
const accepted = [];
|
|
@@ -4823,11 +5049,15 @@ var GraphQLManager = class {
|
|
|
4823
5049
|
#getSubgraphDefinitions() {
|
|
4824
5050
|
const subgraphs = this.#getAllSubgraphs();
|
|
4825
5051
|
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
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
5052
|
+
return Array.from(subgraphs.entries()).map(([subgraphPath, subgraph]) => {
|
|
5053
|
+
const module = this.#buildSubgraphSchemaModule(subgraph);
|
|
5054
|
+
return {
|
|
5055
|
+
name: subgraphPath.replace("/", ":"),
|
|
5056
|
+
typeDefs: module.typeDefs,
|
|
5057
|
+
resolvers: module.resolvers,
|
|
5058
|
+
url: `${herokuOrLocal}${subgraphPath}`
|
|
5059
|
+
};
|
|
5060
|
+
});
|
|
4831
5061
|
}
|
|
4832
5062
|
async #createSupergraphGateway() {
|
|
4833
5063
|
const superGraphPath = path.posix.join(this.path, "graphql");
|
|
@@ -5190,6 +5420,21 @@ const PHDocumentResultPageDTO = z.object({
|
|
|
5190
5420
|
hasPreviousPage: z.boolean(),
|
|
5191
5421
|
cursor: z.string().nullable().optional()
|
|
5192
5422
|
}).strip();
|
|
5423
|
+
const DocumentRelationshipDTO = z.object({
|
|
5424
|
+
sourceId: z.string(),
|
|
5425
|
+
targetId: z.string(),
|
|
5426
|
+
relationshipType: z.string(),
|
|
5427
|
+
metadata: JSONObjectDTO.nullable().optional(),
|
|
5428
|
+
createdAt: DateTimeDTO,
|
|
5429
|
+
updatedAt: DateTimeDTO
|
|
5430
|
+
}).strip();
|
|
5431
|
+
const DocumentRelationshipResultPageDTO = z.object({
|
|
5432
|
+
items: z.array(DocumentRelationshipDTO),
|
|
5433
|
+
totalCount: z.number().int(),
|
|
5434
|
+
hasNextPage: z.boolean(),
|
|
5435
|
+
hasPreviousPage: z.boolean(),
|
|
5436
|
+
cursor: z.string().nullable().optional()
|
|
5437
|
+
}).strip();
|
|
5193
5438
|
const DocumentWithChildrenDTO = z.object({
|
|
5194
5439
|
document: PHDocumentDTO,
|
|
5195
5440
|
childIds: z.array(z.string())
|
|
@@ -5293,6 +5538,12 @@ const operationValidators = {
|
|
|
5293
5538
|
GetDocumentIncomingRelationships: (data) => {
|
|
5294
5539
|
if (data.documentIncomingRelationships) PHDocumentResultPageDTO.parse(data.documentIncomingRelationships);
|
|
5295
5540
|
},
|
|
5541
|
+
GetDocumentOutgoingRelationshipEdges: (data) => {
|
|
5542
|
+
if (data.documentOutgoingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentOutgoingRelationshipEdges);
|
|
5543
|
+
},
|
|
5544
|
+
GetDocumentIncomingRelationshipEdges: (data) => {
|
|
5545
|
+
if (data.documentIncomingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentIncomingRelationshipEdges);
|
|
5546
|
+
},
|
|
5296
5547
|
FindDocuments: (data) => {
|
|
5297
5548
|
if (data.findDocuments) PHDocumentResultPageDTO.parse(data.findDocuments);
|
|
5298
5549
|
},
|
|
@@ -5326,6 +5577,9 @@ const operationValidators = {
|
|
|
5326
5577
|
AddRelationship: (data) => {
|
|
5327
5578
|
if (data.addRelationship) PHDocumentDTO.parse(data.addRelationship);
|
|
5328
5579
|
},
|
|
5580
|
+
UpdateRelationship: (data) => {
|
|
5581
|
+
if (data.updateRelationship) PHDocumentDTO.parse(data.updateRelationship);
|
|
5582
|
+
},
|
|
5329
5583
|
RemoveRelationship: (data) => {
|
|
5330
5584
|
if (data.removeRelationship) PHDocumentDTO.parse(data.removeRelationship);
|
|
5331
5585
|
},
|
|
@@ -5367,7 +5621,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
|
|
|
5367
5621
|
}
|
|
5368
5622
|
//#endregion
|
|
5369
5623
|
//#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";
|
|
5624
|
+
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
5625
|
//#endregion
|
|
5372
5626
|
//#region src/graphql/reactor/pubsub.ts
|
|
5373
5627
|
const pubSub = new PubSub();
|
|
@@ -5414,7 +5668,7 @@ function ensureJobSubscription(reactorClient, jobId) {
|
|
|
5414
5668
|
createdAt: jobInfo.createdAtUtcIso,
|
|
5415
5669
|
completedAt: jobInfo.completedAtUtcIso ?? null,
|
|
5416
5670
|
error: jobInfo.error?.message ?? null,
|
|
5417
|
-
result: jobInfo.result ??
|
|
5671
|
+
result: jobInfo.result ?? null
|
|
5418
5672
|
},
|
|
5419
5673
|
jobId,
|
|
5420
5674
|
documentId: jobInfo.documentId
|
|
@@ -5492,6 +5746,48 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5492
5746
|
}
|
|
5493
5747
|
}
|
|
5494
5748
|
/**
|
|
5749
|
+
* Drops the relationship edges whose far-end document the caller cannot read.
|
|
5750
|
+
*
|
|
5751
|
+
* An edge names two documents, so returning it discloses the far end's id. The
|
|
5752
|
+
* document-shaped relationship queries already withhold far-end documents the
|
|
5753
|
+
* caller cannot read; the edge-shaped queries have to withhold the same ones,
|
|
5754
|
+
* or they become a way to enumerate around that check. Supreme admins read
|
|
5755
|
+
* everything, so the per-edge check is skipped for them.
|
|
5756
|
+
*
|
|
5757
|
+
* The counts describe what is served; the cursor describes where to resume in
|
|
5758
|
+
* the underlying unfiltered stream, so it is left as it came. A filtered page
|
|
5759
|
+
* is therefore often shorter than the requested limit, and that is the stream
|
|
5760
|
+
* continuing rather than a sign it ended.
|
|
5761
|
+
*/
|
|
5762
|
+
async filterRelationshipEdges(page, farEnd, ctx) {
|
|
5763
|
+
if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
|
|
5764
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
5765
|
+
const items = [];
|
|
5766
|
+
for (const edge of page.items) {
|
|
5767
|
+
const documentId = edge[farEnd];
|
|
5768
|
+
let canRead = decisions.get(documentId);
|
|
5769
|
+
if (canRead === void 0) {
|
|
5770
|
+
canRead = await this.canReadDocument(documentId, ctx);
|
|
5771
|
+
decisions.set(documentId, canRead);
|
|
5772
|
+
}
|
|
5773
|
+
if (canRead) items.push(edge);
|
|
5774
|
+
}
|
|
5775
|
+
return this.servedPage(page, items);
|
|
5776
|
+
}
|
|
5777
|
+
/**
|
|
5778
|
+
* Restates a page's totalCount over the items that survived a read filter, so
|
|
5779
|
+
* that the difference between the two cannot be read off as a count of what
|
|
5780
|
+
* the caller was not allowed to see.
|
|
5781
|
+
*/
|
|
5782
|
+
servedPage(page, items) {
|
|
5783
|
+
const withheld = page.items.length - items.length;
|
|
5784
|
+
return {
|
|
5785
|
+
...page,
|
|
5786
|
+
items,
|
|
5787
|
+
totalCount: Math.max(0, page.totalCount - withheld)
|
|
5788
|
+
};
|
|
5789
|
+
}
|
|
5790
|
+
/**
|
|
5495
5791
|
* Adds to `forbidden` the canonical document ids in `syncOps` that the caller
|
|
5496
5792
|
* cannot read, checking each distinct id once. Sync operation document ids are
|
|
5497
5793
|
* canonical (never slugs), so no resolution is needed.
|
|
@@ -5519,14 +5815,18 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5519
5815
|
* queued for the adopter.
|
|
5520
5816
|
*
|
|
5521
5817
|
* 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.
|
|
5818
|
+
* signal to authenticate again rather than as a transport failure. Pushes are
|
|
5819
|
+
* refused the same way -- a push interacts with the same private queue, and
|
|
5820
|
+
* reports the liveness that keeps it from being reclaimed -- but they never
|
|
5821
|
+
* adopt, since the push path has no drive check to clear the claimant.
|
|
5523
5822
|
*
|
|
5524
5823
|
* Nothing is enforced or adopted without a serving gate. Below
|
|
5525
5824
|
* `authEnforcement` there is no policy being enforced for the channel to
|
|
5526
5825
|
* belong to, and refusing a poll there would break sync for no gain.
|
|
5527
5826
|
*/
|
|
5528
|
-
async #bindOrRefuseChannel(channelId, ctx) {
|
|
5827
|
+
async #bindOrRefuseChannel(channelId, ctx, options = {}) {
|
|
5529
5828
|
if (!this.syncServingGate) return;
|
|
5829
|
+
const { adopt = true, action = "poll" } = options;
|
|
5530
5830
|
let remote;
|
|
5531
5831
|
try {
|
|
5532
5832
|
remote = this.syncManager.getById(channelId);
|
|
@@ -5536,10 +5836,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5536
5836
|
const bound = remote.meta.options.boundAddress;
|
|
5537
5837
|
const address = ctx.user?.address;
|
|
5538
5838
|
if (bound === void 0) {
|
|
5539
|
-
if (address !== void 0) await this.syncManager.bindRemote(channelId, address);
|
|
5839
|
+
if (adopt && address !== void 0) await this.syncManager.bindRemote(channelId, address);
|
|
5540
5840
|
return;
|
|
5541
5841
|
}
|
|
5542
|
-
if (bound !== address) throw new ForbiddenError(
|
|
5842
|
+
if (bound !== address) throw new ForbiddenError(`to ${action} this sync channel`);
|
|
5543
5843
|
}
|
|
5544
5844
|
/** The address a channel created by this request belongs to, if any. */
|
|
5545
5845
|
#creatorBinding(ctx) {
|
|
@@ -5616,10 +5916,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5616
5916
|
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
5617
5917
|
const filteredItems = [];
|
|
5618
5918
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
5619
|
-
return
|
|
5620
|
-
...result,
|
|
5621
|
-
items: filteredItems
|
|
5622
|
-
};
|
|
5919
|
+
return this.servedPage(result, filteredItems);
|
|
5623
5920
|
}
|
|
5624
5921
|
return result;
|
|
5625
5922
|
} catch (error) {
|
|
@@ -5627,6 +5924,34 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5627
5924
|
throw error;
|
|
5628
5925
|
}
|
|
5629
5926
|
},
|
|
5927
|
+
documentOutgoingRelationshipEdges: async (_parent, args, ctx) => {
|
|
5928
|
+
this.logger.debug("documentOutgoingRelationshipEdges(@args)", args);
|
|
5929
|
+
try {
|
|
5930
|
+
const handle = await this.assertCanRead(args.sourceIdentifier, ctx);
|
|
5931
|
+
const result = await documentOutgoingRelationshipEdges(this.reactorClient, {
|
|
5932
|
+
...args,
|
|
5933
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
5934
|
+
}, this.viewSubject(ctx));
|
|
5935
|
+
return await this.filterRelationshipEdges(result, "targetId", ctx);
|
|
5936
|
+
} catch (error) {
|
|
5937
|
+
this.logger.error("Error in documentOutgoingRelationshipEdges: @Error", error);
|
|
5938
|
+
throw error;
|
|
5939
|
+
}
|
|
5940
|
+
},
|
|
5941
|
+
documentIncomingRelationshipEdges: async (_parent, args, ctx) => {
|
|
5942
|
+
this.logger.debug("documentIncomingRelationshipEdges(@args)", args);
|
|
5943
|
+
try {
|
|
5944
|
+
const handle = await this.assertCanRead(args.targetIdentifier, ctx);
|
|
5945
|
+
const result = await documentIncomingRelationshipEdges(this.reactorClient, {
|
|
5946
|
+
...args,
|
|
5947
|
+
targetIdentifier: handle.fetchIdentifier
|
|
5948
|
+
}, this.viewSubject(ctx));
|
|
5949
|
+
return await this.filterRelationshipEdges(result, "sourceId", ctx);
|
|
5950
|
+
} catch (error) {
|
|
5951
|
+
this.logger.error("Error in documentIncomingRelationshipEdges: @Error", error);
|
|
5952
|
+
throw error;
|
|
5953
|
+
}
|
|
5954
|
+
},
|
|
5630
5955
|
findDocuments: async (_parent, args, ctx) => {
|
|
5631
5956
|
this.logger.debug("findDocuments(@args)", args);
|
|
5632
5957
|
try {
|
|
@@ -5637,10 +5962,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5637
5962
|
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
5638
5963
|
const filteredItems = [];
|
|
5639
5964
|
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
5640
|
-
return
|
|
5641
|
-
...result,
|
|
5642
|
-
items: filteredItems
|
|
5643
|
-
};
|
|
5965
|
+
return this.servedPage(result, filteredItems);
|
|
5644
5966
|
}
|
|
5645
5967
|
return result;
|
|
5646
5968
|
} catch (error) {
|
|
@@ -5842,6 +6164,19 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5842
6164
|
throw error;
|
|
5843
6165
|
}
|
|
5844
6166
|
},
|
|
6167
|
+
updateRelationship: async (_parent, args, ctx) => {
|
|
6168
|
+
this.logger.debug("updateRelationship(@args)", args);
|
|
6169
|
+
try {
|
|
6170
|
+
const handle = await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
6171
|
+
return await updateRelationship(this.reactorClient, {
|
|
6172
|
+
...args,
|
|
6173
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
6174
|
+
});
|
|
6175
|
+
} catch (error) {
|
|
6176
|
+
this.logger.error("Error in updateRelationship(@args): @Error", args, error);
|
|
6177
|
+
throw error;
|
|
6178
|
+
}
|
|
6179
|
+
},
|
|
5845
6180
|
removeRelationship: async (_parent, args, ctx) => {
|
|
5846
6181
|
this.logger.debug("removeRelationship(@args)", args);
|
|
5847
6182
|
try {
|
|
@@ -5920,6 +6255,16 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5920
6255
|
pushSyncEnvelopes: async (_parent, args, ctx) => {
|
|
5921
6256
|
this.logger.debug("pushSyncEnvelopes(@args)", args);
|
|
5922
6257
|
try {
|
|
6258
|
+
const boundChannelIds = /* @__PURE__ */ new Set();
|
|
6259
|
+
for (const envelope of args.envelopes) {
|
|
6260
|
+
const channelId = envelope.channelMeta.id;
|
|
6261
|
+
if (boundChannelIds.has(channelId)) continue;
|
|
6262
|
+
boundChannelIds.add(channelId);
|
|
6263
|
+
await this.#bindOrRefuseChannel(channelId, ctx, {
|
|
6264
|
+
adopt: false,
|
|
6265
|
+
action: "push to"
|
|
6266
|
+
});
|
|
6267
|
+
}
|
|
5923
6268
|
const checkedOperations = /* @__PURE__ */ new Map();
|
|
5924
6269
|
for (const envelope of args.envelopes) for (const op of envelope.operations ?? []) {
|
|
5925
6270
|
const documentId = op.context.documentId;
|
|
@@ -6023,10 +6368,10 @@ const ADMIN_USERS = getAdminUsers();
|
|
|
6023
6368
|
//#endregion
|
|
6024
6369
|
//#region src/graphql/system/version.ts
|
|
6025
6370
|
function getVersion() {
|
|
6026
|
-
return "6.2.3-dev.
|
|
6371
|
+
return "6.2.3-dev.11";
|
|
6027
6372
|
}
|
|
6028
6373
|
function getGitHash() {
|
|
6029
|
-
return "
|
|
6374
|
+
return "1e70eb49a7dcd4c0d2c3bbfd94867a56d8887b34";
|
|
6030
6375
|
}
|
|
6031
6376
|
function getGitUrl() {
|
|
6032
6377
|
return buildTreeUrl(getGitHash());
|
|
@@ -6984,13 +7329,11 @@ var AuthService = class {
|
|
|
6984
7329
|
return new Response(JSON.stringify({ error: "Authentication failed" }), { status: 401 });
|
|
6985
7330
|
}
|
|
6986
7331
|
}
|
|
7332
|
+
/** Throws only for a bearer that is present and unusable, as HTTP 401s it. */
|
|
6987
7333
|
async authenticateWebSocketConnection(connectionParams) {
|
|
6988
7334
|
if (!this.resolvesIdentity) return null;
|
|
6989
7335
|
const authHeader = connectionParams.authorization;
|
|
6990
|
-
if (!authHeader)
|
|
6991
|
-
if (!this.config.enabled) return null;
|
|
6992
|
-
throw new Error("Missing authorization in connection parameters");
|
|
6993
|
-
}
|
|
7336
|
+
if (!authHeader) return null;
|
|
6994
7337
|
const token = authHeader.split(" ")[1];
|
|
6995
7338
|
if (!token) throw new Error("Invalid authorization format");
|
|
6996
7339
|
const verified = await this.verifyToken(token);
|
|
@@ -7676,10 +8019,23 @@ function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
|
|
|
7676
8019
|
return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
|
|
7677
8020
|
}
|
|
7678
8021
|
/**
|
|
8022
|
+
* Resolves the gateway adapter type from the `GATEWAY_ADAPTER` env var.
|
|
8023
|
+
* Defaults to "apollo" (the federation gateway, production behavior).
|
|
8024
|
+
* "stitching" selects the in-process graphql-tools merge gateway (#1565
|
|
8025
|
+
* prototype); "mercurius" the Fastify federation gateway.
|
|
8026
|
+
*/
|
|
8027
|
+
function resolveGatewayAdapterType(logger) {
|
|
8028
|
+
const configured = process.env.GATEWAY_ADAPTER;
|
|
8029
|
+
if (configured === void 0) return "apollo";
|
|
8030
|
+
if (configured === "apollo" || configured === "mercurius" || configured === "stitching") return configured;
|
|
8031
|
+
logger.warn(`Unknown GATEWAY_ADAPTER="${configured}"; falling back to "apollo"`);
|
|
8032
|
+
return "apollo";
|
|
8033
|
+
}
|
|
8034
|
+
/**
|
|
7679
8035
|
* Sets up the subgraph manager and registers subgraphs
|
|
7680
8036
|
*/
|
|
7681
8037
|
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(
|
|
8038
|
+
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
8039
|
await graphqlManager.init(subgraphs.core, authFetchMiddleware, requireAuthFetchMiddleware);
|
|
7684
8040
|
for (const [packageName, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql", false, packageName);
|
|
7685
8041
|
await graphqlManager.updateRouter();
|
|
@@ -7872,7 +8228,7 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7872
8228
|
if (requireAuthenticatedCaller) {
|
|
7873
8229
|
requireAuthFetchMiddleware = createRequireAuthFetchMiddleware();
|
|
7874
8230
|
logger.info("Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph");
|
|
7875
|
-
}
|
|
8231
|
+
} 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
8232
|
}
|
|
7877
8233
|
const dbClosers = [];
|
|
7878
8234
|
const { relationalDb, analyticsStore, closers: analyticsClosers } = await initializeDatabaseAndAnalytics(options.dbPath, options.pgliteFactory);
|
|
@@ -8222,7 +8578,7 @@ var PackageManagementService = class {
|
|
|
8222
8578
|
}
|
|
8223
8579
|
};
|
|
8224
8580
|
//#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 };
|
|
8581
|
+
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, DocumentRelationshipFieldsFragmentDoc, EvaluateActionsDocument, FindDocumentsDocument, GetDocumentDocument, GetDocumentIncomingRelationshipEdgesDocument, GetDocumentIncomingRelationshipsDocument, GetDocumentModelsDocument, GetDocumentOperationsDocument, GetDocumentOutgoingRelationshipEdgesDocument, 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, 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
8582
|
|
|
8227
8583
|
//# sourceMappingURL=index.mjs.map
|
|
8228
|
-
//# debugId=
|
|
8584
|
+
//# debugId=2b9e4602-aa9c-53a6-ad69-f3f871c0ba28
|