@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.
- 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 +303 -18
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +523 -60
- 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/{utils-XWVecaNn.mjs → pieces-eqj2RKgd.mjs} +95 -5
- package/dist/pieces-eqj2RKgd.mjs.map +1 -0
- package/dist/src/packages/vite-loader.d.mts +6 -1
- package/dist/src/packages/vite-loader.d.mts.map +1 -1
- package/dist/src/packages/vite-loader.mjs +65 -6
- package/dist/src/packages/vite-loader.mjs.map +1 -1
- package/dist/{types-Bz2niABC.d.mts → types-BXjLvTe3.d.mts} +25 -2
- package/dist/types-BXjLvTe3.d.mts.map +1 -0
- 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/types-Bz2niABC.d.mts.map +0 -1
- 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]="
|
|
3
|
-
import {
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
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(
|
|
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
|
|
5708
|
-
const
|
|
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.
|
|
6375
|
+
return "6.2.3-dev.21";
|
|
6027
6376
|
}
|
|
6028
6377
|
function getGitHash() {
|
|
6029
|
-
return "
|
|
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
|
|
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(
|
|
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=
|
|
8691
|
+
//# debugId=cdb25466-37b1-55fd-be52-a82be4286288
|