@powerhousedao/reactor-api 6.2.3-dev.8 → 6.2.3-staging.0
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/README.md +7 -2
- package/dist/{adapter-gateway-apollo-CxJNBVyD.mjs → adapter-gateway-apollo-B54G_cXT.mjs} +15 -13
- package/dist/{adapter-gateway-apollo-CxJNBVyD.mjs.map → adapter-gateway-apollo-B54G_cXT.mjs.map} +1 -1
- package/dist/{adapter-gateway-apollo-DowrX6E_.mjs → adapter-gateway-apollo-D2DNlPY0.mjs} +3 -3
- package/dist/{adapter-gateway-mercurius-CZVy88Za.mjs → adapter-gateway-mercurius-rbKlwF9m.mjs} +4 -4
- package/dist/{adapter-gateway-mercurius-CZVy88Za.mjs.map → adapter-gateway-mercurius-rbKlwF9m.mjs.map} +1 -1
- package/dist/{adapter-gateway-stitching-CaZjvRzx.mjs → adapter-gateway-stitching-Dzv10u8D.mjs} +15 -10
- package/dist/{adapter-gateway-stitching-CaZjvRzx.mjs.map → adapter-gateway-stitching-Dzv10u8D.mjs.map} +1 -1
- package/dist/{adapter-http-express-B39rPhqH.mjs → adapter-http-express-CWqziB1p.mjs} +7 -7
- package/dist/adapter-http-express-CWqziB1p.mjs.map +1 -0
- package/dist/{adapter-http-fastify-BwXO6rqE.mjs → adapter-http-fastify-CYZQ6FFv.mjs} +10 -9
- package/dist/adapter-http-fastify-CYZQ6FFv.mjs.map +1 -0
- package/dist/index.d.mts +959 -459
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1209 -312
- package/dist/index.mjs.map +1 -1
- package/dist/{utils-XWVecaNn.mjs → pieces-swupjgIO.mjs} +146 -9
- package/dist/pieces-swupjgIO.mjs.map +1 -0
- package/dist/rolldown-runtime-8H4AJuhK.mjs +16 -0
- package/dist/run-node-handler-CQjyjOuN.mjs +52 -0
- package/dist/run-node-handler-CQjyjOuN.mjs.map +1 -0
- package/dist/src/packages/https-hooks.d.mts +4 -4
- package/dist/src/packages/https-hooks.d.mts.map +1 -1
- package/dist/src/packages/https-hooks.mjs +2 -2
- package/dist/src/packages/https-hooks.mjs.map +1 -1
- package/dist/src/packages/vite-loader.d.mts +18 -8
- package/dist/src/packages/vite-loader.d.mts.map +1 -1
- package/dist/src/packages/vite-loader.mjs +114 -46
- package/dist/src/packages/vite-loader.mjs.map +1 -1
- package/dist/{types-DcYyNcHJ.d.mts → types-CuSz_zv6.d.mts} +26 -3
- package/dist/types-CuSz_zv6.d.mts.map +1 -0
- package/dist/{websocket-DncAB9XK.mjs → websocket-_SH5aBcy.mjs} +3 -3
- package/dist/{websocket-DncAB9XK.mjs.map → websocket-_SH5aBcy.mjs.map} +1 -1
- package/package.json +18 -18
- package/dist/adapter-http-express-B39rPhqH.mjs.map +0 -1
- package/dist/adapter-http-fastify-BwXO6rqE.mjs.map +0 -1
- package/dist/path-normalize-BOQwY9wR.mjs +0 -23
- package/dist/path-normalize-BOQwY9wR.mjs.map +0 -1
- package/dist/types-DcYyNcHJ.d.mts.map +0 -1
- package/dist/utils-XWVecaNn.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
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]="66ae2db7-0d74-5986-a811-364313026902")}catch(e){}}();
|
|
3
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
4
|
+
import { C as AuthorizationPolicy, D as AuthenticationRequiredError, E as AuthEvaluationUnsupportedError, O as ForbiddenError, S as createCanonicalDocumentIdResolver, T as createAuthorizationService, _ as loadProcessors, a as pieceListLocation, b as callerSubject, c as buildGraphQlDocument, d as buildGraphqlOperations, f as isSubgraphClass, g as loadDocumentModels, k as MalformedStoredOperationError, l as buildGraphQlDriveDocument, m as extractUpgradeManifests, o as piecesFromCdnList, p as debounce, r as PIECE_SOURCE_LIST, s as piecesFromListModule, u as buildGraphqlOperation, v as loadSubgraphs, w as AuthorizedDocumentHandle, x as CanonicalDocumentIdResolutionError, y as BaseSubgraph } from "./pieces-swupjgIO.mjs";
|
|
4
5
|
import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
|
|
5
6
|
import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
|
|
6
7
|
import { gql } from "graphql-tag";
|
|
7
|
-
import { GraphQLError, Kind, parse, print } from "graphql";
|
|
8
|
-
import { AuthEnforcementDisabledError, DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, ModelReadGate, PropagationMode as PropagationMode$1, SyncScopeGate, consolidateSyncOperations, createReactorHostModuleBase, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, readDecisionModel, syncOperationErrorType } from "@powerhousedao/reactor";
|
|
8
|
+
import { GraphQLError, Kind, Lexer, Source, TokenKind, parse, print, visit } from "graphql";
|
|
9
|
+
import { AuthEnforcementDisabledError, BareReadGate, ChannelError, ChannelErrorSource, DEFAULT_DRIVE_CONTAINER_TYPES, DriveCollectionId, JobStatus, ModelReadGate, PropagationMode as PropagationMode$1, SyncOperation, SyncScopeGate, consolidateSyncOperations, createReactorHostModuleBase, driveIdFromUrl, envelopesToSyncOperations, parseDriveUrl, readDecisionModel, syncOperationErrorType } from "@powerhousedao/reactor";
|
|
9
10
|
import { ConsoleLogger, childLogger, documentModelDocumentModelModule } from "document-model";
|
|
10
|
-
import path, { join } from "node:path";
|
|
11
|
+
import path, { isAbsolute, join } from "node:path";
|
|
11
12
|
import { match } from "path-to-regexp";
|
|
12
13
|
import posix from "node:path/posix";
|
|
13
14
|
import { Readable } from "node:stream";
|
|
14
|
-
import fs, { readFileSync, watchFile } from "node:fs";
|
|
15
|
+
import fs, { existsSync, readFileSync, watchFile } from "node:fs";
|
|
15
16
|
import { pipeline } from "node:stream/promises";
|
|
16
17
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
18
|
+
import { pathToFileURL } from "node:url";
|
|
17
19
|
import { buildSubgraphSchema } from "@apollo/subgraph";
|
|
18
20
|
import { typeDefs } from "@powerhousedao/document-engineering/graphql";
|
|
19
21
|
import { camelCase, kebabCase, pascalCase } from "change-case";
|
|
20
22
|
import { GraphQLJSON, GraphQLJSONObject } from "graphql-type-json";
|
|
21
|
-
import { deserializeSignature, serializeSignature, setName } from "@powerhousedao/shared/document-model";
|
|
23
|
+
import { deserializeSignature, readPeerManifest, serializeSignature, setName, withSignaturePolicy } from "@powerhousedao/shared/document-model";
|
|
22
24
|
import * as z$1 from "zod";
|
|
23
25
|
import { z } from "zod";
|
|
24
26
|
import { createHandler } from "graphql-sse/lib/use/fetch";
|
|
@@ -46,19 +48,8 @@ import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
|
|
|
46
48
|
import knex from "knex";
|
|
47
49
|
import ClientPgLite from "knex-pglite";
|
|
48
50
|
import { KyselyKnexDialect, PGColdDialect } from "kysely-knex";
|
|
49
|
-
//#region \0rolldown/runtime.js
|
|
50
|
-
var __defProp = Object.defineProperty;
|
|
51
|
-
var __exportAll = (all, no_symbols) => {
|
|
52
|
-
let target = {};
|
|
53
|
-
for (var name in all) __defProp(target, name, {
|
|
54
|
-
get: all[name],
|
|
55
|
-
enumerable: true
|
|
56
|
-
});
|
|
57
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
58
|
-
return target;
|
|
59
|
-
};
|
|
60
|
-
//#endregion
|
|
61
51
|
//#region src/graphql/analytics-subgraph.ts
|
|
52
|
+
const analyticsResolvers = AnalyticsResolvers;
|
|
62
53
|
var AnalyticsSubgraph = class extends BaseSubgraph {
|
|
63
54
|
analyticsStore;
|
|
64
55
|
analyticsModel;
|
|
@@ -66,13 +57,33 @@ var AnalyticsSubgraph = class extends BaseSubgraph {
|
|
|
66
57
|
typeDefs = gql`
|
|
67
58
|
${typedefs}
|
|
68
59
|
`;
|
|
69
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Every analytics field hangs off `Query.analytics`, so gating it gates them
|
|
62
|
+
* all. Series are aggregates a processor built from many documents and carry
|
|
63
|
+
* no document to decide on, so only a caller with policy-wide read (anyone
|
|
64
|
+
* under OPEN, an admin otherwise) may read them.
|
|
65
|
+
*/
|
|
66
|
+
resolvers = {
|
|
67
|
+
...analyticsResolvers,
|
|
68
|
+
Query: {
|
|
69
|
+
...analyticsResolvers.Query,
|
|
70
|
+
analytics: (parent, args, ctx) => {
|
|
71
|
+
this.#assertCanReadAnalytics(ctx);
|
|
72
|
+
return analyticsResolvers.Query.analytics(parent, args, ctx);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
70
76
|
constructor(args) {
|
|
71
77
|
super(args);
|
|
72
78
|
this.analyticsStore = args.analyticsStore;
|
|
73
79
|
this.analyticsModel = new AnalyticsModel(new AnalyticsQueryEngine(this.analyticsStore));
|
|
74
80
|
args.graphqlManager.setAdditionalContextFields({ dataSources: { db: { Analytics: this.analyticsModel } } });
|
|
75
81
|
}
|
|
82
|
+
#assertCanReadAnalytics(ctx) {
|
|
83
|
+
if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return;
|
|
84
|
+
if (ctx.user?.address) throw new ForbiddenError("to read analytics");
|
|
85
|
+
throw new AuthenticationRequiredError("to read analytics");
|
|
86
|
+
}
|
|
76
87
|
};
|
|
77
88
|
//#endregion
|
|
78
89
|
//#region src/graphql/auth/schema.graphql
|
|
@@ -311,15 +322,15 @@ function createAuthFetchMiddleware(authService) {
|
|
|
311
322
|
async function createGatewayAdapter(type, logger) {
|
|
312
323
|
switch (type) {
|
|
313
324
|
case "apollo": {
|
|
314
|
-
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-
|
|
325
|
+
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-D2DNlPY0.mjs");
|
|
315
326
|
return new ApolloGatewayAdapter(logger);
|
|
316
327
|
}
|
|
317
328
|
case "mercurius": {
|
|
318
|
-
const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-
|
|
329
|
+
const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-rbKlwF9m.mjs");
|
|
319
330
|
return new MercuriusGatewayAdapter(logger);
|
|
320
331
|
}
|
|
321
332
|
case "stitching": {
|
|
322
|
-
const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-
|
|
333
|
+
const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-Dzv10u8D.mjs");
|
|
323
334
|
return new StitchingGatewayAdapter(logger);
|
|
324
335
|
}
|
|
325
336
|
}
|
|
@@ -327,11 +338,11 @@ async function createGatewayAdapter(type, logger) {
|
|
|
327
338
|
async function createHttpAdapter(type) {
|
|
328
339
|
switch (type) {
|
|
329
340
|
case "express": {
|
|
330
|
-
const { createExpressHttpAdapter } = await import("./adapter-http-express-
|
|
341
|
+
const { createExpressHttpAdapter } = await import("./adapter-http-express-CWqziB1p.mjs");
|
|
331
342
|
return createExpressHttpAdapter();
|
|
332
343
|
}
|
|
333
344
|
case "fastify": {
|
|
334
|
-
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-
|
|
345
|
+
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-CYZQ6FFv.mjs");
|
|
335
346
|
return createFastifyHttpAdapter();
|
|
336
347
|
}
|
|
337
348
|
}
|
|
@@ -359,7 +370,7 @@ const WS_AUTH_CLOSE_REASONS = [WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE
|
|
|
359
370
|
*/
|
|
360
371
|
const SEGMENT = /^[a-z0-9][-a-z0-9._]*$/i;
|
|
361
372
|
/** Segments that would read as path traversal rather than as a name. */
|
|
362
|
-
const TRAVERSAL = new Set([".", ".."]);
|
|
373
|
+
const TRAVERSAL = /* @__PURE__ */ new Set([".", ".."]);
|
|
363
374
|
var InvalidNamespaceError = class extends Error {};
|
|
364
375
|
/**
|
|
365
376
|
* Turns a package name into the path segments it occupies.
|
|
@@ -845,27 +856,29 @@ var HttpScope = class {
|
|
|
845
856
|
headers: fetchHeaders(req),
|
|
846
857
|
signal: controller.signal
|
|
847
858
|
};
|
|
848
|
-
if (method !== "GET" && method !== "HEAD" && body !== "none")
|
|
849
|
-
|
|
850
|
-
body: Readable.toWeb(req),
|
|
851
|
-
duplex: "half"
|
|
852
|
-
};
|
|
853
|
-
else {
|
|
854
|
-
try {
|
|
855
|
-
raw = await readBody(req, spec.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES$1);
|
|
856
|
-
} catch (error) {
|
|
857
|
-
if (error instanceof BodyTooLargeError) {
|
|
858
|
-
res.setHeader("connection", "close");
|
|
859
|
-
res.once("finish", () => req.destroy());
|
|
860
|
-
writeError(res, 413, "Payload too large");
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
|
-
throw error;
|
|
864
|
-
}
|
|
865
|
-
if (raw.length) init = {
|
|
859
|
+
if (method !== "GET" && method !== "HEAD" && body !== "none") {
|
|
860
|
+
if (body === "stream") init = {
|
|
866
861
|
...init,
|
|
867
|
-
body:
|
|
862
|
+
body: Readable.toWeb(req),
|
|
863
|
+
duplex: "half"
|
|
868
864
|
};
|
|
865
|
+
else {
|
|
866
|
+
try {
|
|
867
|
+
raw = await readBody(req, spec.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES$1);
|
|
868
|
+
} catch (error) {
|
|
869
|
+
if (error instanceof BodyTooLargeError) {
|
|
870
|
+
res.setHeader("connection", "close");
|
|
871
|
+
res.once("finish", () => req.destroy());
|
|
872
|
+
writeError(res, 413, "Payload too large");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
throw error;
|
|
876
|
+
}
|
|
877
|
+
if (raw.length) init = {
|
|
878
|
+
...init,
|
|
879
|
+
body: new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength)
|
|
880
|
+
};
|
|
881
|
+
}
|
|
869
882
|
}
|
|
870
883
|
await writeResponse(res, await spec.handler(new Request(url, init), {
|
|
871
884
|
...ctx,
|
|
@@ -1042,7 +1055,7 @@ function verifyWebhook(options) {
|
|
|
1042
1055
|
}
|
|
1043
1056
|
/** Headers that would put a credential in a log or run journal. The configured signature
|
|
1044
1057
|
* header joins them: it is an HMAC of the body under a live secret. */
|
|
1045
|
-
const REDACTED_HEADERS = new Set([
|
|
1058
|
+
const REDACTED_HEADERS = /* @__PURE__ */ new Set([
|
|
1046
1059
|
"authorization",
|
|
1047
1060
|
"proxy-authorization",
|
|
1048
1061
|
"cookie",
|
|
@@ -1315,6 +1328,7 @@ function newWebhookToken() {
|
|
|
1315
1328
|
const NAMESPACE = "reactor_webhooks";
|
|
1316
1329
|
/** Relational implementation, over the reactor's own database. */
|
|
1317
1330
|
var RelationalWebhookStore = class {
|
|
1331
|
+
relationalDb;
|
|
1318
1332
|
#db;
|
|
1319
1333
|
constructor(relationalDb) {
|
|
1320
1334
|
this.relationalDb = relationalDb;
|
|
@@ -1329,7 +1343,11 @@ var RelationalWebhookStore = class {
|
|
|
1329
1343
|
"owner_key"
|
|
1330
1344
|
]).unique().execute();
|
|
1331
1345
|
await db.schema.createTable("webhook_deliveries").ifNotExists().addColumn("token", "text", (col) => col.notNull()).addColumn("dedupe_key", "text", (col) => col.notNull()).addColumn("seen_at", "text", (col) => col.notNull()).addColumn("claim_id", "text", (col) => col.notNull()).execute();
|
|
1346
|
+
try {
|
|
1347
|
+
await db.schema.alterTable("webhook_deliveries").addColumn("expires_at", "text").execute();
|
|
1348
|
+
} catch {}
|
|
1332
1349
|
await db.schema.createIndex("webhook_deliveries_key").ifNotExists().on("webhook_deliveries").columns(["token", "dedupe_key"]).unique().execute();
|
|
1350
|
+
await db.schema.createIndex("webhook_deliveries_expires_at").ifNotExists().on("webhook_deliveries").columns(["expires_at"]).execute();
|
|
1333
1351
|
await db.schema.createIndex("webhook_deliveries_seen_at").ifNotExists().on("webhook_deliveries").columns(["seen_at"]).execute();
|
|
1334
1352
|
this.#db = db;
|
|
1335
1353
|
}
|
|
@@ -1370,13 +1388,16 @@ var RelationalWebhookStore = class {
|
|
|
1370
1388
|
}
|
|
1371
1389
|
async seen(token, key, ttlSeconds) {
|
|
1372
1390
|
const now = Date.now();
|
|
1391
|
+
const nowIso = new Date(now).toISOString();
|
|
1373
1392
|
const cutoff = (/* @__PURE__ */ new Date(now - ttlSeconds * 1e3)).toISOString();
|
|
1374
|
-
await this.#handle.deleteFrom("webhook_deliveries").where("seen_at", "<", cutoff).execute();
|
|
1393
|
+
await this.#handle.deleteFrom("webhook_deliveries").where((eb) => eb.or([eb("expires_at", "<", nowIso), eb.and([eb("expires_at", "is", null), eb("seen_at", "<", cutoff)])])).execute();
|
|
1394
|
+
await this.#handle.deleteFrom("webhook_deliveries").where("token", "=", token).where("dedupe_key", "=", key).where("seen_at", "<", cutoff).execute();
|
|
1375
1395
|
const claimId = randomBytes(16).toString("hex");
|
|
1376
1396
|
await this.#handle.insertInto("webhook_deliveries").values({
|
|
1377
1397
|
token,
|
|
1378
1398
|
dedupe_key: key,
|
|
1379
|
-
seen_at:
|
|
1399
|
+
seen_at: nowIso,
|
|
1400
|
+
expires_at: new Date(now + ttlSeconds * 1e3).toISOString(),
|
|
1380
1401
|
claim_id: claimId
|
|
1381
1402
|
}).onConflict((oc) => oc.columns(["token", "dedupe_key"]).doNothing()).execute();
|
|
1382
1403
|
return (await this.#handle.selectFrom("webhook_deliveries").select("claim_id").where("token", "=", token).where("dedupe_key", "=", key).executeTakeFirst())?.claim_id !== claimId;
|
|
@@ -1422,10 +1443,14 @@ var MemoryWebhookStore = class {
|
|
|
1422
1443
|
}
|
|
1423
1444
|
seen(token, key, ttlSeconds) {
|
|
1424
1445
|
const now = Date.now();
|
|
1425
|
-
for (const [k,
|
|
1446
|
+
for (const [k, entry] of this.#deliveries) if (entry.expiresAt < now) this.#deliveries.delete(k);
|
|
1426
1447
|
const composite = `${token}|${key}`;
|
|
1427
|
-
|
|
1428
|
-
|
|
1448
|
+
const existing = this.#deliveries.get(composite);
|
|
1449
|
+
if (existing && existing.seenAt >= now - ttlSeconds * 1e3) return Promise.resolve(true);
|
|
1450
|
+
this.#deliveries.set(composite, {
|
|
1451
|
+
seenAt: now,
|
|
1452
|
+
expiresAt: now + ttlSeconds * 1e3
|
|
1453
|
+
});
|
|
1429
1454
|
return Promise.resolve(false);
|
|
1430
1455
|
}
|
|
1431
1456
|
};
|
|
@@ -1460,7 +1485,7 @@ const stripScalarDefinitions = (doc) => {
|
|
|
1460
1485
|
* whole gateway. We dedupe these by name; field/operation/extension nodes are
|
|
1461
1486
|
* left untouched.
|
|
1462
1487
|
*/
|
|
1463
|
-
const TYPE_DEFINITION_KINDS = new Set([
|
|
1488
|
+
const TYPE_DEFINITION_KINDS = /* @__PURE__ */ new Set([
|
|
1464
1489
|
Kind.OBJECT_TYPE_DEFINITION,
|
|
1465
1490
|
Kind.ENUM_TYPE_DEFINITION,
|
|
1466
1491
|
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
@@ -1496,6 +1521,98 @@ const dedupeTypeDefinitions = (doc) => {
|
|
|
1496
1521
|
definitions
|
|
1497
1522
|
};
|
|
1498
1523
|
};
|
|
1524
|
+
const EMPTY_DOCUMENT = {
|
|
1525
|
+
kind: Kind.DOCUMENT,
|
|
1526
|
+
definitions: []
|
|
1527
|
+
};
|
|
1528
|
+
const OUTPUT_TYPE_KINDS = [
|
|
1529
|
+
Kind.OBJECT_TYPE_DEFINITION,
|
|
1530
|
+
Kind.OBJECT_TYPE_EXTENSION,
|
|
1531
|
+
Kind.ENUM_TYPE_DEFINITION,
|
|
1532
|
+
Kind.ENUM_TYPE_EXTENSION,
|
|
1533
|
+
Kind.UNION_TYPE_DEFINITION,
|
|
1534
|
+
Kind.UNION_TYPE_EXTENSION,
|
|
1535
|
+
Kind.INTERFACE_TYPE_DEFINITION,
|
|
1536
|
+
Kind.INTERFACE_TYPE_EXTENSION
|
|
1537
|
+
];
|
|
1538
|
+
const STATE_PREFIX_KINDS = new Set(OUTPUT_TYPE_KINDS);
|
|
1539
|
+
const ALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
|
|
1540
|
+
...OUTPUT_TYPE_KINDS,
|
|
1541
|
+
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
1542
|
+
Kind.INPUT_OBJECT_TYPE_EXTENSION
|
|
1543
|
+
]);
|
|
1544
|
+
const TYPE_NAME_KINDS = /* @__PURE__ */ new Set([
|
|
1545
|
+
...ALL_PREFIX_KINDS,
|
|
1546
|
+
Kind.SCALAR_TYPE_DEFINITION,
|
|
1547
|
+
Kind.SCALAR_TYPE_EXTENSION,
|
|
1548
|
+
Kind.NAMED_TYPE
|
|
1549
|
+
]);
|
|
1550
|
+
const LOCAL_STATE_STUBS = /* @__PURE__ */ new Set([
|
|
1551
|
+
"AccountSnapshotLocalState",
|
|
1552
|
+
"BudgetStatementLocalState",
|
|
1553
|
+
"ScopeFrameworkLocalState"
|
|
1554
|
+
]);
|
|
1555
|
+
/** Parse SDL; blank or comment-only input yields an empty document. */
|
|
1556
|
+
function parseSdl(sdl) {
|
|
1557
|
+
const source = new Source(sdl);
|
|
1558
|
+
if (new Lexer(source).advance().kind === TokenKind.EOF) return EMPTY_DOCUMENT;
|
|
1559
|
+
try {
|
|
1560
|
+
return parse(source, { noLocation: true });
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
const withoutSemicolons = sdl.replaceAll(";", "");
|
|
1563
|
+
if (withoutSemicolons === sdl) throw error;
|
|
1564
|
+
return parse(withoutSemicolons, { noLocation: true });
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
function definedTypeNames(doc, kinds) {
|
|
1568
|
+
return doc.definitions.filter((def) => kinds.has(def.kind)).map((def) => def.name.value);
|
|
1569
|
+
}
|
|
1570
|
+
/** Prefix type definition names and type references found in `names`. */
|
|
1571
|
+
function prefixTypeNames(doc, prefix, names) {
|
|
1572
|
+
return visit(doc, { enter(node) {
|
|
1573
|
+
if (!TYPE_NAME_KINDS.has(node.kind)) return void 0;
|
|
1574
|
+
const { name } = node;
|
|
1575
|
+
if (!names.has(name.value)) return void 0;
|
|
1576
|
+
return {
|
|
1577
|
+
...node,
|
|
1578
|
+
name: {
|
|
1579
|
+
...name,
|
|
1580
|
+
value: `${prefix}_${name.value}`
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
} });
|
|
1584
|
+
}
|
|
1585
|
+
/** State schema definitions without inputs, `DateTime` or the given object types. */
|
|
1586
|
+
function stateTypeDefinitions(sdl, droppedObjectTypes = /* @__PURE__ */ new Set()) {
|
|
1587
|
+
return parseSdl(sdl).definitions.filter((def) => def.kind !== Kind.INPUT_OBJECT_TYPE_DEFINITION && def.kind !== Kind.INPUT_OBJECT_TYPE_EXTENSION && !(def.kind === Kind.SCALAR_TYPE_DEFINITION && def.name.value === "DateTime") && !(def.kind === Kind.OBJECT_TYPE_DEFINITION && droppedObjectTypes.has(def.name.value)));
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Prefixed operation schemas per module. The first definition of each module
|
|
1591
|
+
* carries a "Module: <Name>" description unless it has its own.
|
|
1592
|
+
*/
|
|
1593
|
+
function buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema) {
|
|
1594
|
+
const names = new Set(allTypeNames);
|
|
1595
|
+
return specification?.modules.map((module) => {
|
|
1596
|
+
const definitions = module.operations.filter((op) => hasValidSchema(op.schema)).flatMap((op) => {
|
|
1597
|
+
const doc = parseSdl(op.schema ?? "");
|
|
1598
|
+
return prefixTypeNames(doc, documentName, /* @__PURE__ */ new Set([...names, ...definedTypeNames(doc, ALL_PREFIX_KINDS)])).definitions;
|
|
1599
|
+
});
|
|
1600
|
+
if (definitions.length === 0) return "";
|
|
1601
|
+
const [first, ...rest] = definitions;
|
|
1602
|
+
const header = "description" in first && !first.description ? {
|
|
1603
|
+
...first,
|
|
1604
|
+
description: {
|
|
1605
|
+
kind: Kind.STRING,
|
|
1606
|
+
value: `Module: ${pascalCase(module.name)}`,
|
|
1607
|
+
block: true
|
|
1608
|
+
}
|
|
1609
|
+
} : first;
|
|
1610
|
+
return print({
|
|
1611
|
+
kind: Kind.DOCUMENT,
|
|
1612
|
+
definitions: [header, ...rest]
|
|
1613
|
+
});
|
|
1614
|
+
}).filter(Boolean).join("\n") ?? "";
|
|
1615
|
+
}
|
|
1499
1616
|
const buildSubgraphSchemaModule = (documentModels, resolvers, typeDefs) => {
|
|
1500
1617
|
const newResolvers = {
|
|
1501
1618
|
...resolvers,
|
|
@@ -1534,20 +1651,13 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1534
1651
|
const latestSpec = documentModel.global.specifications.at(-1);
|
|
1535
1652
|
const globalSchema = latestSpec?.state.global.schema ?? "";
|
|
1536
1653
|
const localSchema = latestSpec?.state.local.schema ?? "";
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
const typeRegex = new RegExp(`(?<![_A-Za-z0-9])(${f})(?![_A-Za-z0-9])|\\[(${f})\\]|\\[(${f})!\\]|\\[(${f})\\]!|\\[(${f})!\\]!`, "g");
|
|
1545
|
-
tmpDmSchema = tmpDmSchema.replace(typeRegex, (match, p1, p2, p3, p4, p5) => {
|
|
1546
|
-
if (match.startsWith("[")) return match.replace(p2 || p3 || p4 || p5, `${dmSchemaName}_${p2 || p3 || p4 || p5}`);
|
|
1547
|
-
return `${dmSchemaName}_${p1}`;
|
|
1548
|
-
});
|
|
1549
|
-
});
|
|
1550
|
-
dmSchema += tmpDmSchema;
|
|
1654
|
+
const definitions = [...stateTypeDefinitions(globalSchema), ...stateTypeDefinitions(localSchema, LOCAL_STATE_STUBS)];
|
|
1655
|
+
const stateDoc = {
|
|
1656
|
+
kind: Kind.DOCUMENT,
|
|
1657
|
+
definitions
|
|
1658
|
+
};
|
|
1659
|
+
const prefixed = prefixTypeNames(stateDoc, dmSchemaName, new Set(definedTypeNames(stateDoc, STATE_PREFIX_KINDS)));
|
|
1660
|
+
if (definitions.length > 0) dmSchema += `${print(prefixed)}\n`;
|
|
1551
1661
|
dmSchema += `
|
|
1552
1662
|
type ${dmSchemaName} implements IDocument {
|
|
1553
1663
|
id: String!
|
|
@@ -1562,7 +1672,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1562
1672
|
stateJSON: JSONObject
|
|
1563
1673
|
}\n`;
|
|
1564
1674
|
});
|
|
1565
|
-
|
|
1675
|
+
const schema = gql`
|
|
1566
1676
|
scalar JSONObject
|
|
1567
1677
|
scalar AttachmentRef
|
|
1568
1678
|
# Codegen scalars not in the document-engineering set.
|
|
@@ -1613,7 +1723,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1613
1723
|
operations(first: Int, skip: Int): [Operation!]!
|
|
1614
1724
|
stateJSON: JSONObject
|
|
1615
1725
|
}
|
|
1616
|
-
${dmSchema
|
|
1726
|
+
${dmSchema}
|
|
1617
1727
|
|
|
1618
1728
|
type GqlDocument implements IDocument {
|
|
1619
1729
|
id: String!
|
|
@@ -1638,34 +1748,32 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1638
1748
|
}
|
|
1639
1749
|
|
|
1640
1750
|
${stripScalarDefinitions(typeDefs$1)}
|
|
1641
|
-
|
|
1751
|
+
`;
|
|
1752
|
+
return dedupeTypeDefinitions(schema);
|
|
1642
1753
|
};
|
|
1643
1754
|
/**
|
|
1644
|
-
* Extract
|
|
1645
|
-
*
|
|
1646
|
-
* @returns {string[]} Array of type names
|
|
1755
|
+
* Extract the names of types defined in a GraphQL schema.
|
|
1756
|
+
* Unparseable schemas yield no names.
|
|
1647
1757
|
*/
|
|
1648
1758
|
function extractTypeNames(schema) {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1759
|
+
try {
|
|
1760
|
+
return definedTypeNames(parseSdl(schema), ALL_PREFIX_KINDS);
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
logger.debug(`Skipping unparseable schema: ${String(error)}`);
|
|
1763
|
+
return [];
|
|
1764
|
+
}
|
|
1652
1765
|
}
|
|
1653
1766
|
/**
|
|
1654
|
-
*
|
|
1655
|
-
* @param
|
|
1656
|
-
* @param {Set<string>} excludeTypeNames - Type names to exclude from extraction
|
|
1657
|
-
* @returns {string} All input type definitions as a string
|
|
1767
|
+
* Print the input type definitions of a GraphQL schema.
|
|
1768
|
+
* @param excludeTypeNames - Input type names to leave out
|
|
1658
1769
|
*/
|
|
1659
1770
|
function extractInputTypeDefinitions(schema, excludeTypeNames = /* @__PURE__ */ new Set()) {
|
|
1660
|
-
const
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
}
|
|
1667
|
-
if (matches.length === 0) return "";
|
|
1668
|
-
return matches.join("\n\n");
|
|
1771
|
+
const definitions = parseSdl(schema).definitions.filter((def) => def.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION && !excludeTypeNames.has(def.name.value));
|
|
1772
|
+
if (definitions.length === 0) return "";
|
|
1773
|
+
return print({
|
|
1774
|
+
kind: Kind.DOCUMENT,
|
|
1775
|
+
definitions
|
|
1776
|
+
});
|
|
1669
1777
|
}
|
|
1670
1778
|
/**
|
|
1671
1779
|
* Extract the root state type name from a scope's GraphQL schema.
|
|
@@ -1734,9 +1842,7 @@ function generateStateInputTypes(stateSchema, excludeTypeNames) {
|
|
|
1734
1842
|
case Kind.OBJECT_TYPE_DEFINITION:
|
|
1735
1843
|
objectNames.add(def.name.value);
|
|
1736
1844
|
break;
|
|
1737
|
-
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
|
|
1738
|
-
existingInputNames.add(def.name.value);
|
|
1739
|
-
break;
|
|
1845
|
+
case Kind.INPUT_OBJECT_TYPE_DEFINITION: existingInputNames.add(def.name.value);
|
|
1740
1846
|
}
|
|
1741
1847
|
const inputTypes = [];
|
|
1742
1848
|
for (const def of ast.definitions) {
|
|
@@ -1763,18 +1869,10 @@ function generateStateInputTypes(stateSchema, excludeTypeNames) {
|
|
|
1763
1869
|
*/
|
|
1764
1870
|
function applyGraphQLTypePrefixes(schema, prefix, externalTypeNames = []) {
|
|
1765
1871
|
if (!schema || !schema.trim()) return schema;
|
|
1766
|
-
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
allTypeNames.forEach((typeName) => {
|
|
1771
|
-
const typeRegex = new RegExp(`(?<![_A-Za-z0-9])(${typeName})(?![_A-Za-z0-9])|\\[(${typeName})\\]|\\[(${typeName})!\\]|\\[(${typeName})\\]!|\\[(${typeName})!\\]!`, "g");
|
|
1772
|
-
processedSchema = processedSchema.replace(typeRegex, (match, p1, p2, p3, p4, p5) => {
|
|
1773
|
-
if (match.startsWith("[")) return match.replace(p2 || p3 || p4 || p5, `${prefix}_${p2 || p3 || p4 || p5}`);
|
|
1774
|
-
return `${prefix}_${p1}`;
|
|
1775
|
-
});
|
|
1776
|
-
});
|
|
1777
|
-
return processedSchema;
|
|
1872
|
+
const doc = parseSdl(schema);
|
|
1873
|
+
const names = /* @__PURE__ */ new Set([...definedTypeNames(doc, ALL_PREFIX_KINDS), ...externalTypeNames]);
|
|
1874
|
+
if (names.size === 0) return schema;
|
|
1875
|
+
return print(prefixTypeNames(doc, prefix, names));
|
|
1778
1876
|
}
|
|
1779
1877
|
/**
|
|
1780
1878
|
* Generate a GraphQL schema for a document model.
|
|
@@ -1793,11 +1891,11 @@ function generateDocumentModelSchema(documentModel, options = {}) {
|
|
|
1793
1891
|
const localStateTypeNames = extractTypeNames(localStateSchema ?? "");
|
|
1794
1892
|
const stateTypeNames = [...globalStateTypeNames, ...localStateTypeNames];
|
|
1795
1893
|
const allOperationTypeNames = specification?.modules.flatMap((module) => module.operations.flatMap((op) => extractTypeNames(op.schema ?? ""))) ?? [];
|
|
1796
|
-
const allTypeNames = [
|
|
1797
|
-
const
|
|
1894
|
+
const allTypeNames = [.../* @__PURE__ */ new Set([...stateTypeNames, ...allOperationTypeNames])];
|
|
1895
|
+
const operationInputTypeNames = new Set(allOperationTypeNames);
|
|
1896
|
+
const prefixedStateInputTypes = applyGraphQLTypePrefixes(extractInputTypeDefinitions(globalStateSchema ?? "", operationInputTypeNames), documentName, allTypeNames);
|
|
1798
1897
|
const hasValidSchema = (schema) => !!(schema && /\b(input|type|enum|union|interface)\s+\w+/.test(schema));
|
|
1799
|
-
|
|
1800
|
-
if (useNewApi) return generateNewApiSchema(documentName, specification, stateSchemaTypes, prefixedStateInputTypes, allTypeNames, hasValidSchema);
|
|
1898
|
+
if (useNewApi) return generateNewApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema);
|
|
1801
1899
|
return generateLegacyApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema);
|
|
1802
1900
|
}
|
|
1803
1901
|
/**
|
|
@@ -1807,10 +1905,7 @@ function generateLegacyApiSchema(documentName, specification, prefixedStateInput
|
|
|
1807
1905
|
const createDocumentMutation = `${documentName}_createDocument(name:String!, driveId:String): String`;
|
|
1808
1906
|
const operationMutations = specification?.modules.flatMap((module) => module.operations.filter((op) => op.name && hasValidSchema(op.schema)).map((op) => `${documentName}_${camelCase(op.name)}(
|
|
1809
1907
|
driveId: String, docId: PHID, input: ${documentName}_${pascalCase(op.name)}Input): Int`)).join("\n ") ?? "";
|
|
1810
|
-
const moduleSchemas = specification
|
|
1811
|
-
Module: ${pascalCase(module.name)}
|
|
1812
|
-
"""
|
|
1813
|
-
${module.operations.filter((op) => hasValidSchema(op.schema)).map((op) => applyGraphQLTypePrefixes(op.schema ?? "", documentName, allTypeNames)).join("\n ")}`).join("\n") ?? "";
|
|
1908
|
+
const moduleSchemas = buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema);
|
|
1814
1909
|
return gql`
|
|
1815
1910
|
"""
|
|
1816
1911
|
Queries: ${documentName} Document
|
|
@@ -1847,7 +1942,7 @@ function generateLegacyApiSchema(documentName, specification, prefixedStateInput
|
|
|
1847
1942
|
* in getDocumentModelTypeDefs() which is used during schema composition.
|
|
1848
1943
|
* Including them here would cause duplicate type definitions.
|
|
1849
1944
|
*/
|
|
1850
|
-
function generateNewApiSchema(documentName, specification,
|
|
1945
|
+
function generateNewApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema) {
|
|
1851
1946
|
const stateType = `${documentName}_FullState!`;
|
|
1852
1947
|
const sharedBaseTypes = `
|
|
1853
1948
|
"""Hash configuration for document state"""
|
|
@@ -1924,7 +2019,6 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1924
2019
|
"""
|
|
1925
2020
|
type ${documentName}_DocumentResultPage {
|
|
1926
2021
|
items: [${documentName}MutationResult!]!
|
|
1927
|
-
totalCount: Int!
|
|
1928
2022
|
hasNextPage: Boolean!
|
|
1929
2023
|
hasPreviousPage: Boolean!
|
|
1930
2024
|
cursor: String
|
|
@@ -1979,10 +2073,7 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1979
2073
|
const createDocumentMutation = initialStateInputSchema ? `createDocument(name: String!, parentIdentifier: String, slug: String, preferredEditor: String, initialState: ${documentName}_InitialStateInput): ${documentName}MutationResult!` : `createDocument(name: String!, parentIdentifier: String, preferredEditor: String): ${documentName}MutationResult!`;
|
|
1980
2074
|
const createEmptyDocumentMutation = `createEmptyDocument(parentIdentifier: String): ${documentName}MutationResult!`;
|
|
1981
2075
|
const operationMutations = specification?.modules.flatMap((module) => module.operations.filter((op) => op.name && hasValidSchema(op.schema)).flatMap((op) => [`${camelCase(op.name)}(docId: PHID!, input: ${documentName}_${pascalCase(op.name)}Input!): ${documentName}MutationResult!`, `${camelCase(op.name)}Async(docId: PHID!, input: ${documentName}_${pascalCase(op.name)}Input!): String!`])).join("\n ") ?? "";
|
|
1982
|
-
const moduleSchemas = specification
|
|
1983
|
-
Module: ${pascalCase(module.name)}
|
|
1984
|
-
"""
|
|
1985
|
-
${module.operations.filter((op) => hasValidSchema(op.schema)).map((op) => applyGraphQLTypePrefixes(op.schema ?? "", documentName, allTypeNames)).join("\n ")}`).join("\n") ?? "";
|
|
2076
|
+
const moduleSchemas = buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema);
|
|
1986
2077
|
return gql`
|
|
1987
2078
|
scalar DateTime
|
|
1988
2079
|
scalar JSONObject
|
|
@@ -2200,11 +2291,18 @@ function SyncEnvelopeInputSchema() {
|
|
|
2200
2291
|
type: SyncEnvelopeTypeSchema
|
|
2201
2292
|
});
|
|
2202
2293
|
}
|
|
2294
|
+
function SyncRefusalInputSchema() {
|
|
2295
|
+
return z$1.object({
|
|
2296
|
+
branch: z$1.string(),
|
|
2297
|
+
documentId: z$1.string()
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2203
2300
|
function TouchChannelInputSchema() {
|
|
2204
2301
|
return z$1.object({
|
|
2205
2302
|
collectionId: z$1.string(),
|
|
2206
2303
|
filter: z$1.lazy(() => RemoteFilterInputSchema()),
|
|
2207
2304
|
id: z$1.string(),
|
|
2305
|
+
manifest: z$1.custom((v) => v != null).nullish(),
|
|
2208
2306
|
name: z$1.string(),
|
|
2209
2307
|
sinceTimestampUtcMs: z$1.string()
|
|
2210
2308
|
});
|
|
@@ -2230,6 +2328,16 @@ const PhDocumentFieldsFragmentDoc = gql`
|
|
|
2230
2328
|
lastModifiedAtUtcIso
|
|
2231
2329
|
}
|
|
2232
2330
|
`;
|
|
2331
|
+
const DocumentRelationshipFieldsFragmentDoc = gql`
|
|
2332
|
+
fragment DocumentRelationshipFields on DocumentRelationship {
|
|
2333
|
+
sourceId
|
|
2334
|
+
targetId
|
|
2335
|
+
relationshipType
|
|
2336
|
+
metadata
|
|
2337
|
+
createdAt
|
|
2338
|
+
updatedAt
|
|
2339
|
+
}
|
|
2340
|
+
`;
|
|
2233
2341
|
const GetDocumentModelsDocument = gql`
|
|
2234
2342
|
query GetDocumentModels($namespace: String, $paging: PagingInput) {
|
|
2235
2343
|
documentModels(namespace: $namespace, paging: $paging) {
|
|
@@ -2240,7 +2348,6 @@ const GetDocumentModelsDocument = gql`
|
|
|
2240
2348
|
version
|
|
2241
2349
|
specification
|
|
2242
2350
|
}
|
|
2243
|
-
totalCount
|
|
2244
2351
|
hasNextPage
|
|
2245
2352
|
hasPreviousPage
|
|
2246
2353
|
cursor
|
|
@@ -2299,7 +2406,6 @@ const GetDocumentWithOperationsDocument = gql`
|
|
|
2299
2406
|
}
|
|
2300
2407
|
}
|
|
2301
2408
|
}
|
|
2302
|
-
totalCount
|
|
2303
2409
|
hasNextPage
|
|
2304
2410
|
hasPreviousPage
|
|
2305
2411
|
cursor
|
|
@@ -2326,7 +2432,6 @@ const GetDocumentOutgoingRelationshipsDocument = gql`
|
|
|
2326
2432
|
items {
|
|
2327
2433
|
...PHDocumentFields
|
|
2328
2434
|
}
|
|
2329
|
-
totalCount
|
|
2330
2435
|
hasNextPage
|
|
2331
2436
|
hasPreviousPage
|
|
2332
2437
|
cursor
|
|
@@ -2350,7 +2455,6 @@ const GetDocumentIncomingRelationshipsDocument = gql`
|
|
|
2350
2455
|
items {
|
|
2351
2456
|
...PHDocumentFields
|
|
2352
2457
|
}
|
|
2353
|
-
totalCount
|
|
2354
2458
|
hasNextPage
|
|
2355
2459
|
hasPreviousPage
|
|
2356
2460
|
cursor
|
|
@@ -2358,6 +2462,52 @@ const GetDocumentIncomingRelationshipsDocument = gql`
|
|
|
2358
2462
|
}
|
|
2359
2463
|
${PhDocumentFieldsFragmentDoc}
|
|
2360
2464
|
`;
|
|
2465
|
+
const GetDocumentOutgoingRelationshipEdgesDocument = gql`
|
|
2466
|
+
query GetDocumentOutgoingRelationshipEdges(
|
|
2467
|
+
$sourceIdentifier: String!
|
|
2468
|
+
$relationshipType: String
|
|
2469
|
+
$view: ViewFilterInput
|
|
2470
|
+
$paging: PagingInput
|
|
2471
|
+
) {
|
|
2472
|
+
documentOutgoingRelationshipEdges(
|
|
2473
|
+
sourceIdentifier: $sourceIdentifier
|
|
2474
|
+
relationshipType: $relationshipType
|
|
2475
|
+
view: $view
|
|
2476
|
+
paging: $paging
|
|
2477
|
+
) {
|
|
2478
|
+
items {
|
|
2479
|
+
...DocumentRelationshipFields
|
|
2480
|
+
}
|
|
2481
|
+
hasNextPage
|
|
2482
|
+
hasPreviousPage
|
|
2483
|
+
cursor
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
${DocumentRelationshipFieldsFragmentDoc}
|
|
2487
|
+
`;
|
|
2488
|
+
const GetDocumentIncomingRelationshipEdgesDocument = gql`
|
|
2489
|
+
query GetDocumentIncomingRelationshipEdges(
|
|
2490
|
+
$targetIdentifier: String!
|
|
2491
|
+
$relationshipType: String
|
|
2492
|
+
$view: ViewFilterInput
|
|
2493
|
+
$paging: PagingInput
|
|
2494
|
+
) {
|
|
2495
|
+
documentIncomingRelationshipEdges(
|
|
2496
|
+
targetIdentifier: $targetIdentifier
|
|
2497
|
+
relationshipType: $relationshipType
|
|
2498
|
+
view: $view
|
|
2499
|
+
paging: $paging
|
|
2500
|
+
) {
|
|
2501
|
+
items {
|
|
2502
|
+
...DocumentRelationshipFields
|
|
2503
|
+
}
|
|
2504
|
+
hasNextPage
|
|
2505
|
+
hasPreviousPage
|
|
2506
|
+
cursor
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
${DocumentRelationshipFieldsFragmentDoc}
|
|
2510
|
+
`;
|
|
2361
2511
|
const FindDocumentsDocument = gql`
|
|
2362
2512
|
query FindDocuments(
|
|
2363
2513
|
$search: SearchFilterInput
|
|
@@ -2368,7 +2518,6 @@ const FindDocumentsDocument = gql`
|
|
|
2368
2518
|
items {
|
|
2369
2519
|
...PHDocumentFields
|
|
2370
2520
|
}
|
|
2371
|
-
totalCount
|
|
2372
2521
|
hasNextPage
|
|
2373
2522
|
hasPreviousPage
|
|
2374
2523
|
cursor
|
|
@@ -2412,7 +2561,6 @@ const GetDocumentOperationsDocument = gql`
|
|
|
2412
2561
|
}
|
|
2413
2562
|
}
|
|
2414
2563
|
}
|
|
2415
|
-
totalCount
|
|
2416
2564
|
hasNextPage
|
|
2417
2565
|
hasPreviousPage
|
|
2418
2566
|
cursor
|
|
@@ -2548,12 +2696,34 @@ const AddRelationshipDocument = gql`
|
|
|
2548
2696
|
$sourceIdentifier: String!
|
|
2549
2697
|
$targetIdentifier: String!
|
|
2550
2698
|
$relationshipType: String!
|
|
2699
|
+
$metadata: JSONObject
|
|
2551
2700
|
$branch: String
|
|
2552
2701
|
) {
|
|
2553
2702
|
addRelationship(
|
|
2554
2703
|
sourceIdentifier: $sourceIdentifier
|
|
2555
2704
|
targetIdentifier: $targetIdentifier
|
|
2556
2705
|
relationshipType: $relationshipType
|
|
2706
|
+
metadata: $metadata
|
|
2707
|
+
branch: $branch
|
|
2708
|
+
) {
|
|
2709
|
+
...PHDocumentFields
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
${PhDocumentFieldsFragmentDoc}
|
|
2713
|
+
`;
|
|
2714
|
+
const UpdateRelationshipDocument = gql`
|
|
2715
|
+
mutation UpdateRelationship(
|
|
2716
|
+
$sourceIdentifier: String!
|
|
2717
|
+
$targetIdentifier: String!
|
|
2718
|
+
$relationshipType: String!
|
|
2719
|
+
$metadata: JSONObject
|
|
2720
|
+
$branch: String
|
|
2721
|
+
) {
|
|
2722
|
+
updateRelationship(
|
|
2723
|
+
sourceIdentifier: $sourceIdentifier
|
|
2724
|
+
targetIdentifier: $targetIdentifier
|
|
2725
|
+
relationshipType: $relationshipType
|
|
2726
|
+
metadata: $metadata
|
|
2557
2727
|
branch: $branch
|
|
2558
2728
|
) {
|
|
2559
2729
|
...PHDocumentFields
|
|
@@ -2650,11 +2820,15 @@ const PollSyncEnvelopesDocument = gql`
|
|
|
2650
2820
|
$channelId: String!
|
|
2651
2821
|
$outboxAck: Int!
|
|
2652
2822
|
$outboxLatest: Int!
|
|
2823
|
+
$manifestRevision: String
|
|
2824
|
+
$refusals: [SyncRefusalInput!]
|
|
2653
2825
|
) {
|
|
2654
2826
|
pollSyncEnvelopes(
|
|
2655
2827
|
channelId: $channelId
|
|
2656
2828
|
outboxAck: $outboxAck
|
|
2657
2829
|
outboxLatest: $outboxLatest
|
|
2830
|
+
manifestRevision: $manifestRevision
|
|
2831
|
+
refusals: $refusals
|
|
2658
2832
|
) {
|
|
2659
2833
|
envelopes {
|
|
2660
2834
|
type
|
|
@@ -2714,6 +2888,8 @@ const PollSyncEnvelopesDocument = gql`
|
|
|
2714
2888
|
errorType
|
|
2715
2889
|
}
|
|
2716
2890
|
hasMore
|
|
2891
|
+
manifestRevision
|
|
2892
|
+
peerManifestRevision
|
|
2717
2893
|
}
|
|
2718
2894
|
}
|
|
2719
2895
|
`;
|
|
@@ -2722,12 +2898,19 @@ const TouchChannelDocument = gql`
|
|
|
2722
2898
|
touchChannel(input: $input) {
|
|
2723
2899
|
success
|
|
2724
2900
|
ackOrdinal
|
|
2901
|
+
manifest
|
|
2725
2902
|
}
|
|
2726
2903
|
}
|
|
2727
2904
|
`;
|
|
2728
2905
|
const PushSyncEnvelopesDocument = gql`
|
|
2729
|
-
mutation PushSyncEnvelopes(
|
|
2730
|
-
|
|
2906
|
+
mutation PushSyncEnvelopes(
|
|
2907
|
+
$envelopes: [SyncEnvelopeInput!]!
|
|
2908
|
+
$peerManifestRevision: String
|
|
2909
|
+
) {
|
|
2910
|
+
pushSyncEnvelopes(
|
|
2911
|
+
envelopes: $envelopes
|
|
2912
|
+
peerManifestRevision: $peerManifestRevision
|
|
2913
|
+
)
|
|
2731
2914
|
}
|
|
2732
2915
|
`;
|
|
2733
2916
|
function getSdk(requester) {
|
|
@@ -2747,6 +2930,12 @@ function getSdk(requester) {
|
|
|
2747
2930
|
GetDocumentIncomingRelationships(variables, options) {
|
|
2748
2931
|
return requester(GetDocumentIncomingRelationshipsDocument, variables, options);
|
|
2749
2932
|
},
|
|
2933
|
+
GetDocumentOutgoingRelationshipEdges(variables, options) {
|
|
2934
|
+
return requester(GetDocumentOutgoingRelationshipEdgesDocument, variables, options);
|
|
2935
|
+
},
|
|
2936
|
+
GetDocumentIncomingRelationshipEdges(variables, options) {
|
|
2937
|
+
return requester(GetDocumentIncomingRelationshipEdgesDocument, variables, options);
|
|
2938
|
+
},
|
|
2750
2939
|
FindDocuments(variables, options) {
|
|
2751
2940
|
return requester(FindDocumentsDocument, variables, options);
|
|
2752
2941
|
},
|
|
@@ -2780,6 +2969,9 @@ function getSdk(requester) {
|
|
|
2780
2969
|
AddRelationship(variables, options) {
|
|
2781
2970
|
return requester(AddRelationshipDocument, variables, options);
|
|
2782
2971
|
},
|
|
2972
|
+
UpdateRelationship(variables, options) {
|
|
2973
|
+
return requester(UpdateRelationshipDocument, variables, options);
|
|
2974
|
+
},
|
|
2783
2975
|
RemoveRelationship(variables, options) {
|
|
2784
2976
|
return requester(RemoveRelationshipDocument, variables, options);
|
|
2785
2977
|
},
|
|
@@ -2820,8 +3012,7 @@ function toDocumentModelResultPage(result) {
|
|
|
2820
3012
|
cursor: result.nextCursor ?? null,
|
|
2821
3013
|
hasNextPage: !!result.nextCursor,
|
|
2822
3014
|
hasPreviousPage: !!result.options.cursor,
|
|
2823
|
-
items: models.map(toGqlDocumentModelState)
|
|
2824
|
-
totalCount: result.results.length
|
|
3015
|
+
items: models.map(toGqlDocumentModelState)
|
|
2825
3016
|
};
|
|
2826
3017
|
}
|
|
2827
3018
|
/**
|
|
@@ -2853,8 +3044,32 @@ function toPhDocumentResultPage(result) {
|
|
|
2853
3044
|
cursor: result.nextCursor ?? null,
|
|
2854
3045
|
hasNextPage: !!result.nextCursor,
|
|
2855
3046
|
hasPreviousPage: !!result.options.cursor,
|
|
2856
|
-
items: result.results.map(toGqlPhDocument)
|
|
2857
|
-
|
|
3047
|
+
items: result.results.map(toGqlPhDocument)
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
/**
|
|
3051
|
+
* Converts a relationship edge from the indexer to GraphQL format.
|
|
3052
|
+
*/
|
|
3053
|
+
function toGqlDocumentRelationship(relationship) {
|
|
3054
|
+
return {
|
|
3055
|
+
sourceId: relationship.sourceId,
|
|
3056
|
+
targetId: relationship.targetId,
|
|
3057
|
+
relationshipType: relationship.relationshipType,
|
|
3058
|
+
metadata: relationship.metadata ?? null,
|
|
3059
|
+
createdAt: relationship.createdAt,
|
|
3060
|
+
updatedAt: relationship.updatedAt
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
/**
|
|
3064
|
+
* Converts a PagedResults of relationship edges to GraphQL
|
|
3065
|
+
* DocumentRelationshipResultPage format
|
|
3066
|
+
*/
|
|
3067
|
+
function toDocumentRelationshipResultPage(result) {
|
|
3068
|
+
return {
|
|
3069
|
+
cursor: result.nextCursor ?? null,
|
|
3070
|
+
hasNextPage: !!result.nextCursor,
|
|
3071
|
+
hasPreviousPage: !!result.options.cursor,
|
|
3072
|
+
items: result.results.map(toGqlDocumentRelationship)
|
|
2858
3073
|
};
|
|
2859
3074
|
}
|
|
2860
3075
|
/**
|
|
@@ -2904,8 +3119,8 @@ function fromInputMaybe(value) {
|
|
|
2904
3119
|
function toReactorPropagationMode(gqlMode) {
|
|
2905
3120
|
if (gqlMode == null) return;
|
|
2906
3121
|
switch (gqlMode) {
|
|
2907
|
-
case
|
|
2908
|
-
case
|
|
3122
|
+
case "CASCADE": return PropagationMode$1.Cascade;
|
|
3123
|
+
case "ORPHAN": return PropagationMode$1.None;
|
|
2909
3124
|
}
|
|
2910
3125
|
}
|
|
2911
3126
|
/**
|
|
@@ -2915,10 +3130,10 @@ function toReactorPropagationMode(gqlMode) {
|
|
|
2915
3130
|
*/
|
|
2916
3131
|
function toGqlActionEvaluation(evaluation) {
|
|
2917
3132
|
return evaluation.decision === "allow" ? {
|
|
2918
|
-
decision:
|
|
3133
|
+
decision: "ALLOW",
|
|
2919
3134
|
reason: null
|
|
2920
3135
|
} : {
|
|
2921
|
-
decision:
|
|
3136
|
+
decision: "DENY",
|
|
2922
3137
|
reason: evaluation.reason
|
|
2923
3138
|
};
|
|
2924
3139
|
}
|
|
@@ -3123,8 +3338,7 @@ function toOperationResultPage(result) {
|
|
|
3123
3338
|
cursor: result.nextCursor ?? null,
|
|
3124
3339
|
hasNextPage: !!result.nextCursor,
|
|
3125
3340
|
hasPreviousPage: !!result.options.cursor && result.options.cursor !== "0",
|
|
3126
|
-
items: result.results.map(serializeOperationForGraphQL)
|
|
3127
|
-
totalCount: result.results.length
|
|
3341
|
+
items: result.results.map(serializeOperationForGraphQL)
|
|
3128
3342
|
};
|
|
3129
3343
|
}
|
|
3130
3344
|
function toGqlDocumentChangeEvent(event) {
|
|
@@ -3196,6 +3410,21 @@ function pickDriveClient(reactorClient, driveContainerType, reactorDriveClient)
|
|
|
3196
3410
|
}
|
|
3197
3411
|
return reactorClient.drives;
|
|
3198
3412
|
}
|
|
3413
|
+
/**
|
|
3414
|
+
* A mutation's document re-read as the caller, since the client reads it back
|
|
3415
|
+
* as its signer. A caller who may write but not read gets it stripped.
|
|
3416
|
+
*/
|
|
3417
|
+
async function readAsCaller(reactorClient, document, subject, branch) {
|
|
3418
|
+
if (!subject) return document;
|
|
3419
|
+
try {
|
|
3420
|
+
return await reactorClient.get(document.header.id, {
|
|
3421
|
+
subject,
|
|
3422
|
+
branch
|
|
3423
|
+
});
|
|
3424
|
+
} catch (error) {
|
|
3425
|
+
throw new GraphQLError(`Failed to read the document back: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3199
3428
|
async function documentModels(reactorClient, args) {
|
|
3200
3429
|
const namespace = fromInputMaybe(args.namespace);
|
|
3201
3430
|
let paging;
|
|
@@ -3303,6 +3532,54 @@ async function documentIncomingRelationships(reactorClient, args, subject) {
|
|
|
3303
3532
|
throw new GraphQLError(`Failed to convert incoming relationships to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3304
3533
|
}
|
|
3305
3534
|
}
|
|
3535
|
+
async function documentOutgoingRelationshipEdges(reactorClient, args, subject) {
|
|
3536
|
+
const view = toRelationshipViewFilter(args.view, subject);
|
|
3537
|
+
const paging = toRelationshipPagingOptions(args.paging);
|
|
3538
|
+
let result;
|
|
3539
|
+
try {
|
|
3540
|
+
result = await reactorClient.getOutgoingRelationshipEdges(args.sourceIdentifier, fromInputMaybe(args.relationshipType), view, paging);
|
|
3541
|
+
} catch (error) {
|
|
3542
|
+
throw new GraphQLError(`Failed to fetch outgoing relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3543
|
+
}
|
|
3544
|
+
try {
|
|
3545
|
+
return toDocumentRelationshipResultPage(result);
|
|
3546
|
+
} catch (error) {
|
|
3547
|
+
throw new GraphQLError(`Failed to convert outgoing relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
async function documentIncomingRelationshipEdges(reactorClient, args, subject) {
|
|
3551
|
+
const view = toRelationshipViewFilter(args.view, subject);
|
|
3552
|
+
const paging = toRelationshipPagingOptions(args.paging);
|
|
3553
|
+
let result;
|
|
3554
|
+
try {
|
|
3555
|
+
result = await reactorClient.getIncomingRelationshipEdges(args.targetIdentifier, fromInputMaybe(args.relationshipType), view, paging);
|
|
3556
|
+
} catch (error) {
|
|
3557
|
+
throw new GraphQLError(`Failed to fetch incoming relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3558
|
+
}
|
|
3559
|
+
try {
|
|
3560
|
+
return toDocumentRelationshipResultPage(result);
|
|
3561
|
+
} catch (error) {
|
|
3562
|
+
throw new GraphQLError(`Failed to convert incoming relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
function toRelationshipViewFilter(input, subject) {
|
|
3566
|
+
if (!input) return subject ? { subject } : void 0;
|
|
3567
|
+
return {
|
|
3568
|
+
subject,
|
|
3569
|
+
branch: fromInputMaybe(input.branch),
|
|
3570
|
+
scopes: toMutableArray(fromInputMaybe(input.scopes))
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
function toRelationshipPagingOptions(input) {
|
|
3574
|
+
if (!input) return;
|
|
3575
|
+
const cursor = fromInputMaybe(input.cursor);
|
|
3576
|
+
const limit = fromInputMaybe(input.limit);
|
|
3577
|
+
if (!cursor && !limit) return;
|
|
3578
|
+
return {
|
|
3579
|
+
cursor: cursor || "",
|
|
3580
|
+
limit: limit || 10
|
|
3581
|
+
};
|
|
3582
|
+
}
|
|
3306
3583
|
async function findDocuments(reactorClient, args, subject) {
|
|
3307
3584
|
let view = subject ? { subject } : void 0;
|
|
3308
3585
|
if (args.view) view = {
|
|
@@ -3335,13 +3612,42 @@ async function findDocuments(reactorClient, args, subject) {
|
|
|
3335
3612
|
throw new GraphQLError(`Failed to convert documents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3336
3613
|
}
|
|
3337
3614
|
}
|
|
3338
|
-
|
|
3615
|
+
/**
|
|
3616
|
+
* The answer for a job the caller may not see, shaped as the reactor answers
|
|
3617
|
+
* an unknown one so that the two cannot be told apart.
|
|
3618
|
+
*/
|
|
3619
|
+
function unknownJob(jobId) {
|
|
3620
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3621
|
+
return {
|
|
3622
|
+
id: jobId,
|
|
3623
|
+
documentId: "",
|
|
3624
|
+
status: JobStatus.FAILED,
|
|
3625
|
+
createdAtUtcIso: now,
|
|
3626
|
+
completedAtUtcIso: now,
|
|
3627
|
+
error: {
|
|
3628
|
+
name: "Error",
|
|
3629
|
+
message: "Job not found",
|
|
3630
|
+
stack: ""
|
|
3631
|
+
},
|
|
3632
|
+
consistencyToken: {
|
|
3633
|
+
version: 1,
|
|
3634
|
+
createdAtUtcIso: now,
|
|
3635
|
+
coordinates: []
|
|
3636
|
+
},
|
|
3637
|
+
meta: {
|
|
3638
|
+
batchId: jobId,
|
|
3639
|
+
batchJobIds: [jobId]
|
|
3640
|
+
}
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
async function jobStatus(reactorClient, args, serves) {
|
|
3339
3644
|
let result;
|
|
3340
3645
|
try {
|
|
3341
3646
|
result = await reactorClient.getJobStatus(args.jobId);
|
|
3342
3647
|
} catch (error) {
|
|
3343
3648
|
throw new GraphQLError(`Failed to fetch job status: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3344
3649
|
}
|
|
3650
|
+
if (!await serves(result.documentId)) result = unknownJob(args.jobId);
|
|
3345
3651
|
try {
|
|
3346
3652
|
return toGqlJobInfo(result);
|
|
3347
3653
|
} catch (error) {
|
|
@@ -3424,7 +3730,7 @@ async function evaluateActions(reactorClient, args, subject) {
|
|
|
3424
3730
|
anyDenied: result.anyDenied
|
|
3425
3731
|
};
|
|
3426
3732
|
}
|
|
3427
|
-
async function createDocument(reactorClient, args, reactorDriveClient) {
|
|
3733
|
+
async function createDocument(reactorClient, args, reactorDriveClient, subject) {
|
|
3428
3734
|
if (!args.document || typeof args.document !== "object") throw new GraphQLError("Invalid document: must be an object");
|
|
3429
3735
|
const document = args.document;
|
|
3430
3736
|
if (!document.header || typeof document.header !== "object") throw new GraphQLError("Invalid document: missing or invalid header");
|
|
@@ -3432,28 +3738,30 @@ async function createDocument(reactorClient, args, reactorDriveClient) {
|
|
|
3432
3738
|
let result;
|
|
3433
3739
|
try {
|
|
3434
3740
|
if (parentIdentifier) {
|
|
3435
|
-
const parent = await reactorClient.get(parentIdentifier);
|
|
3741
|
+
const parent = await reactorClient.get(parentIdentifier, subject && { subject });
|
|
3436
3742
|
if (isDriveContainerType(parent.header.documentType)) result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parentIdentifier, document);
|
|
3437
3743
|
else result = await reactorClient.create(document, parentIdentifier);
|
|
3438
3744
|
} else result = await reactorClient.create(document);
|
|
3439
3745
|
} catch (error) {
|
|
3440
3746
|
throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3441
3747
|
}
|
|
3748
|
+
result = await readAsCaller(reactorClient, result, subject);
|
|
3442
3749
|
try {
|
|
3443
3750
|
return toGqlPhDocument(result);
|
|
3444
3751
|
} catch (error) {
|
|
3445
3752
|
throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3446
3753
|
}
|
|
3447
3754
|
}
|
|
3448
|
-
async function createEmptyDocument(reactorClient, args, reactorDriveClient) {
|
|
3755
|
+
async function createEmptyDocument(reactorClient, args, reactorDriveClient, subject) {
|
|
3449
3756
|
const parentIdentifier = fromInputMaybe(args.parentIdentifier);
|
|
3450
3757
|
const name = fromInputMaybe(args.name);
|
|
3451
3758
|
let result;
|
|
3452
3759
|
try {
|
|
3453
3760
|
if (parentIdentifier) {
|
|
3454
|
-
const parent = await reactorClient.get(parentIdentifier);
|
|
3761
|
+
const parent = await reactorClient.get(parentIdentifier, subject && { subject });
|
|
3455
3762
|
if (isDriveContainerType(parent.header.documentType)) {
|
|
3456
|
-
const
|
|
3763
|
+
const module = await reactorClient.getDocumentModelModule(args.documentType);
|
|
3764
|
+
const document = withSignaturePolicy(module.utils.createDocument(), await reactorClient.getCreateSignaturePolicy());
|
|
3457
3765
|
if (name) document.header.name = name;
|
|
3458
3766
|
result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parentIdentifier, document);
|
|
3459
3767
|
} else result = await reactorClient.createEmpty(args.documentType, { parentIdentifier });
|
|
@@ -3461,13 +3769,14 @@ async function createEmptyDocument(reactorClient, args, reactorDriveClient) {
|
|
|
3461
3769
|
} catch (error) {
|
|
3462
3770
|
throw new GraphQLError(`Failed to create empty document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3463
3771
|
}
|
|
3772
|
+
result = await readAsCaller(reactorClient, result, subject);
|
|
3464
3773
|
try {
|
|
3465
3774
|
return toGqlPhDocument(result);
|
|
3466
3775
|
} catch (error) {
|
|
3467
3776
|
throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3468
3777
|
}
|
|
3469
3778
|
}
|
|
3470
|
-
async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient) {
|
|
3779
|
+
async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient, subject) {
|
|
3471
3780
|
const parentIdentifier = fromInputMaybe(args.parentIdentifier);
|
|
3472
3781
|
const name = fromInputMaybe(args.name);
|
|
3473
3782
|
const slug = fromInputMaybe(args.slug);
|
|
@@ -3478,7 +3787,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
|
|
|
3478
3787
|
} catch (error) {
|
|
3479
3788
|
throw new GraphQLError(`Document model not found for type ${args.documentType}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3480
3789
|
}
|
|
3481
|
-
const document = module.utils.createDocument();
|
|
3790
|
+
const document = withSignaturePolicy(module.utils.createDocument(), await reactorClient.getCreateSignaturePolicy());
|
|
3482
3791
|
const allowedScopes = new Set(Object.keys(module.documentModel.global.specifications.at(-1)?.state ?? {}));
|
|
3483
3792
|
const state = document.state;
|
|
3484
3793
|
for (const [scope, scopeState] of Object.entries(args.initialState)) if (allowedScopes.has(scope) && scope in state) state[scope] = {
|
|
@@ -3495,7 +3804,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
|
|
|
3495
3804
|
if (parentIdentifier) {
|
|
3496
3805
|
let parent;
|
|
3497
3806
|
try {
|
|
3498
|
-
parent = await reactorClient.get(parentIdentifier);
|
|
3807
|
+
parent = await reactorClient.get(parentIdentifier, subject && { subject });
|
|
3499
3808
|
} catch (error) {
|
|
3500
3809
|
throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3501
3810
|
}
|
|
@@ -3516,6 +3825,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
|
|
|
3516
3825
|
} catch (error) {
|
|
3517
3826
|
throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3518
3827
|
}
|
|
3828
|
+
result = await readAsCaller(reactorClient, result, subject);
|
|
3519
3829
|
try {
|
|
3520
3830
|
return toGqlPhDocument(result);
|
|
3521
3831
|
} catch (error) {
|
|
@@ -3532,12 +3842,12 @@ const DEFAULT_BRANCH = "main";
|
|
|
3532
3842
|
* the schema cannot express: a signature travels as one string, and verification
|
|
3533
3843
|
* reads it as the tuple it was.
|
|
3534
3844
|
*/
|
|
3535
|
-
async function execute(reactorClient, args) {
|
|
3845
|
+
async function execute(reactorClient, args, subject) {
|
|
3536
3846
|
const actions = toSubmittableActions(args.actions);
|
|
3537
3847
|
const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
|
|
3538
3848
|
let result;
|
|
3539
3849
|
try {
|
|
3540
|
-
result = await reactorClient.execute(args.documentIdentifier, branch, actions);
|
|
3850
|
+
result = await reactorClient.execute(args.documentIdentifier, branch, actions, void 0, subject);
|
|
3541
3851
|
} catch (error) {
|
|
3542
3852
|
throw new GraphQLError(`Failed to execute actions: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3543
3853
|
}
|
|
@@ -3565,7 +3875,7 @@ async function executeAsync(reactorClient, args) {
|
|
|
3565
3875
|
}
|
|
3566
3876
|
return toGqlJobInfo(job);
|
|
3567
3877
|
}
|
|
3568
|
-
async function mutateDocument(reactorClient, args) {
|
|
3878
|
+
async function mutateDocument(reactorClient, args, subject) {
|
|
3569
3879
|
let validatedActions;
|
|
3570
3880
|
try {
|
|
3571
3881
|
validatedActions = validateActions(args.actions);
|
|
@@ -3576,7 +3886,7 @@ async function mutateDocument(reactorClient, args) {
|
|
|
3576
3886
|
const branch = args.view?.branch ?? "main";
|
|
3577
3887
|
let result;
|
|
3578
3888
|
try {
|
|
3579
|
-
result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions);
|
|
3889
|
+
result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions, void 0, subject);
|
|
3580
3890
|
} catch (error) {
|
|
3581
3891
|
throw new GraphQLError(`Failed to mutate document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3582
3892
|
}
|
|
@@ -3603,7 +3913,7 @@ async function mutateDocumentAsync(reactorClient, args) {
|
|
|
3603
3913
|
}
|
|
3604
3914
|
return result.id;
|
|
3605
3915
|
}
|
|
3606
|
-
async function renameDocument(reactorClient, args, signal) {
|
|
3916
|
+
async function renameDocument(reactorClient, args, signal, subject) {
|
|
3607
3917
|
const branch = fromInputMaybe(args.branch);
|
|
3608
3918
|
let result;
|
|
3609
3919
|
try {
|
|
@@ -3611,13 +3921,14 @@ async function renameDocument(reactorClient, args, signal) {
|
|
|
3611
3921
|
} catch (error) {
|
|
3612
3922
|
throw new GraphQLError(`Failed to rename document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3613
3923
|
}
|
|
3924
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3614
3925
|
try {
|
|
3615
3926
|
return toGqlPhDocument(result);
|
|
3616
3927
|
} catch (error) {
|
|
3617
3928
|
throw new GraphQLError(`Failed to convert renamed document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3618
3929
|
}
|
|
3619
3930
|
}
|
|
3620
|
-
async function setPreferredEditor(reactorClient, args, signal) {
|
|
3931
|
+
async function setPreferredEditor(reactorClient, args, signal, subject) {
|
|
3621
3932
|
const branch = fromInputMaybe(args.branch);
|
|
3622
3933
|
const preferredEditor = fromInputMaybe(args.preferredEditor) ?? null;
|
|
3623
3934
|
let result;
|
|
@@ -3626,27 +3937,46 @@ async function setPreferredEditor(reactorClient, args, signal) {
|
|
|
3626
3937
|
} catch (error) {
|
|
3627
3938
|
throw new GraphQLError(`Failed to set preferred editor: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3628
3939
|
}
|
|
3940
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3629
3941
|
try {
|
|
3630
3942
|
return toGqlPhDocument(result);
|
|
3631
3943
|
} catch (error) {
|
|
3632
3944
|
throw new GraphQLError(`Failed to convert updated document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3633
3945
|
}
|
|
3634
3946
|
}
|
|
3635
|
-
async function addRelationship(reactorClient, args) {
|
|
3947
|
+
async function addRelationship(reactorClient, args, subject) {
|
|
3636
3948
|
const branch = fromInputMaybe(args.branch);
|
|
3949
|
+
const metadata = fromInputMaybe(args.metadata);
|
|
3637
3950
|
let result;
|
|
3638
3951
|
try {
|
|
3639
|
-
result = await reactorClient.addRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, branch);
|
|
3952
|
+
result = await reactorClient.addRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, metadata, branch);
|
|
3640
3953
|
} catch (error) {
|
|
3641
3954
|
throw new GraphQLError(`Failed to add relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3642
3955
|
}
|
|
3956
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3957
|
+
try {
|
|
3958
|
+
return toGqlPhDocument(result);
|
|
3959
|
+
} catch (error) {
|
|
3960
|
+
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
async function updateRelationship(reactorClient, args, subject) {
|
|
3964
|
+
const branch = fromInputMaybe(args.branch);
|
|
3965
|
+
const metadata = fromInputMaybe(args.metadata) ?? null;
|
|
3966
|
+
let result;
|
|
3967
|
+
try {
|
|
3968
|
+
result = await reactorClient.updateRelationship(args.sourceIdentifier, args.targetIdentifier, args.relationshipType, metadata, branch);
|
|
3969
|
+
} catch (error) {
|
|
3970
|
+
throw new GraphQLError(`Failed to update relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3971
|
+
}
|
|
3972
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3643
3973
|
try {
|
|
3644
3974
|
return toGqlPhDocument(result);
|
|
3645
3975
|
} catch (error) {
|
|
3646
3976
|
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3647
3977
|
}
|
|
3648
3978
|
}
|
|
3649
|
-
async function removeRelationship(reactorClient, args) {
|
|
3979
|
+
async function removeRelationship(reactorClient, args, subject) {
|
|
3650
3980
|
const branch = fromInputMaybe(args.branch);
|
|
3651
3981
|
let result;
|
|
3652
3982
|
try {
|
|
@@ -3654,13 +3984,14 @@ async function removeRelationship(reactorClient, args) {
|
|
|
3654
3984
|
} catch (error) {
|
|
3655
3985
|
throw new GraphQLError(`Failed to remove relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3656
3986
|
}
|
|
3987
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3657
3988
|
try {
|
|
3658
3989
|
return toGqlPhDocument(result);
|
|
3659
3990
|
} catch (error) {
|
|
3660
3991
|
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3661
3992
|
}
|
|
3662
3993
|
}
|
|
3663
|
-
async function moveRelationship(reactorClient, args) {
|
|
3994
|
+
async function moveRelationship(reactorClient, args, subject) {
|
|
3664
3995
|
const branch = fromInputMaybe(args.branch);
|
|
3665
3996
|
let result;
|
|
3666
3997
|
try {
|
|
@@ -3668,6 +3999,10 @@ async function moveRelationship(reactorClient, args) {
|
|
|
3668
3999
|
} catch (error) {
|
|
3669
4000
|
throw new GraphQLError(`Failed to move relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3670
4001
|
}
|
|
4002
|
+
result = {
|
|
4003
|
+
source: await readAsCaller(reactorClient, result.source, subject, branch),
|
|
4004
|
+
target: await readAsCaller(reactorClient, result.target, subject, branch)
|
|
4005
|
+
};
|
|
3671
4006
|
try {
|
|
3672
4007
|
return {
|
|
3673
4008
|
source: toGqlPhDocument(result.source),
|
|
@@ -3706,12 +4041,19 @@ async function deleteDocuments(reactorClient, args) {
|
|
|
3706
4041
|
* an unanswered question: the first authenticated subject to use it adopts it.
|
|
3707
4042
|
*/
|
|
3708
4043
|
async function touchChannel(syncManager, args, boundAddress) {
|
|
4044
|
+
const peer = readPeerManifest(args.input.manifest);
|
|
4045
|
+
let existing;
|
|
3709
4046
|
try {
|
|
4047
|
+
existing = syncManager.getById(args.input.id);
|
|
4048
|
+
} catch {}
|
|
4049
|
+
if (existing) {
|
|
4050
|
+
await syncManager.setPeerManifest(args.input.id, peer);
|
|
3710
4051
|
return {
|
|
3711
4052
|
success: true,
|
|
3712
|
-
ackOrdinal:
|
|
4053
|
+
ackOrdinal: existing.channel.inbox.ackOrdinal,
|
|
4054
|
+
manifest: syncManager.localManifest()
|
|
3713
4055
|
};
|
|
3714
|
-
}
|
|
4056
|
+
}
|
|
3715
4057
|
const filter = {
|
|
3716
4058
|
documentId: [...args.input.filter.documentId],
|
|
3717
4059
|
scope: [...args.input.filter.scope],
|
|
@@ -3725,13 +4067,45 @@ async function touchChannel(syncManager, args, boundAddress) {
|
|
|
3725
4067
|
await syncManager.add(args.input.name, DriveCollectionId.fromKey(args.input.collectionId), {
|
|
3726
4068
|
type: "polling",
|
|
3727
4069
|
parameters: {}
|
|
3728
|
-
}, filter, options, args.input.id);
|
|
4070
|
+
}, filter, options, args.input.id, peer);
|
|
3729
4071
|
} catch (error) {
|
|
3730
4072
|
throw new GraphQLError(`Failed to create channel: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3731
4073
|
}
|
|
3732
4074
|
return {
|
|
3733
4075
|
success: true,
|
|
3734
|
-
ackOrdinal: 0
|
|
4076
|
+
ackOrdinal: 0,
|
|
4077
|
+
manifest: syncManager.localManifest()
|
|
4078
|
+
};
|
|
4079
|
+
}
|
|
4080
|
+
async function syncHolds(syncManager, args) {
|
|
4081
|
+
return (await syncManager.listHolds({
|
|
4082
|
+
remoteName: args.remoteName ?? void 0,
|
|
4083
|
+
documentId: args.documentId ?? void 0
|
|
4084
|
+
})).map((hold) => ({
|
|
4085
|
+
...hold,
|
|
4086
|
+
reason: {
|
|
4087
|
+
...hold.reason,
|
|
4088
|
+
peerSupports: [...hold.reason.peerSupports]
|
|
4089
|
+
},
|
|
4090
|
+
heldAtUtcMs: String(hold.heldAtUtcMs)
|
|
4091
|
+
}));
|
|
4092
|
+
}
|
|
4093
|
+
function peerAgreement(syncManager, args) {
|
|
4094
|
+
const agreement = syncManager.agreement();
|
|
4095
|
+
const members = [...agreement.members([args.collectionId])].map(([remoteName, supports]) => ({
|
|
4096
|
+
remoteName,
|
|
4097
|
+
announced: "revision" in supports,
|
|
4098
|
+
protocols: supports.protocols,
|
|
4099
|
+
features: supports.features
|
|
4100
|
+
}));
|
|
4101
|
+
return {
|
|
4102
|
+
collectionId: args.collectionId,
|
|
4103
|
+
local: agreement.local(),
|
|
4104
|
+
members,
|
|
4105
|
+
limitedBy: Object.keys(agreement.basis().wanted).map((protocol) => ({
|
|
4106
|
+
protocol,
|
|
4107
|
+
remoteNames: agreement.limitedBy(args.collectionId, protocol)
|
|
4108
|
+
}))
|
|
3735
4109
|
};
|
|
3736
4110
|
}
|
|
3737
4111
|
/**
|
|
@@ -3786,7 +4160,8 @@ async function collectHeldSyncOperations(syncOps, gate, subject, signal) {
|
|
|
3786
4160
|
* client ordinal the switchboard has successfully applied, so the client
|
|
3787
4161
|
* can trim its own outbox)
|
|
3788
4162
|
*/
|
|
3789
|
-
function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set()) {
|
|
4163
|
+
function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set(), gatedOpIds) {
|
|
4164
|
+
const isHeld = (syncOp) => heldOpIds.has(syncOp.id) || gatedOpIds !== void 0 && !gatedOpIds.has(syncOp.id);
|
|
3790
4165
|
let remote;
|
|
3791
4166
|
try {
|
|
3792
4167
|
remote = syncManager.getById(args.channelId);
|
|
@@ -3794,7 +4169,11 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3794
4169
|
throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3795
4170
|
}
|
|
3796
4171
|
remote.channel.notePoll();
|
|
3797
|
-
const
|
|
4172
|
+
const revisions = {
|
|
4173
|
+
manifestRevision: syncManager.localManifest().revision,
|
|
4174
|
+
peerManifestRevision: remote.meta.peer?.manifest?.revision ?? null
|
|
4175
|
+
};
|
|
4176
|
+
const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId) && !isHeld(syncOp)).map((syncOp) => ({
|
|
3798
4177
|
documentId: syncOp.documentId,
|
|
3799
4178
|
error: syncOp.error?.message ?? "Unknown error",
|
|
3800
4179
|
errorType: syncOperationErrorType(syncOp.error),
|
|
@@ -3829,7 +4208,8 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3829
4208
|
envelopes: [],
|
|
3830
4209
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
3831
4210
|
deadLetters,
|
|
3832
|
-
hasMore: false
|
|
4211
|
+
hasMore: false,
|
|
4212
|
+
...revisions
|
|
3833
4213
|
};
|
|
3834
4214
|
const sorted = [...operations].sort((a, b) => {
|
|
3835
4215
|
return (a.operations[0]?.context.ordinal ?? 0) - (b.operations[0]?.context.ordinal ?? 0);
|
|
@@ -3843,7 +4223,7 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3843
4223
|
hasMore = true;
|
|
3844
4224
|
break;
|
|
3845
4225
|
}
|
|
3846
|
-
if (
|
|
4226
|
+
if (isHeld(syncOp)) continue;
|
|
3847
4227
|
syncOp.deliveredCount ??= 0;
|
|
3848
4228
|
syncOp.emittedCount ??= 0;
|
|
3849
4229
|
while (syncOp.deliveredCount < syncOp.emittedCount && syncOp.operations[syncOp.deliveredCount].context.ordinal <= args.outboxLatest) syncOp.deliveredCount += 1;
|
|
@@ -3897,10 +4277,44 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3897
4277
|
envelopes,
|
|
3898
4278
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
3899
4279
|
deadLetters,
|
|
3900
|
-
hasMore
|
|
4280
|
+
hasMore,
|
|
4281
|
+
...revisions
|
|
3901
4282
|
};
|
|
3902
4283
|
}
|
|
3903
4284
|
/**
|
|
4285
|
+
* The client's UNSUPPORTED_PROTOCOL refusals of polled rows. Each becomes a hold
|
|
4286
|
+
* for that client, as a pushed refusal does, rather than counting as delivered.
|
|
4287
|
+
*/
|
|
4288
|
+
function holdPollRefusals(syncManager, channelId, refusals) {
|
|
4289
|
+
if (!refusals?.length) return;
|
|
4290
|
+
let remote;
|
|
4291
|
+
try {
|
|
4292
|
+
remote = syncManager.getById(channelId);
|
|
4293
|
+
} catch {
|
|
4294
|
+
return;
|
|
4295
|
+
}
|
|
4296
|
+
const refused = refusals.map((refusal) => {
|
|
4297
|
+
const syncOp = new SyncOperation(crypto.randomUUID(), "", [], remote.meta.name, refusal.documentId, [], refusal.branch, []);
|
|
4298
|
+
syncOp.failed(new ChannelError(ChannelErrorSource.Outbox, /* @__PURE__ */ new Error(`Refused by ${remote.meta.name}`), "UNSUPPORTED_PROTOCOL"));
|
|
4299
|
+
return syncOp;
|
|
4300
|
+
});
|
|
4301
|
+
remote.channel.deadLetter.add(...refused);
|
|
4302
|
+
}
|
|
4303
|
+
/**
|
|
4304
|
+
* A poll naming no revision is from a client without peer agreement: it is
|
|
4305
|
+
* recorded silent, and what it can no longer run is held, before it is served.
|
|
4306
|
+
*/
|
|
4307
|
+
async function silenceUnversionedPoll(syncManager, channelId, manifestRevision) {
|
|
4308
|
+
if (typeof manifestRevision === "string") return;
|
|
4309
|
+
let remote;
|
|
4310
|
+
try {
|
|
4311
|
+
remote = syncManager.getById(channelId);
|
|
4312
|
+
} catch {
|
|
4313
|
+
return;
|
|
4314
|
+
}
|
|
4315
|
+
if (remote.meta.peer?.manifest) await syncManager.setPeerManifest(channelId, null);
|
|
4316
|
+
}
|
|
4317
|
+
/**
|
|
3904
4318
|
* Receives sync envelopes pushed by a client and adds them to the
|
|
3905
4319
|
* appropriate channel inboxes.
|
|
3906
4320
|
*
|
|
@@ -3956,6 +4370,16 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
3956
4370
|
const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
|
|
3957
4371
|
return this.resolvers[`${documentName}Mutations`];
|
|
3958
4372
|
}
|
|
4373
|
+
/** Drops the items the host's own ACL refuses the caller. */
|
|
4374
|
+
async #readableItems(page, ctx) {
|
|
4375
|
+
if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
|
|
4376
|
+
const items = [];
|
|
4377
|
+
for (const item of page.items) if (await this.canReadDocument(item.id, ctx)) items.push(item);
|
|
4378
|
+
return {
|
|
4379
|
+
...page,
|
|
4380
|
+
items
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
3959
4383
|
/**
|
|
3960
4384
|
* Generate __resolveType functions for union types found in the document model schema.
|
|
3961
4385
|
* Parses the state schema to find union definitions and their member types,
|
|
@@ -4016,7 +4440,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4016
4440
|
const result = await document(this.reactorClient, {
|
|
4017
4441
|
identifier,
|
|
4018
4442
|
view
|
|
4019
|
-
});
|
|
4443
|
+
}, this.viewSubject(ctx));
|
|
4020
4444
|
if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${identifier} is not of type ${documentType}`);
|
|
4021
4445
|
await this.assertCanReadCanonical(result.document.id, ctx);
|
|
4022
4446
|
return result;
|
|
@@ -4026,17 +4450,8 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4026
4450
|
const result = await findDocuments(this.reactorClient, {
|
|
4027
4451
|
search: { type: documentType },
|
|
4028
4452
|
paging
|
|
4029
|
-
});
|
|
4030
|
-
|
|
4031
|
-
const filteredItems = [];
|
|
4032
|
-
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
4033
|
-
return {
|
|
4034
|
-
...result,
|
|
4035
|
-
items: filteredItems,
|
|
4036
|
-
totalCount: filteredItems.length
|
|
4037
|
-
};
|
|
4038
|
-
}
|
|
4039
|
-
return result;
|
|
4453
|
+
}, this.viewSubject(ctx));
|
|
4454
|
+
return this.#readableItems(result, ctx);
|
|
4040
4455
|
},
|
|
4041
4456
|
findDocuments: async (_, args, ctx) => {
|
|
4042
4457
|
const { search, view, paging } = args;
|
|
@@ -4047,17 +4462,8 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4047
4462
|
},
|
|
4048
4463
|
view,
|
|
4049
4464
|
paging
|
|
4050
|
-
});
|
|
4051
|
-
|
|
4052
|
-
const filteredItems = [];
|
|
4053
|
-
for (const item of result.items) if (await this.canReadDocument(item.id, ctx)) filteredItems.push(item);
|
|
4054
|
-
return {
|
|
4055
|
-
...result,
|
|
4056
|
-
items: filteredItems,
|
|
4057
|
-
totalCount: filteredItems.length
|
|
4058
|
-
};
|
|
4059
|
-
}
|
|
4060
|
-
return result;
|
|
4465
|
+
}, this.viewSubject(ctx));
|
|
4466
|
+
return this.#readableItems(result, ctx);
|
|
4061
4467
|
},
|
|
4062
4468
|
documentOutgoingRelationships: async (_, args, ctx) => {
|
|
4063
4469
|
const { relationshipType, view, paging } = args;
|
|
@@ -4067,23 +4473,24 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4067
4473
|
relationshipType,
|
|
4068
4474
|
view,
|
|
4069
4475
|
paging
|
|
4070
|
-
});
|
|
4071
|
-
const
|
|
4476
|
+
}, this.viewSubject(ctx));
|
|
4477
|
+
const readable = await this.#readableItems(result, ctx);
|
|
4478
|
+
const filteredItems = readable.items.filter((item) => item.documentType === documentType);
|
|
4072
4479
|
return {
|
|
4073
|
-
...
|
|
4074
|
-
items: filteredItems
|
|
4075
|
-
totalCount: filteredItems.length
|
|
4480
|
+
...readable,
|
|
4481
|
+
items: filteredItems
|
|
4076
4482
|
};
|
|
4077
4483
|
},
|
|
4078
4484
|
documentIncomingRelationships: async (_, args, ctx) => {
|
|
4079
4485
|
const { relationshipType, view, paging } = args;
|
|
4080
4486
|
const handle = await this.assertCanRead(args.targetIdentifier, ctx);
|
|
4081
|
-
|
|
4487
|
+
const result = await documentIncomingRelationships(this.reactorClient, {
|
|
4082
4488
|
targetIdentifier: handle.fetchIdentifier,
|
|
4083
4489
|
relationshipType,
|
|
4084
4490
|
view,
|
|
4085
4491
|
paging
|
|
4086
|
-
});
|
|
4492
|
+
}, this.viewSubject(ctx));
|
|
4493
|
+
return this.#readableItems(result, ctx);
|
|
4087
4494
|
}
|
|
4088
4495
|
},
|
|
4089
4496
|
Mutation: { [documentName]: () => ({}) },
|
|
@@ -4101,14 +4508,14 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4101
4508
|
slug,
|
|
4102
4509
|
preferredEditor,
|
|
4103
4510
|
initialState: initialState ?? {}
|
|
4104
|
-
}, this.graphqlManager.reactorDriveClient);
|
|
4511
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
4105
4512
|
else createdDoc = await createEmptyDocument(this.reactorClient, {
|
|
4106
4513
|
documentType,
|
|
4107
4514
|
parentIdentifier,
|
|
4108
4515
|
name
|
|
4109
|
-
}, this.graphqlManager.reactorDriveClient);
|
|
4516
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
4110
4517
|
if (ctx.user?.address && createdDoc?.id) await this.documentPermissionService?.initializeDocumentProtection(createdDoc.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4111
|
-
if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)]));
|
|
4518
|
+
if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)], void 0, this.viewSubject(ctx)));
|
|
4112
4519
|
return createdDoc;
|
|
4113
4520
|
},
|
|
4114
4521
|
createEmptyDocument: async (_, args, ctx) => {
|
|
@@ -4118,7 +4525,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4118
4525
|
const result = await createEmptyDocument(this.reactorClient, {
|
|
4119
4526
|
documentType,
|
|
4120
4527
|
parentIdentifier
|
|
4121
|
-
}, this.graphqlManager.reactorDriveClient);
|
|
4528
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
4122
4529
|
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4123
4530
|
return result;
|
|
4124
4531
|
},
|
|
@@ -4126,11 +4533,11 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4126
4533
|
mutations[camelCase(op.name)] = async (_, args, ctx) => {
|
|
4127
4534
|
const { docId, input } = args;
|
|
4128
4535
|
const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
|
|
4129
|
-
if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
4536
|
+
if ((await this.reactorClient.get(effectiveDocId, { subject: this.viewSubject(ctx) })).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
4130
4537
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
4131
4538
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
4132
4539
|
try {
|
|
4133
|
-
return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)]));
|
|
4540
|
+
return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)], void 0, this.viewSubject(ctx)));
|
|
4134
4541
|
} catch (error) {
|
|
4135
4542
|
throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
|
|
4136
4543
|
}
|
|
@@ -4138,7 +4545,7 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4138
4545
|
mutations[`${camelCase(op.name)}Async`] = async (_, args, ctx) => {
|
|
4139
4546
|
const { docId, input } = args;
|
|
4140
4547
|
const effectiveDocId = (await this.assertCanExecuteOperation(docId, op.name, ctx)).fetchIdentifier;
|
|
4141
|
-
if ((await this.reactorClient.get(effectiveDocId)).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
4548
|
+
if ((await this.reactorClient.get(effectiveDocId, { subject: this.viewSubject(ctx) })).header.documentType !== documentType) throw new GraphQLError(`Document with id ${docId} is not of type ${documentType}`);
|
|
4142
4549
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
4143
4550
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
4144
4551
|
try {
|
|
@@ -4162,7 +4569,7 @@ const DRIVE_ID_HEADER = "drive-id";
|
|
|
4162
4569
|
* in the ownership set yet); other create-shaped operations that may
|
|
4163
4570
|
* synthesize a drive must be added here too.
|
|
4164
4571
|
*/
|
|
4165
|
-
const CACHE_BYPASS_OPERATIONS = new Set(["createDocument", "createEmptyDocument"]);
|
|
4572
|
+
const CACHE_BYPASS_OPERATIONS = /* @__PURE__ */ new Set(["createDocument", "createEmptyDocument"]);
|
|
4166
4573
|
const driveIdMap = /* @__PURE__ */ new WeakMap();
|
|
4167
4574
|
/** Internal — only `graphql-manager.ts` should call this. */
|
|
4168
4575
|
function getRequestDriveId(request) {
|
|
@@ -4232,6 +4639,7 @@ function wrongShardResponse(driveId) {
|
|
|
4232
4639
|
* 421 response.
|
|
4233
4640
|
*/
|
|
4234
4641
|
var DriveOwnershipCache = class {
|
|
4642
|
+
reactorClient;
|
|
4235
4643
|
drives = /* @__PURE__ */ new Set();
|
|
4236
4644
|
constructor(reactorClient) {
|
|
4237
4645
|
this.reactorClient = reactorClient;
|
|
@@ -4349,6 +4757,22 @@ function createWsAuthHandlers(opts) {
|
|
|
4349
4757
|
};
|
|
4350
4758
|
}
|
|
4351
4759
|
var GraphQLManager = class {
|
|
4760
|
+
path;
|
|
4761
|
+
httpServer;
|
|
4762
|
+
wsServer;
|
|
4763
|
+
reactorClient;
|
|
4764
|
+
relationalDb;
|
|
4765
|
+
analyticsStore;
|
|
4766
|
+
syncManager;
|
|
4767
|
+
logger;
|
|
4768
|
+
httpAdapter;
|
|
4769
|
+
gatewayAdapter;
|
|
4770
|
+
authService;
|
|
4771
|
+
documentPermissionService;
|
|
4772
|
+
featureFlags;
|
|
4773
|
+
port;
|
|
4774
|
+
syncServingGate;
|
|
4775
|
+
httpRoutes;
|
|
4352
4776
|
initialized = false;
|
|
4353
4777
|
coreSubgraphsMap = /* @__PURE__ */ new Map();
|
|
4354
4778
|
contextFields = {};
|
|
@@ -4363,8 +4787,8 @@ var GraphQLManager = class {
|
|
|
4363
4787
|
subgraphHandlerCache = /* @__PURE__ */ new Map();
|
|
4364
4788
|
/** subgraphPath → the http adapter handle of its mounted route. */
|
|
4365
4789
|
subgraphRouteHandles = /* @__PURE__ */ new Map();
|
|
4366
|
-
/**
|
|
4367
|
-
|
|
4790
|
+
/** Mounted SSE routes, keyed by path: the supergraph's and each subgraph's. */
|
|
4791
|
+
sseRouteHandles = /* @__PURE__ */ new Map();
|
|
4368
4792
|
/**
|
|
4369
4793
|
* Package name → the subgraph instances registered from it, keyed by
|
|
4370
4794
|
* subgraph name. Tracks the package association so that removed packages
|
|
@@ -4425,19 +4849,21 @@ var GraphQLManager = class {
|
|
|
4425
4849
|
const matched = driveMatcher(url.pathname);
|
|
4426
4850
|
const driveIdOrSlug = matched ? matched.params.drive : void 0;
|
|
4427
4851
|
if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
|
|
4852
|
+
const user = getAuthContext(request)?.user;
|
|
4428
4853
|
try {
|
|
4429
|
-
const driveDoc = await this.reactorClient.get(driveIdOrSlug);
|
|
4430
|
-
if (!await this.authorizationService.canRead(driveDoc.header.id,
|
|
4854
|
+
const driveDoc = await this.reactorClient.get(driveIdOrSlug, { subject: callerSubject(user) });
|
|
4855
|
+
if (!await this.authorizationService.canRead(driveDoc.header.id, user?.address)) {
|
|
4431
4856
|
this.logger.debug(`Drive read refused: ${driveIdOrSlug}`);
|
|
4432
4857
|
return Response.json({ error: "Drive not found" }, { status: 404 });
|
|
4433
4858
|
}
|
|
4434
4859
|
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`;
|
|
4860
|
+
const global = driveDoc.state.global;
|
|
4435
4861
|
return Response.json({
|
|
4436
4862
|
id: driveDoc.header.id,
|
|
4437
4863
|
slug: driveDoc.header.slug,
|
|
4438
4864
|
meta: driveDoc.header.meta,
|
|
4439
|
-
name:
|
|
4440
|
-
icon:
|
|
4865
|
+
name: global?.name || driveDoc.header.name,
|
|
4866
|
+
icon: global?.icon ?? void 0,
|
|
4441
4867
|
...graphqlEndpoint && { graphqlEndpoint }
|
|
4442
4868
|
});
|
|
4443
4869
|
} catch (error) {
|
|
@@ -4562,6 +4988,7 @@ var GraphQLManager = class {
|
|
|
4562
4988
|
routeHandle.dispose();
|
|
4563
4989
|
this.subgraphRouteHandles.delete(subgraphPath);
|
|
4564
4990
|
}
|
|
4991
|
+
this.#disposeSSERoute(subgraphPath);
|
|
4565
4992
|
const wsDisposer = this.subgraphWsDisposers.get(subgraphPath);
|
|
4566
4993
|
if (wsDisposer) {
|
|
4567
4994
|
this.subgraphWsDisposers.delete(subgraphPath);
|
|
@@ -4901,10 +5328,7 @@ var GraphQLManager = class {
|
|
|
4901
5328
|
const allSubgraphs = this.#getAllSubgraphs();
|
|
4902
5329
|
const modules = Array.from(allSubgraphs.values()).filter((subgraph) => subgraph.hasSubscriptions).map((subgraph) => this.#buildSubgraphSchemaModule(subgraph));
|
|
4903
5330
|
if (modules.length === 0) {
|
|
4904
|
-
|
|
4905
|
-
this.sseRouteHandle.dispose();
|
|
4906
|
-
this.sseRouteHandle = void 0;
|
|
4907
|
-
}
|
|
5331
|
+
this.#disposeSSERoute(superGraphPath);
|
|
4908
5332
|
return;
|
|
4909
5333
|
}
|
|
4910
5334
|
try {
|
|
@@ -4930,8 +5354,16 @@ var GraphQLManager = class {
|
|
|
4930
5354
|
contextFactory: this.#makeContextFactory()
|
|
4931
5355
|
});
|
|
4932
5356
|
const handler = this.#composeFetchMiddleware(rawHandler);
|
|
4933
|
-
|
|
4934
|
-
this.
|
|
5357
|
+
this.#disposeSSERoute(basePath);
|
|
5358
|
+
this.sseRouteHandles.set(ssePath, this.httpAdapter.mount(ssePath, handler, { exact: true }));
|
|
5359
|
+
}
|
|
5360
|
+
#disposeSSERoute(basePath) {
|
|
5361
|
+
const ssePath = basePath + "/stream";
|
|
5362
|
+
const handle = this.sseRouteHandles.get(ssePath);
|
|
5363
|
+
if (handle !== void 0) {
|
|
5364
|
+
handle.dispose();
|
|
5365
|
+
this.sseRouteHandles.delete(ssePath);
|
|
5366
|
+
}
|
|
4935
5367
|
}
|
|
4936
5368
|
/**
|
|
4937
5369
|
* Compose the request-level fetch middleware chain. Auth runs first
|
|
@@ -5215,7 +5647,6 @@ const DocumentModelStateDTO = z.object({
|
|
|
5215
5647
|
}).strip();
|
|
5216
5648
|
const DocumentModelResultPageDTO = z.object({
|
|
5217
5649
|
items: z.array(DocumentModelStateDTO),
|
|
5218
|
-
totalCount: z.number().int(),
|
|
5219
5650
|
hasNextPage: z.boolean(),
|
|
5220
5651
|
hasPreviousPage: z.boolean(),
|
|
5221
5652
|
cursor: z.string().nullable().optional()
|
|
@@ -5236,7 +5667,20 @@ const PHDocumentDTO = z.object({
|
|
|
5236
5667
|
}).strip();
|
|
5237
5668
|
const PHDocumentResultPageDTO = z.object({
|
|
5238
5669
|
items: z.array(PHDocumentDTO),
|
|
5239
|
-
|
|
5670
|
+
hasNextPage: z.boolean(),
|
|
5671
|
+
hasPreviousPage: z.boolean(),
|
|
5672
|
+
cursor: z.string().nullable().optional()
|
|
5673
|
+
}).strip();
|
|
5674
|
+
const DocumentRelationshipDTO = z.object({
|
|
5675
|
+
sourceId: z.string(),
|
|
5676
|
+
targetId: z.string(),
|
|
5677
|
+
relationshipType: z.string(),
|
|
5678
|
+
metadata: JSONObjectDTO.nullable().optional(),
|
|
5679
|
+
createdAt: DateTimeDTO,
|
|
5680
|
+
updatedAt: DateTimeDTO
|
|
5681
|
+
}).strip();
|
|
5682
|
+
const DocumentRelationshipResultPageDTO = z.object({
|
|
5683
|
+
items: z.array(DocumentRelationshipDTO),
|
|
5240
5684
|
hasNextPage: z.boolean(),
|
|
5241
5685
|
hasPreviousPage: z.boolean(),
|
|
5242
5686
|
cursor: z.string().nullable().optional()
|
|
@@ -5291,7 +5735,6 @@ const OperationDTO = z.object({
|
|
|
5291
5735
|
}).strip();
|
|
5292
5736
|
const OperationResultPageDTO = z.object({
|
|
5293
5737
|
items: z.array(OperationDTO),
|
|
5294
|
-
totalCount: z.number().int(),
|
|
5295
5738
|
hasNextPage: z.boolean(),
|
|
5296
5739
|
hasPreviousPage: z.boolean(),
|
|
5297
5740
|
cursor: z.string().nullable().optional()
|
|
@@ -5344,6 +5787,12 @@ const operationValidators = {
|
|
|
5344
5787
|
GetDocumentIncomingRelationships: (data) => {
|
|
5345
5788
|
if (data.documentIncomingRelationships) PHDocumentResultPageDTO.parse(data.documentIncomingRelationships);
|
|
5346
5789
|
},
|
|
5790
|
+
GetDocumentOutgoingRelationshipEdges: (data) => {
|
|
5791
|
+
if (data.documentOutgoingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentOutgoingRelationshipEdges);
|
|
5792
|
+
},
|
|
5793
|
+
GetDocumentIncomingRelationshipEdges: (data) => {
|
|
5794
|
+
if (data.documentIncomingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentIncomingRelationshipEdges);
|
|
5795
|
+
},
|
|
5347
5796
|
FindDocuments: (data) => {
|
|
5348
5797
|
if (data.findDocuments) PHDocumentResultPageDTO.parse(data.findDocuments);
|
|
5349
5798
|
},
|
|
@@ -5377,6 +5826,9 @@ const operationValidators = {
|
|
|
5377
5826
|
AddRelationship: (data) => {
|
|
5378
5827
|
if (data.addRelationship) PHDocumentDTO.parse(data.addRelationship);
|
|
5379
5828
|
},
|
|
5829
|
+
UpdateRelationship: (data) => {
|
|
5830
|
+
if (data.updateRelationship) PHDocumentDTO.parse(data.updateRelationship);
|
|
5831
|
+
},
|
|
5380
5832
|
RemoveRelationship: (data) => {
|
|
5381
5833
|
if (data.removeRelationship) PHDocumentDTO.parse(data.removeRelationship);
|
|
5382
5834
|
},
|
|
@@ -5418,7 +5870,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
|
|
|
5418
5870
|
}
|
|
5419
5871
|
//#endregion
|
|
5420
5872
|
//#region src/graphql/reactor/schema.graphql
|
|
5421
|
-
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";
|
|
5873
|
+
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 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 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 hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n cursor: String\n}\n\ntype ReactorOperationResultPage {\n items: [ReactorOperation!]!\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 # The server's current manifest revision\n manifestRevision: String\n # The client's manifest revision as the server holds it\n peerManifestRevision: String\n}\n\ntype SyncHoldReason {\n protocol: String!\n version: Int!\n peerSupports: [Int!]!\n}\n\n# A document held back from a remote whose peer cannot run its versions\ntype SyncHold {\n remoteName: String!\n documentId: String!\n branch: String!\n reason: SyncHoldReason!\n heldAtUtcMs: String!\n}\n\ntype PeerAgreementMember {\n remoteName: String!\n # False for a peer that announced nothing: it gets the baselines\n announced: Boolean!\n protocols: JSONObject!\n features: JSONObject!\n}\n\ntype PeerAgreementLimit {\n protocol: String!\n # The remotes whose peers lack the version new documents would take\n remoteNames: [String!]!\n}\n\ntype PeerAgreement {\n collectionId: String!\n local: JSONObject!\n members: [PeerAgreementMember!]!\n limitedBy: [PeerAgreementLimit!]!\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 \"\"\"\n The index of the last operation in the scope the action applies to.\n \"\"\"\n prevOpIndex: Int\n\n \"\"\"\n The hash of the state the action applies to.\n \"\"\"\n prevOpHash: String\n\n \"\"\"\n Covers replay of an operation that would otherwise be a no-op.\n \"\"\"\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 SyncRefusalInput {\n documentId: String!\n branch: String!\n}\n\ninput TouchChannelInput {\n id: String!\n name: String!\n collectionId: String!\n filter: RemoteFilterInput!\n sinceTimestampUtcMs: String!\n # The client's peer manifest; absent from a client without peer agreement\n manifest: JSONObject\n}\n\ntype TouchChannelResult {\n success: Boolean!\n ackOrdinal: Int!\n # The server's peer manifest\n manifest: JSONObject\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 # The client's manifest revision; absent from a client without peer agreement\n manifestRevision: String\n # Documents the client refused as UNSUPPORTED_PROTOCOL from earlier polls\n refusals: [SyncRefusalInput!]\n ): PollSyncEnvelopesResult!\n\n # Supreme admin, or the address the named remote's channel is bound to\n syncHolds(remoteName: String, documentId: String): [SyncHold!]!\n\n # Supreme admin, or an address bound to a channel in the collection\n peerAgreement(collectionId: String!): PeerAgreement!\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(\n envelopes: [SyncEnvelopeInput!]!\n # The server's manifest revision the client gated these envelopes under\n peerManifestRevision: String\n ): 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";
|
|
5422
5874
|
//#endregion
|
|
5423
5875
|
//#region src/graphql/reactor/pubsub.ts
|
|
5424
5876
|
const pubSub = new PubSub();
|
|
@@ -5429,26 +5881,79 @@ const SUBSCRIPTION_TRIGGERS = {
|
|
|
5429
5881
|
DOCUMENT_CHANGES: "DOCUMENT_CHANGES",
|
|
5430
5882
|
JOB_CHANGES: "JOB_CHANGES"
|
|
5431
5883
|
};
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5884
|
+
function subjectKey(subject) {
|
|
5885
|
+
return `${subject.address ?? ""}|${subject.key ?? ""}`;
|
|
5886
|
+
}
|
|
5887
|
+
/**
|
|
5888
|
+
* The reactor's change feed, read once per distinct subject and shared by that
|
|
5889
|
+
* subject's GraphQL subscriptions. Each feed is read as its subject, so the
|
|
5890
|
+
* client withholds what that subject may not read; one feed read with no view
|
|
5891
|
+
* would gate every subscriber as the host.
|
|
5892
|
+
*/
|
|
5893
|
+
var DocumentChangeFeed = class {
|
|
5894
|
+
reactorClient;
|
|
5895
|
+
#pubSub = new PubSub();
|
|
5896
|
+
#feeds = /* @__PURE__ */ new Map();
|
|
5897
|
+
constructor(reactorClient) {
|
|
5898
|
+
this.reactorClient = reactorClient;
|
|
5899
|
+
}
|
|
5900
|
+
subscribe(subject) {
|
|
5901
|
+
const key = subjectKey(subject);
|
|
5902
|
+
const topic = `${SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES}:${key}`;
|
|
5903
|
+
const iterator = this.#pubSub.asyncIterableIterator(topic);
|
|
5904
|
+
this.#acquire(key, topic, subject);
|
|
5905
|
+
let released = false;
|
|
5906
|
+
const release = () => {
|
|
5907
|
+
if (!released) {
|
|
5908
|
+
released = true;
|
|
5909
|
+
this.#release(key);
|
|
5910
|
+
}
|
|
5440
5911
|
};
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5912
|
+
return {
|
|
5913
|
+
next: () => iterator.next(),
|
|
5914
|
+
return: () => {
|
|
5915
|
+
release();
|
|
5916
|
+
return iterator.return();
|
|
5917
|
+
},
|
|
5918
|
+
throw: (error) => {
|
|
5919
|
+
release();
|
|
5920
|
+
return iterator.throw(error);
|
|
5921
|
+
},
|
|
5922
|
+
[Symbol.asyncIterator]() {
|
|
5923
|
+
return this;
|
|
5924
|
+
}
|
|
5925
|
+
};
|
|
5926
|
+
}
|
|
5927
|
+
#acquire(key, topic, subject) {
|
|
5928
|
+
const feed = this.#feeds.get(key);
|
|
5929
|
+
if (feed) {
|
|
5930
|
+
feed.subscribers++;
|
|
5931
|
+
return;
|
|
5449
5932
|
}
|
|
5450
|
-
|
|
5451
|
-
}
|
|
5933
|
+
const view = { subject };
|
|
5934
|
+
const unsubscribe = this.reactorClient.subscribe({}, (event) => {
|
|
5935
|
+
const payload = {
|
|
5936
|
+
documentChanges: event,
|
|
5937
|
+
search: {},
|
|
5938
|
+
view
|
|
5939
|
+
};
|
|
5940
|
+
this.#pubSub.publish(topic, payload);
|
|
5941
|
+
}, view);
|
|
5942
|
+
this.#feeds.set(key, {
|
|
5943
|
+
unsubscribe,
|
|
5944
|
+
subscribers: 1
|
|
5945
|
+
});
|
|
5946
|
+
}
|
|
5947
|
+
#release(key) {
|
|
5948
|
+
const feed = this.#feeds.get(key);
|
|
5949
|
+
if (!feed) return;
|
|
5950
|
+
feed.subscribers--;
|
|
5951
|
+
if (feed.subscribers === 0) {
|
|
5952
|
+
this.#feeds.delete(key);
|
|
5953
|
+
feed.unsubscribe();
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5956
|
+
};
|
|
5452
5957
|
const activeJobSubscriptions = /* @__PURE__ */ new Map();
|
|
5453
5958
|
function ensureJobSubscription(reactorClient, jobId) {
|
|
5454
5959
|
let subscription = activeJobSubscriptions.get(jobId);
|
|
@@ -5465,7 +5970,7 @@ function ensureJobSubscription(reactorClient, jobId) {
|
|
|
5465
5970
|
createdAt: jobInfo.createdAtUtcIso,
|
|
5466
5971
|
completedAt: jobInfo.completedAtUtcIso ?? null,
|
|
5467
5972
|
error: jobInfo.error?.message ?? null,
|
|
5468
|
-
result: jobInfo.result ??
|
|
5973
|
+
result: jobInfo.result ?? null
|
|
5469
5974
|
},
|
|
5470
5975
|
jobId,
|
|
5471
5976
|
documentId: jobInfo.documentId
|
|
@@ -5513,6 +6018,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5513
6018
|
}
|
|
5514
6019
|
name = "r";
|
|
5515
6020
|
hasSubscriptions = true;
|
|
6021
|
+
#documentChanges;
|
|
6022
|
+
get documentChanges() {
|
|
6023
|
+
this.#documentChanges ??= new DocumentChangeFeed(this.reactorClient);
|
|
6024
|
+
return this.#documentChanges;
|
|
6025
|
+
}
|
|
5516
6026
|
/**
|
|
5517
6027
|
* Check operation-level permissions for an array of actions.
|
|
5518
6028
|
* Delegates to base assertCanExecuteOperation for each action.
|
|
@@ -5543,6 +6053,38 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5543
6053
|
}
|
|
5544
6054
|
}
|
|
5545
6055
|
/**
|
|
6056
|
+
* Drops the relationship edges whose far-end document the caller cannot read.
|
|
6057
|
+
*
|
|
6058
|
+
* An edge names two documents, so returning it discloses the far end's id. The
|
|
6059
|
+
* document-shaped relationship queries already withhold far-end documents the
|
|
6060
|
+
* caller cannot read; the edge-shaped queries have to withhold the same ones,
|
|
6061
|
+
* or they become a way to enumerate around that check. Supreme admins read
|
|
6062
|
+
* everything, so the per-edge check is skipped for them.
|
|
6063
|
+
*
|
|
6064
|
+
* The cursor describes where to resume in the underlying unfiltered stream,
|
|
6065
|
+
* so it is left as it came. A filtered page is therefore often shorter than
|
|
6066
|
+
* the requested limit, and that is the stream continuing rather than a sign
|
|
6067
|
+
* it ended.
|
|
6068
|
+
*/
|
|
6069
|
+
async filterRelationshipEdges(page, farEnd, ctx) {
|
|
6070
|
+
if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
|
|
6071
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
6072
|
+
const items = [];
|
|
6073
|
+
for (const edge of page.items) {
|
|
6074
|
+
const documentId = edge[farEnd];
|
|
6075
|
+
let canRead = decisions.get(documentId);
|
|
6076
|
+
if (canRead === void 0) {
|
|
6077
|
+
canRead = await this.canReadDocument(documentId, ctx);
|
|
6078
|
+
decisions.set(documentId, canRead);
|
|
6079
|
+
}
|
|
6080
|
+
if (canRead) items.push(edge);
|
|
6081
|
+
}
|
|
6082
|
+
return {
|
|
6083
|
+
...page,
|
|
6084
|
+
items
|
|
6085
|
+
};
|
|
6086
|
+
}
|
|
6087
|
+
/**
|
|
5546
6088
|
* Adds to `forbidden` the canonical document ids in `syncOps` that the caller
|
|
5547
6089
|
* cannot read, checking each distinct id once. Sync operation document ids are
|
|
5548
6090
|
* canonical (never slugs), so no resolution is needed.
|
|
@@ -5575,9 +6117,9 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5575
6117
|
* reports the liveness that keeps it from being reclaimed -- but they never
|
|
5576
6118
|
* adopt, since the push path has no drive check to clear the claimant.
|
|
5577
6119
|
*
|
|
5578
|
-
* Nothing is enforced or adopted without a serving gate
|
|
5579
|
-
*
|
|
5580
|
-
* belong to
|
|
6120
|
+
* Nothing is enforced or adopted without a serving gate: with no gate the
|
|
6121
|
+
* channel serves every subject alike, so there is no subject for it to
|
|
6122
|
+
* belong to.
|
|
5581
6123
|
*/
|
|
5582
6124
|
async #bindOrRefuseChannel(channelId, ctx, options = {}) {
|
|
5583
6125
|
if (!this.syncServingGate) return;
|
|
@@ -5682,6 +6224,34 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5682
6224
|
throw error;
|
|
5683
6225
|
}
|
|
5684
6226
|
},
|
|
6227
|
+
documentOutgoingRelationshipEdges: async (_parent, args, ctx) => {
|
|
6228
|
+
this.logger.debug("documentOutgoingRelationshipEdges(@args)", args);
|
|
6229
|
+
try {
|
|
6230
|
+
const handle = await this.assertCanRead(args.sourceIdentifier, ctx);
|
|
6231
|
+
const result = await documentOutgoingRelationshipEdges(this.reactorClient, {
|
|
6232
|
+
...args,
|
|
6233
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
6234
|
+
}, this.viewSubject(ctx));
|
|
6235
|
+
return await this.filterRelationshipEdges(result, "targetId", ctx);
|
|
6236
|
+
} catch (error) {
|
|
6237
|
+
this.logger.error("Error in documentOutgoingRelationshipEdges: @Error", error);
|
|
6238
|
+
throw error;
|
|
6239
|
+
}
|
|
6240
|
+
},
|
|
6241
|
+
documentIncomingRelationshipEdges: async (_parent, args, ctx) => {
|
|
6242
|
+
this.logger.debug("documentIncomingRelationshipEdges(@args)", args);
|
|
6243
|
+
try {
|
|
6244
|
+
const handle = await this.assertCanRead(args.targetIdentifier, ctx);
|
|
6245
|
+
const result = await documentIncomingRelationshipEdges(this.reactorClient, {
|
|
6246
|
+
...args,
|
|
6247
|
+
targetIdentifier: handle.fetchIdentifier
|
|
6248
|
+
}, this.viewSubject(ctx));
|
|
6249
|
+
return await this.filterRelationshipEdges(result, "sourceId", ctx);
|
|
6250
|
+
} catch (error) {
|
|
6251
|
+
this.logger.error("Error in documentIncomingRelationshipEdges: @Error", error);
|
|
6252
|
+
throw error;
|
|
6253
|
+
}
|
|
6254
|
+
},
|
|
5685
6255
|
findDocuments: async (_parent, args, ctx) => {
|
|
5686
6256
|
this.logger.debug("findDocuments(@args)", args);
|
|
5687
6257
|
try {
|
|
@@ -5703,10 +6273,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5703
6273
|
throw error;
|
|
5704
6274
|
}
|
|
5705
6275
|
},
|
|
5706
|
-
jobStatus: async (_parent, args) => {
|
|
6276
|
+
jobStatus: async (_parent, args, ctx) => {
|
|
5707
6277
|
this.logger.debug("jobStatus(@args)", args);
|
|
5708
6278
|
try {
|
|
5709
|
-
return await jobStatus(this.reactorClient, args);
|
|
6279
|
+
return await jobStatus(this.reactorClient, args, (id) => this.servesDocument(id, ctx));
|
|
5710
6280
|
} catch (error) {
|
|
5711
6281
|
this.logger.error("Error in jobStatus: @Error", error);
|
|
5712
6282
|
throw error;
|
|
@@ -5728,6 +6298,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5728
6298
|
throw error;
|
|
5729
6299
|
}
|
|
5730
6300
|
},
|
|
6301
|
+
/**
|
|
6302
|
+
* Gated on reading the document, the same gate every other read here
|
|
6303
|
+
* carries: a verdict is derived from the document's policy, so a caller
|
|
6304
|
+
* who may not read the document may not preflight against it either.
|
|
6305
|
+
* That gate decides whether the query runs; the answer comes entirely
|
|
6306
|
+
* from the reactor's decision model.
|
|
6307
|
+
*/
|
|
5731
6308
|
evaluateActions: async (_parent, args, ctx) => {
|
|
5732
6309
|
this.logger.debug("evaluateActions(@args)", args);
|
|
5733
6310
|
try {
|
|
@@ -5741,6 +6318,24 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5741
6318
|
throw error;
|
|
5742
6319
|
}
|
|
5743
6320
|
},
|
|
6321
|
+
syncHolds: async (_parent, args, ctx) => {
|
|
6322
|
+
const address = ctx.user?.address;
|
|
6323
|
+
if (!this.authorizationService.isSupremeAdmin(address)) {
|
|
6324
|
+
let bound;
|
|
6325
|
+
try {
|
|
6326
|
+
bound = args.remoteName ? this.syncManager.getByName(args.remoteName).meta.options.boundAddress : void 0;
|
|
6327
|
+
} catch {
|
|
6328
|
+
bound = void 0;
|
|
6329
|
+
}
|
|
6330
|
+
if (address === void 0 || bound !== address) throw new ForbiddenError("to list sync holds");
|
|
6331
|
+
}
|
|
6332
|
+
return syncHolds(this.syncManager, args);
|
|
6333
|
+
},
|
|
6334
|
+
peerAgreement: (_parent, args, ctx) => {
|
|
6335
|
+
const address = ctx.user?.address;
|
|
6336
|
+
if (!this.authorizationService.isSupremeAdmin(address) && (address === void 0 || !this.syncManager.list().some((remote) => remote.meta.collectionId.key === args.collectionId && remote.meta.options.boundAddress === address))) throw new ForbiddenError("to read peer agreement");
|
|
6337
|
+
return peerAgreement(this.syncManager, args);
|
|
6338
|
+
},
|
|
5744
6339
|
pollSyncEnvelopes: async (_parent, args, ctx) => {
|
|
5745
6340
|
this.logger.debug("pollSyncEnvelopes(@args)", args);
|
|
5746
6341
|
try {
|
|
@@ -5754,19 +6349,17 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5754
6349
|
const isAdmin = this.authorizationService.isSupremeAdmin(ctx.user?.address);
|
|
5755
6350
|
if (!isAdmin) await this.assertCanReadCanonical(driveId, ctx);
|
|
5756
6351
|
await this.#bindOrRefuseChannel(args.channelId, ctx);
|
|
6352
|
+
holdPollRefusals(this.syncManager, args.channelId, args.refusals);
|
|
6353
|
+
await silenceUnversionedPoll(this.syncManager, args.channelId, args.manifestRevision);
|
|
5757
6354
|
const forbiddenIds = /* @__PURE__ */ new Set();
|
|
5758
6355
|
if (!isAdmin) {
|
|
5759
6356
|
if (await this.authorizationService.canRead(driveId, void 0)) await this.#collectForbiddenDocuments(remote.channel.outbox.items, forbiddenIds, ctx);
|
|
5760
6357
|
await this.#collectForbiddenDocuments(remote.channel.deadLetter.items, forbiddenIds, ctx);
|
|
5761
6358
|
}
|
|
5762
|
-
const
|
|
5763
|
-
const
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
ackOrdinal,
|
|
5767
|
-
deadLetters,
|
|
5768
|
-
hasMore
|
|
5769
|
-
};
|
|
6359
|
+
const gate = this.syncServingGate;
|
|
6360
|
+
const gated = gate ? [...remote.channel.outbox.items, ...remote.channel.deadLetter.items] : void 0;
|
|
6361
|
+
const heldOpIds = gate && gated ? await collectHeldSyncOperations(gated, gate, this.viewSubject(ctx)) : /* @__PURE__ */ new Set();
|
|
6362
|
+
return pollSyncEnvelopes(this.syncManager, args, forbiddenIds, heldOpIds, gated && new Set(gated.map((syncOp) => syncOp.id)));
|
|
5770
6363
|
} catch (error) {
|
|
5771
6364
|
this.logger.error("Error in pollSyncEnvelopes(@args): @Error", args, error);
|
|
5772
6365
|
throw error;
|
|
@@ -5781,7 +6374,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5781
6374
|
const parent = await document(this.reactorClient, { identifier: args.parentIdentifier });
|
|
5782
6375
|
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
5783
6376
|
} else this.assertCanCreate(ctx);
|
|
5784
|
-
const result = await createDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
|
|
6377
|
+
const result = await createDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
5785
6378
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
5786
6379
|
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
5787
6380
|
return result;
|
|
@@ -5797,7 +6390,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5797
6390
|
const parent = await document(this.reactorClient, { identifier: args.parentIdentifier });
|
|
5798
6391
|
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
5799
6392
|
} else this.assertCanCreate(ctx);
|
|
5800
|
-
const result = await createEmptyDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient);
|
|
6393
|
+
const result = await createEmptyDocument(this.reactorClient, args, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
5801
6394
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
5802
6395
|
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
5803
6396
|
return result;
|
|
@@ -5813,7 +6406,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5813
6406
|
return await execute(this.reactorClient, {
|
|
5814
6407
|
...args,
|
|
5815
6408
|
documentIdentifier: handle.fetchIdentifier
|
|
5816
|
-
});
|
|
6409
|
+
}, this.viewSubject(ctx));
|
|
5817
6410
|
} catch (error) {
|
|
5818
6411
|
this.logger.error("Error in execute(@args): @Error", args, error);
|
|
5819
6412
|
throw error;
|
|
@@ -5839,7 +6432,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5839
6432
|
return await mutateDocument(this.reactorClient, {
|
|
5840
6433
|
...args,
|
|
5841
6434
|
documentIdentifier: handle.fetchIdentifier
|
|
5842
|
-
});
|
|
6435
|
+
}, this.viewSubject(ctx));
|
|
5843
6436
|
} catch (error) {
|
|
5844
6437
|
this.logger.error("Error in mutateDocument(@args): @Error", args, error);
|
|
5845
6438
|
throw error;
|
|
@@ -5865,7 +6458,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5865
6458
|
return await renameDocument(this.reactorClient, {
|
|
5866
6459
|
...args,
|
|
5867
6460
|
documentIdentifier: handle.fetchIdentifier
|
|
5868
|
-
});
|
|
6461
|
+
}, void 0, this.viewSubject(ctx));
|
|
5869
6462
|
} catch (error) {
|
|
5870
6463
|
this.logger.error("Error in renameDocument(@args): @Error", args, error);
|
|
5871
6464
|
throw error;
|
|
@@ -5878,7 +6471,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5878
6471
|
return await setPreferredEditor(this.reactorClient, {
|
|
5879
6472
|
...args,
|
|
5880
6473
|
documentIdentifier: handle.fetchIdentifier
|
|
5881
|
-
});
|
|
6474
|
+
}, void 0, this.viewSubject(ctx));
|
|
5882
6475
|
} catch (error) {
|
|
5883
6476
|
this.logger.error("Error in setPreferredEditor(@args): @Error", args, error);
|
|
5884
6477
|
throw error;
|
|
@@ -5891,12 +6484,25 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5891
6484
|
return await addRelationship(this.reactorClient, {
|
|
5892
6485
|
...args,
|
|
5893
6486
|
sourceIdentifier: handle.fetchIdentifier
|
|
5894
|
-
});
|
|
6487
|
+
}, this.viewSubject(ctx));
|
|
5895
6488
|
} catch (error) {
|
|
5896
6489
|
this.logger.error("Error in addRelationship(@args): @Error", args, error);
|
|
5897
6490
|
throw error;
|
|
5898
6491
|
}
|
|
5899
6492
|
},
|
|
6493
|
+
updateRelationship: async (_parent, args, ctx) => {
|
|
6494
|
+
this.logger.debug("updateRelationship(@args)", args);
|
|
6495
|
+
try {
|
|
6496
|
+
const handle = await this.assertCanWrite(args.sourceIdentifier, ctx);
|
|
6497
|
+
return await updateRelationship(this.reactorClient, {
|
|
6498
|
+
...args,
|
|
6499
|
+
sourceIdentifier: handle.fetchIdentifier
|
|
6500
|
+
}, this.viewSubject(ctx));
|
|
6501
|
+
} catch (error) {
|
|
6502
|
+
this.logger.error("Error in updateRelationship(@args): @Error", args, error);
|
|
6503
|
+
throw error;
|
|
6504
|
+
}
|
|
6505
|
+
},
|
|
5900
6506
|
removeRelationship: async (_parent, args, ctx) => {
|
|
5901
6507
|
this.logger.debug("removeRelationship(@args)", args);
|
|
5902
6508
|
try {
|
|
@@ -5904,7 +6510,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5904
6510
|
return await removeRelationship(this.reactorClient, {
|
|
5905
6511
|
...args,
|
|
5906
6512
|
sourceIdentifier: handle.fetchIdentifier
|
|
5907
|
-
});
|
|
6513
|
+
}, this.viewSubject(ctx));
|
|
5908
6514
|
} catch (error) {
|
|
5909
6515
|
this.logger.error("Error in removeRelationship(@args): @Error", args, error);
|
|
5910
6516
|
throw error;
|
|
@@ -5919,7 +6525,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5919
6525
|
...args,
|
|
5920
6526
|
sourceParentIdentifier: sourceHandle.fetchIdentifier,
|
|
5921
6527
|
targetParentIdentifier: targetHandle.fetchIdentifier
|
|
5922
|
-
});
|
|
6528
|
+
}, this.viewSubject(ctx));
|
|
5923
6529
|
} catch (error) {
|
|
5924
6530
|
this.logger.error("Error in moveRelationship(@args): @Error @args", error, args);
|
|
5925
6531
|
throw error;
|
|
@@ -6030,10 +6636,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
6030
6636
|
documentChanges: {
|
|
6031
6637
|
subscribe: (rootValue, args, ctx, info) => {
|
|
6032
6638
|
this.logger.debug("documentChanges subscription started");
|
|
6033
|
-
return withFilter(() => {
|
|
6034
|
-
ensureGlobalDocumentSubscription(this.reactorClient);
|
|
6035
|
-
return getPubSub().asyncIterableIterator(SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES);
|
|
6036
|
-
}, async (payload, filterArgs, filterCtx) => {
|
|
6639
|
+
return withFilter(() => this.documentChanges.subscribe(this.viewSubject(ctx)), async (payload, filterArgs, filterCtx) => {
|
|
6037
6640
|
if (!payload) return false;
|
|
6038
6641
|
const search = {
|
|
6039
6642
|
type: filterArgs?.search?.type ?? void 0,
|
|
@@ -6060,6 +6663,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
6060
6663
|
}, async (payload, filterArgs, filterCtx) => {
|
|
6061
6664
|
if (!payload || !filterArgs) return false;
|
|
6062
6665
|
if (!matchesJobFilter(payload, filterArgs)) return false;
|
|
6666
|
+
if (!await this.servesDocument(payload.documentId, filterCtx)) return false;
|
|
6063
6667
|
if (this.authorizationService.isSupremeAdmin(filterCtx?.user?.address)) return true;
|
|
6064
6668
|
if (!payload.documentId) return false;
|
|
6065
6669
|
return this.canReadDocument(payload.documentId, filterCtx);
|
|
@@ -6088,10 +6692,10 @@ const ADMIN_USERS = getAdminUsers();
|
|
|
6088
6692
|
//#endregion
|
|
6089
6693
|
//#region src/graphql/system/version.ts
|
|
6090
6694
|
function getVersion() {
|
|
6091
|
-
return "6.2.3-
|
|
6695
|
+
return "6.2.3-staging.0";
|
|
6092
6696
|
}
|
|
6093
6697
|
function getGitHash() {
|
|
6094
|
-
return "
|
|
6698
|
+
return "f3313e8dc648158bd837a5151bf2cdc43afd9287";
|
|
6095
6699
|
}
|
|
6096
6700
|
function getGitUrl() {
|
|
6097
6701
|
return buildTreeUrl(getGitHash());
|
|
@@ -6120,6 +6724,10 @@ var SystemSubgraph = class extends BaseSubgraph {
|
|
|
6120
6724
|
};
|
|
6121
6725
|
//#endregion
|
|
6122
6726
|
//#region src/packages/http-loader.ts
|
|
6727
|
+
const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
6728
|
+
function piecesBaseUrl(registryUrl, packageName, version) {
|
|
6729
|
+
return `${registryUrl.endsWith("/") ? registryUrl : `${registryUrl}/`}-/cdn/${packageName}@${version}/node/pieces/`;
|
|
6730
|
+
}
|
|
6123
6731
|
/**
|
|
6124
6732
|
* Extract subgraph classes from the imported subgraphs/index.mjs module shape.
|
|
6125
6733
|
*
|
|
@@ -6192,7 +6800,8 @@ var HttpPackageLoader = class {
|
|
|
6192
6800
|
async loadUpgradeManifests(packageSpec) {
|
|
6193
6801
|
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
6194
6802
|
if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
|
|
6195
|
-
const
|
|
6803
|
+
const module = await import(`${this.registryUrl}-/cdn/${packageSpec}/node/document-models/index.mjs`);
|
|
6804
|
+
const manifests = extractUpgradeManifests(module);
|
|
6196
6805
|
if (manifests.length > 0) this.logger.verbose(`Loaded ${manifests.length} upgrade manifests from ${packageName}`);
|
|
6197
6806
|
return manifests;
|
|
6198
6807
|
}
|
|
@@ -6218,6 +6827,27 @@ var HttpPackageLoader = class {
|
|
|
6218
6827
|
this.logger.verbose(`No processor factory found in ${packageName}`);
|
|
6219
6828
|
return null;
|
|
6220
6829
|
}
|
|
6830
|
+
async loadPieces(packageSpec) {
|
|
6831
|
+
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
6832
|
+
if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
|
|
6833
|
+
const version = await this.packageVersion(packageSpec);
|
|
6834
|
+
if (!version) {
|
|
6835
|
+
this.logger.verbose(`No package version found for: ${packageSpec}`);
|
|
6836
|
+
return [];
|
|
6837
|
+
}
|
|
6838
|
+
const base = piecesBaseUrl(this.registryUrl, packageName, version);
|
|
6839
|
+
this.logger.verbose(`Importing pieces from: ${base}index.mjs`);
|
|
6840
|
+
let module;
|
|
6841
|
+
try {
|
|
6842
|
+
module = await import(`${base}index.mjs`);
|
|
6843
|
+
} catch (error) {
|
|
6844
|
+
this.logger.verbose(`No pieces found for: ${packageName}`, error);
|
|
6845
|
+
return [];
|
|
6846
|
+
}
|
|
6847
|
+
const pieces = piecesFromCdnList(module, base, packageName, this.logger, version);
|
|
6848
|
+
this.logger.verbose(`Loaded ${pieces.length} pieces from ${packageName}`);
|
|
6849
|
+
return pieces;
|
|
6850
|
+
}
|
|
6221
6851
|
/**
|
|
6222
6852
|
* Load document models from multiple packages.
|
|
6223
6853
|
* Continues loading even if some packages fail.
|
|
@@ -6241,6 +6871,17 @@ var HttpPackageLoader = class {
|
|
|
6241
6871
|
}
|
|
6242
6872
|
return allModels;
|
|
6243
6873
|
}
|
|
6874
|
+
async packageVersion(packageSpec) {
|
|
6875
|
+
try {
|
|
6876
|
+
const response = await fetch(`${this.registryUrl}-/cdn/${packageSpec}/package.json`);
|
|
6877
|
+
if (!response.ok) return void 0;
|
|
6878
|
+
const pkg = await response.json();
|
|
6879
|
+
return typeof pkg.version === "string" && EXACT_VERSION.test(pkg.version) ? pkg.version : void 0;
|
|
6880
|
+
} catch (error) {
|
|
6881
|
+
this.logger.verbose(`Could not read the version of ${packageSpec}`, error);
|
|
6882
|
+
return;
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6244
6885
|
isValidPackageName(name) {
|
|
6245
6886
|
return /^(@[a-z0-9][-a-z0-9._]*\/)?[a-z0-9][-a-z0-9._]*$/i.test(name) && !name.includes("..") && name.length <= 214;
|
|
6246
6887
|
}
|
|
@@ -6315,8 +6956,13 @@ var HttpDocumentModelLoader = class {
|
|
|
6315
6956
|
* This class is used to load packages using the import keyword.
|
|
6316
6957
|
*/
|
|
6317
6958
|
var ImportPackageLoader = class {
|
|
6959
|
+
hostDir;
|
|
6318
6960
|
logger = childLogger(["reactor-api", "import-loader"]);
|
|
6319
6961
|
name = "ImportPackageLoader";
|
|
6962
|
+
constructor(hostDir = process.cwd()) {
|
|
6963
|
+
this.hostDir = hostDir;
|
|
6964
|
+
}
|
|
6965
|
+
reportedUnbuilt = /* @__PURE__ */ new Set();
|
|
6320
6966
|
async loadDocumentModels(identifier) {
|
|
6321
6967
|
this.logger.verbose(`Loading document models from package: ${identifier}`);
|
|
6322
6968
|
const pkgModule = await loadDocumentModels(identifier);
|
|
@@ -6359,6 +7005,31 @@ var ImportPackageLoader = class {
|
|
|
6359
7005
|
this.logger.verbose(` ➜ No Processor Factory found: ${identifier}`);
|
|
6360
7006
|
return null;
|
|
6361
7007
|
}
|
|
7008
|
+
async loadPieces(identifier) {
|
|
7009
|
+
this.logger.verbose(`Loading pieces from package: ${identifier}`);
|
|
7010
|
+
const { root, listPath } = pieceListLocation(identifier, this.hostDir);
|
|
7011
|
+
if (!existsSync(listPath)) {
|
|
7012
|
+
this.reportUnbuilt(identifier, root, listPath);
|
|
7013
|
+
return [];
|
|
7014
|
+
}
|
|
7015
|
+
const module = await import(
|
|
7016
|
+
/* @vite-ignore */
|
|
7017
|
+
pathToFileURL(listPath).href
|
|
7018
|
+
);
|
|
7019
|
+
const pieces = piecesFromListModule(module, {
|
|
7020
|
+
root,
|
|
7021
|
+
identifier,
|
|
7022
|
+
logger: this.logger
|
|
7023
|
+
});
|
|
7024
|
+
this.logger.verbose(` ➜ Loaded ${pieces.length} Pieces from: ${identifier}`);
|
|
7025
|
+
return pieces;
|
|
7026
|
+
}
|
|
7027
|
+
reportUnbuilt(identifier, root, listPath) {
|
|
7028
|
+
if (!isAbsolute(identifier) || this.reportedUnbuilt.has(identifier)) return;
|
|
7029
|
+
if (!existsSync(join(root, PIECE_SOURCE_LIST))) return;
|
|
7030
|
+
this.reportedUnbuilt.add(identifier);
|
|
7031
|
+
this.logger.warn("@pkg declares pieces but @list was never built; run `ph build`", identifier, listPath);
|
|
7032
|
+
}
|
|
6362
7033
|
};
|
|
6363
7034
|
//#endregion
|
|
6364
7035
|
//#region src/packages/package-manager.ts
|
|
@@ -6374,10 +7045,12 @@ var ImportPackageLoader = class {
|
|
|
6374
7045
|
* either bare (`'pkg'`) or as a subpath (`'pkg/subgraphs'`), since loaders
|
|
6375
7046
|
* import sub-entries like `${pkg}/subgraphs` / `${pkg}/document-models`.
|
|
6376
7047
|
*/
|
|
6377
|
-
function isExpectedLoaderMiss(error, pkg) {
|
|
7048
|
+
function isExpectedLoaderMiss(error, pkg, subPath) {
|
|
6378
7049
|
if (!(error instanceof Error)) return false;
|
|
6379
7050
|
const code = error.code;
|
|
6380
7051
|
if (code === "ERR_UNSUPPORTED_DIR_IMPORT") return true;
|
|
7052
|
+
if (code === "ERR_PACKAGE_PATH_NOT_EXPORTED" && subPath !== void 0 && error.message.includes(`'./${subPath}'`)) return true;
|
|
7053
|
+
if (subPath !== void 0 && new RegExp(`^Failed to fetch \\S*/${subPath}/index\\.mjs: 404$`).test(error.message)) return true;
|
|
6381
7054
|
if (error.message.startsWith("Invalid package name:")) return true;
|
|
6382
7055
|
if (code === "ERR_MODULE_NOT_FOUND") return error.message.includes(`'${pkg}'`) || error.message.includes(`"${pkg}"`) || error.message.includes(`'${pkg}/`) || error.message.includes(`"${pkg}/`);
|
|
6383
7056
|
return false;
|
|
@@ -6393,12 +7066,14 @@ function getUniqueUpgradeManifests(...upgradeManifests) {
|
|
|
6393
7066
|
return Array.from(uniqueManifests.values());
|
|
6394
7067
|
}
|
|
6395
7068
|
var PackageManager = class {
|
|
7069
|
+
options;
|
|
6396
7070
|
logger = childLogger(["reactor-api", "package-manager"]);
|
|
6397
7071
|
loaders;
|
|
6398
7072
|
docModelsMap = /* @__PURE__ */ new Map();
|
|
6399
7073
|
upgradeManifestsMap = /* @__PURE__ */ new Map();
|
|
6400
7074
|
subgraphsMap = /* @__PURE__ */ new Map();
|
|
6401
7075
|
processorMap = /* @__PURE__ */ new Map();
|
|
7076
|
+
piecesMap = /* @__PURE__ */ new Map();
|
|
6402
7077
|
configWatcher;
|
|
6403
7078
|
debouncedUpdateCallbacks = /* @__PURE__ */ new Map();
|
|
6404
7079
|
eventEmitter = new EventEmitter();
|
|
@@ -6417,10 +7092,12 @@ var PackageManager = class {
|
|
|
6417
7092
|
const upgradeManifestsMap = await this.loadUpgradeManifests(packages);
|
|
6418
7093
|
const subgraphsMap = await this.loadSubgraphs(packages);
|
|
6419
7094
|
const processorsMap = await this.loadProcessors(packages);
|
|
7095
|
+
const piecesMap = await this.loadPieces(packages);
|
|
6420
7096
|
this.upgradeManifestsMap = upgradeManifestsMap;
|
|
6421
7097
|
this.updatePackagesMap(documentModelsMap);
|
|
6422
7098
|
this.updateSubgraphsMap(subgraphsMap);
|
|
6423
7099
|
this.updateProcessorsMap(processorsMap);
|
|
7100
|
+
this.updatePiecesMap(piecesMap);
|
|
6424
7101
|
try {
|
|
6425
7102
|
this.subscribePackages(packages);
|
|
6426
7103
|
} catch (error) {
|
|
@@ -6430,7 +7107,8 @@ var PackageManager = class {
|
|
|
6430
7107
|
documentModels: getUniqueDocumentModels(...Array.from(documentModelsMap.values())),
|
|
6431
7108
|
upgradeManifests: this.getUniqueUpgradeManifests(),
|
|
6432
7109
|
subgraphs: subgraphsMap,
|
|
6433
|
-
processors: processorsMap
|
|
7110
|
+
processors: processorsMap,
|
|
7111
|
+
pieces: piecesMap
|
|
6434
7112
|
};
|
|
6435
7113
|
}
|
|
6436
7114
|
async loadDocumentModels(packages) {
|
|
@@ -6530,9 +7208,34 @@ var PackageManager = class {
|
|
|
6530
7208
|
}
|
|
6531
7209
|
return processorsMap;
|
|
6532
7210
|
}
|
|
7211
|
+
async loadPieces(packages) {
|
|
7212
|
+
this.logger.debug(`Loading pieces from packages: ${packages.join(", ")}`);
|
|
7213
|
+
const piecesMap = /* @__PURE__ */ new Map();
|
|
7214
|
+
for (const pkg of packages) {
|
|
7215
|
+
const allPieces = [];
|
|
7216
|
+
const failures = [];
|
|
7217
|
+
let succeeded = false;
|
|
7218
|
+
for (const loader of this.loaders) try {
|
|
7219
|
+
allPieces.push(...await loader.loadPieces(pkg));
|
|
7220
|
+
succeeded = true;
|
|
7221
|
+
break;
|
|
7222
|
+
} catch (error) {
|
|
7223
|
+
failures.push({
|
|
7224
|
+
loader: loader.name,
|
|
7225
|
+
error
|
|
7226
|
+
});
|
|
7227
|
+
this.logger.debug(`[${loader.name}] Failed to load pieces from package ${pkg}`, error);
|
|
7228
|
+
}
|
|
7229
|
+
this.maybeWarnAllLoadersFailed("pieces", pkg, succeeded, failures);
|
|
7230
|
+
if (allPieces.length > 0) this.logger.info("Loaded @count piece(s) from package @pkg: @pieces", allPieces.length, pkg, allPieces.map((piece) => piece.name).join(", "));
|
|
7231
|
+
piecesMap.set(pkg, allPieces);
|
|
7232
|
+
}
|
|
7233
|
+
return piecesMap;
|
|
7234
|
+
}
|
|
6533
7235
|
maybeWarnAllLoadersFailed(kind, pkg, succeeded, failures) {
|
|
6534
7236
|
if (succeeded || failures.length === 0) return;
|
|
6535
|
-
const
|
|
7237
|
+
const subPath = kind === "document models" ? "document-models" : kind;
|
|
7238
|
+
const realFailures = failures.filter(({ error }) => !isExpectedLoaderMiss(error, pkg, subPath));
|
|
6536
7239
|
if (realFailures.length === 0) return;
|
|
6537
7240
|
const details = realFailures.map(({ loader, error }) => {
|
|
6538
7241
|
return ` [${loader}] ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -6572,6 +7275,15 @@ var PackageManager = class {
|
|
|
6572
7275
|
else processorsMap.set(pkg, pkgProcessors);
|
|
6573
7276
|
this.updateProcessorsMap(processorsMap);
|
|
6574
7277
|
}
|
|
7278
|
+
async updatePiecesForPackage(pkg) {
|
|
7279
|
+
this.logger.debug(`Updating pieces for package: ${pkg}`);
|
|
7280
|
+
const pieces = await this.loadPieces([pkg]);
|
|
7281
|
+
const piecesMap = new Map(this.piecesMap);
|
|
7282
|
+
const pkgPieces = pieces.get(pkg) ?? [];
|
|
7283
|
+
if (pkgPieces.length === 0) piecesMap.delete(pkg);
|
|
7284
|
+
else piecesMap.set(pkg, pkgPieces);
|
|
7285
|
+
this.updatePiecesMap(piecesMap);
|
|
7286
|
+
}
|
|
6575
7287
|
/** Debounced per key so repeated subscribePackages calls reuse one timer. */
|
|
6576
7288
|
getDebouncedUpdateCallback(key, update) {
|
|
6577
7289
|
if (!this.debouncedUpdateCallbacks.has(key)) {
|
|
@@ -6590,10 +7302,12 @@ var PackageManager = class {
|
|
|
6590
7302
|
const onDocumentModels = this.getDebouncedUpdateCallback(`${pkg}:document-models`, () => this.updateDocumentModelsForPackage(pkg));
|
|
6591
7303
|
const onSubgraphs = this.getDebouncedUpdateCallback(`${pkg}:subgraphs`, () => this.updateSubgraphsForPackage(pkg));
|
|
6592
7304
|
const onProcessors = this.getDebouncedUpdateCallback(`${pkg}:processors`, () => this.updateProcessorsForPackage(pkg));
|
|
7305
|
+
const onPieces = this.getDebouncedUpdateCallback(`${pkg}:pieces`, () => this.updatePiecesForPackage(pkg));
|
|
6593
7306
|
for (const loader of this.loaders) {
|
|
6594
7307
|
if (loader.onDocumentModelsChange) unsubs.push(loader.onDocumentModelsChange(pkg, onDocumentModels));
|
|
6595
7308
|
if (loader.onSubgraphsChange) unsubs.push(loader.onSubgraphsChange(pkg, onSubgraphs));
|
|
6596
7309
|
if (loader.onProcessorsChange) unsubs.push(loader.onProcessorsChange(pkg, onProcessors));
|
|
7310
|
+
if (loader.onPiecesChange) unsubs.push(loader.onPiecesChange(pkg, onPieces));
|
|
6597
7311
|
}
|
|
6598
7312
|
}
|
|
6599
7313
|
return () => {
|
|
@@ -6641,6 +7355,19 @@ var PackageManager = class {
|
|
|
6641
7355
|
this.processorMap = processorsMap;
|
|
6642
7356
|
this.eventEmitter.emit("processorsChange", processorsMap);
|
|
6643
7357
|
}
|
|
7358
|
+
updatePiecesMap(piecesMap) {
|
|
7359
|
+
const oldPackages = Array.from(this.piecesMap.keys());
|
|
7360
|
+
const newPackages = Array.from(piecesMap.keys());
|
|
7361
|
+
oldPackages.filter((pkg) => !newPackages.includes(pkg)).forEach((pkg) => {
|
|
7362
|
+
this.logger.info("Removed pieces from: @pkg", pkg);
|
|
7363
|
+
});
|
|
7364
|
+
this.piecesMap = piecesMap;
|
|
7365
|
+
this.eventEmitter.emit("piecesChange", piecesMap);
|
|
7366
|
+
}
|
|
7367
|
+
/** The pieces every loaded package ships, keyed by the package. */
|
|
7368
|
+
getPieces() {
|
|
7369
|
+
return new Map(this.piecesMap);
|
|
7370
|
+
}
|
|
6644
7371
|
/**
|
|
6645
7372
|
* Remove `pkg` from all package maps and emit the change events, tearing
|
|
6646
7373
|
* down everything the package registered (subgraphs, processors, document
|
|
@@ -6659,9 +7386,11 @@ var PackageManager = class {
|
|
|
6659
7386
|
this.upgradeManifestsMap = dropKey(this.upgradeManifestsMap);
|
|
6660
7387
|
this.subgraphsMap = dropKey(this.subgraphsMap);
|
|
6661
7388
|
this.processorMap = dropKey(this.processorMap);
|
|
7389
|
+
this.piecesMap = dropKey(this.piecesMap);
|
|
6662
7390
|
this.updatePackagesMap(this.docModelsMap);
|
|
6663
7391
|
this.updateSubgraphsMap(this.subgraphsMap);
|
|
6664
7392
|
this.updateProcessorsMap(this.processorMap);
|
|
7393
|
+
this.updatePiecesMap(this.piecesMap);
|
|
6665
7394
|
}
|
|
6666
7395
|
onDocumentModelsChange(handler) {
|
|
6667
7396
|
this.eventEmitter.on("documentModelsChange", handler);
|
|
@@ -6672,6 +7401,9 @@ var PackageManager = class {
|
|
|
6672
7401
|
onProcessorsChange(handler) {
|
|
6673
7402
|
this.eventEmitter.on("processorsChange", handler);
|
|
6674
7403
|
}
|
|
7404
|
+
onPiecesChange(handler) {
|
|
7405
|
+
this.eventEmitter.on("piecesChange", handler);
|
|
7406
|
+
}
|
|
6675
7407
|
};
|
|
6676
7408
|
//#endregion
|
|
6677
7409
|
//#region src/config.ts
|
|
@@ -6689,20 +7421,48 @@ async function createStartupAttachmentBackend({ db, env = process.env, s3Depende
|
|
|
6689
7421
|
}
|
|
6690
7422
|
//#endregion
|
|
6691
7423
|
//#region src/services/attachment-access.service.ts
|
|
7424
|
+
/** The scope an attachment's bytes belong to: the document's own domain state. */
|
|
7425
|
+
const ATTACHMENT_SCOPE = "global";
|
|
7426
|
+
/** The branch an attachment reference is resolved against. */
|
|
7427
|
+
const ATTACHMENT_BRANCH = "main";
|
|
6692
7428
|
const HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
6693
7429
|
/**
|
|
6694
7430
|
* Composes document authorization with the projected document/ref
|
|
6695
7431
|
* relationship. Order is fixed: validate ref, resolve the canonical document
|
|
6696
|
-
* id,
|
|
6697
|
-
*
|
|
6698
|
-
* attachment
|
|
7432
|
+
* id, decide the read, check the reactor's read gate serves the document, then
|
|
7433
|
+
* check the reference index and that the subject may read a scope whose
|
|
7434
|
+
* operations reference the attachment. A denied document never reaches the
|
|
7435
|
+
* reference reader, and the facade never touches attachment metadata, storage
|
|
7436
|
+
* backends, or presigners.
|
|
7437
|
+
*
|
|
7438
|
+
* The named document decides alone: an attachment several documents reference
|
|
7439
|
+
* is readable through each of them on that document's own terms.
|
|
7440
|
+
*
|
|
7441
|
+
* The read is decided by the document's own policy when this composition has a
|
|
7442
|
+
* model to enforce it with, and by the host's permission tables when it does
|
|
7443
|
+
* not. That is not a preference between two equivalent answers: a host running
|
|
7444
|
+
* document policies keeps no rows in the permission tables, so asking them
|
|
7445
|
+
* about such a document returns whatever the host-wide policy says — under
|
|
7446
|
+
* `OPEN`, `true`, for everyone. Serving bytes on that answer hands the file to
|
|
7447
|
+
* anyone who learns its hash, which the document's own state may well have told
|
|
7448
|
+
* them. Both are kept because they are the two halves of one rule: an
|
|
7449
|
+
* attachment is readable by whoever may read the document that references it,
|
|
7450
|
+
* and each composition can only express that in its own terms.
|
|
6699
7451
|
*/
|
|
6700
7452
|
var AttachmentAccessService = class {
|
|
6701
|
-
|
|
7453
|
+
resolveCanonicalId;
|
|
7454
|
+
authorization;
|
|
7455
|
+
references;
|
|
7456
|
+
projection;
|
|
7457
|
+
readGate;
|
|
7458
|
+
scopeGate;
|
|
7459
|
+
constructor(resolveCanonicalId, authorization, references, projection, readGate, scopeGate) {
|
|
6702
7460
|
this.resolveCanonicalId = resolveCanonicalId;
|
|
6703
7461
|
this.authorization = authorization;
|
|
6704
7462
|
this.references = references;
|
|
6705
7463
|
this.projection = projection;
|
|
7464
|
+
this.readGate = readGate;
|
|
7465
|
+
this.scopeGate = scopeGate;
|
|
6706
7466
|
}
|
|
6707
7467
|
async canReadAttachment(request) {
|
|
6708
7468
|
if (this.projection.status !== "available") return { kind: "projection-unavailable" };
|
|
@@ -6714,14 +7474,51 @@ var AttachmentAccessService = class {
|
|
|
6714
7474
|
} catch {
|
|
6715
7475
|
return { kind: "denied" };
|
|
6716
7476
|
}
|
|
6717
|
-
if (!await this.
|
|
6718
|
-
|
|
7477
|
+
if (!await this.canReadDocument(documentId, request)) return { kind: "denied" };
|
|
7478
|
+
const subject = {
|
|
7479
|
+
address: request.userAddress,
|
|
7480
|
+
key: request.appKey
|
|
7481
|
+
};
|
|
7482
|
+
let served;
|
|
7483
|
+
try {
|
|
7484
|
+
served = await this.readGate.isServed(documentId, { subject });
|
|
7485
|
+
} catch {
|
|
7486
|
+
return { kind: "denied" };
|
|
7487
|
+
}
|
|
7488
|
+
if (!served) return { kind: "denied" };
|
|
7489
|
+
const scopes = await this.references.referencingScopes(documentId, ref);
|
|
7490
|
+
if (scopes.length === 0) return { kind: "denied" };
|
|
7491
|
+
let held;
|
|
7492
|
+
try {
|
|
7493
|
+
held = (await this.readGate.get(documentId, {
|
|
7494
|
+
subject,
|
|
7495
|
+
scopes
|
|
7496
|
+
})).state;
|
|
7497
|
+
} catch {
|
|
7498
|
+
return { kind: "denied" };
|
|
7499
|
+
}
|
|
7500
|
+
if (!scopes.some((scope) => scope in held)) return { kind: "denied" };
|
|
6719
7501
|
return {
|
|
6720
7502
|
kind: "allowed",
|
|
6721
7503
|
documentId,
|
|
6722
7504
|
ref
|
|
6723
7505
|
};
|
|
6724
7506
|
}
|
|
7507
|
+
/**
|
|
7508
|
+
* Whether the caller may read the state the reference lives in.
|
|
7509
|
+
*
|
|
7510
|
+
* A failure here is rethrown rather than turned into a denial. A read side
|
|
7511
|
+
* that is down must not read as a policy: a denial nobody can distinguish
|
|
7512
|
+
* from a refusal is one nobody investigates, and the route answers 500 for a
|
|
7513
|
+
* reason.
|
|
7514
|
+
*/
|
|
7515
|
+
async canReadDocument(documentId, request) {
|
|
7516
|
+
if (!this.scopeGate) return this.authorization.canRead(documentId, request.userAddress);
|
|
7517
|
+
return (await this.scopeGate.scopePredicateById(documentId, {
|
|
7518
|
+
address: request.userAddress,
|
|
7519
|
+
key: request.appKey
|
|
7520
|
+
}, ATTACHMENT_BRANCH))(ATTACHMENT_SCOPE);
|
|
7521
|
+
}
|
|
6725
7522
|
};
|
|
6726
7523
|
/**
|
|
6727
7524
|
* Parses and canonicalizes a caller-supplied ref through the shared parser.
|
|
@@ -6767,14 +7564,42 @@ function normalizeAttachmentRef(value) {
|
|
|
6767
7564
|
* context has no caller, and is rejected. The server never composes this
|
|
6768
7565
|
* middleware without the auth middleware in the chain (see
|
|
6769
7566
|
* `assertRequireAuthenticatedCallerAllowed` in `server.ts`).
|
|
7567
|
+
*
|
|
7568
|
+
* `exemptPaths` names the mounted paths that stay reachable anonymously — a
|
|
7569
|
+
* deployment whose product has a flow that runs before sign-in, such as
|
|
7570
|
+
* previewing an invitation from its code, needs somewhere to serve it from.
|
|
7571
|
+
* Each entry is a hole in the floor, so the match is deliberately the
|
|
7572
|
+
* narrowest one that works: the request's pathname, compared in full. A
|
|
7573
|
+
* prefix rule would exempt `/graphql/public-admin` along with
|
|
7574
|
+
* `/graphql/public`, and an exempt surface nobody can enumerate by reading
|
|
7575
|
+
* the configuration is not one anybody audits.
|
|
6770
7576
|
*/
|
|
6771
|
-
function createRequireAuthFetchMiddleware() {
|
|
7577
|
+
function createRequireAuthFetchMiddleware(exemptPaths = []) {
|
|
7578
|
+
const exempt = new Set(exemptPaths.map(normalizeExemptPath));
|
|
6772
7579
|
return (next) => async (request) => {
|
|
6773
7580
|
if (request.method === "OPTIONS") return next(request);
|
|
7581
|
+
if (exempt.size > 0 && exempt.has(normalizeExemptPath(new URL(request.url).pathname))) return next(request);
|
|
6774
7582
|
if (getAuthContext(request)?.user === void 0) return new globalThis.Response(JSON.stringify({ error: "Authentication required" }), { status: 401 });
|
|
6775
7583
|
return next(request);
|
|
6776
7584
|
};
|
|
6777
7585
|
}
|
|
7586
|
+
/**
|
|
7587
|
+
* The form both sides of the comparison are reduced to: surrounding whitespace
|
|
7588
|
+
* gone, and one trailing slash gone, so `/graphql/public/` and
|
|
7589
|
+
* `/graphql/public` are the same path. A client that appends a slash is not
|
|
7590
|
+
* asking for a different route, and a configuration that carries one is not
|
|
7591
|
+
* asking for a different rule.
|
|
7592
|
+
*
|
|
7593
|
+
* Deliberately NOT normalized: case, percent-encoding, and a missing leading
|
|
7594
|
+
* slash. The first two would let two spellings of one path diverge from how
|
|
7595
|
+
* the router itself matches; the third is a configuration error, and
|
|
7596
|
+
* `assertRequireAuthenticatedCallerAllowed` refuses to boot on it rather than
|
|
7597
|
+
* leaving an exemption that silently never applies.
|
|
7598
|
+
*/
|
|
7599
|
+
function normalizeExemptPath(path) {
|
|
7600
|
+
const trimmed = path.trim();
|
|
7601
|
+
return trimmed.length > 1 && trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
7602
|
+
}
|
|
6778
7603
|
//#endregion
|
|
6779
7604
|
//#region src/migrations/001_create_document_permissions.ts
|
|
6780
7605
|
var _001_create_document_permissions_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -7177,6 +8002,7 @@ async function createRenownCredentialVerifier(config = {}) {
|
|
|
7177
8002
|
* - Users can be granted permission to execute specific operations
|
|
7178
8003
|
*/
|
|
7179
8004
|
var DocumentPermissionService = class {
|
|
8005
|
+
db;
|
|
7180
8006
|
config;
|
|
7181
8007
|
constructor(db, config = { defaultProtection: false }) {
|
|
7182
8008
|
this.db = db;
|
|
@@ -7447,12 +8273,16 @@ function createGetParentIdsFn(reactorClient) {
|
|
|
7447
8273
|
* Authorization gate for the /mcp endpoint (AUTH_REVIEW S-C1). MCP tools have
|
|
7448
8274
|
* unrestricted reactor access, so access is limited to supreme admins when
|
|
7449
8275
|
* auth is enabled. OPEN policy is public; any other policy without an
|
|
7450
|
-
* AuthService fails closed.
|
|
8276
|
+
* AuthService fails closed. The tools read as the caller: anonymous under OPEN,
|
|
8277
|
+
* else the admin's address and the app key its token carries.
|
|
7451
8278
|
*/
|
|
7452
8279
|
function createMcpRequestAuthorizer(authService, authorizationService) {
|
|
7453
8280
|
return async (req) => {
|
|
7454
8281
|
if (!authService) {
|
|
7455
|
-
if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return {
|
|
8282
|
+
if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return {
|
|
8283
|
+
authorized: true,
|
|
8284
|
+
subject: {}
|
|
8285
|
+
};
|
|
7456
8286
|
return {
|
|
7457
8287
|
authorized: false,
|
|
7458
8288
|
status: 401,
|
|
@@ -7475,10 +8305,39 @@ function createMcpRequestAuthorizer(authService, authorizationService) {
|
|
|
7475
8305
|
status: 403,
|
|
7476
8306
|
message: "Forbidden: MCP access requires an administrator"
|
|
7477
8307
|
};
|
|
7478
|
-
return {
|
|
8308
|
+
return {
|
|
8309
|
+
authorized: true,
|
|
8310
|
+
subject: {
|
|
8311
|
+
address: context.user.address,
|
|
8312
|
+
key: context.user.appKey
|
|
8313
|
+
}
|
|
8314
|
+
};
|
|
7479
8315
|
};
|
|
7480
8316
|
}
|
|
7481
8317
|
//#endregion
|
|
8318
|
+
//#region src/services/sync-serving-gate.ts
|
|
8319
|
+
/**
|
|
8320
|
+
* The gate sync serving evaluates a document's own policy through, deciding
|
|
8321
|
+
* what the reactor client's read gate decides at the same flag level.
|
|
8322
|
+
*
|
|
8323
|
+
* Under `authEnforcement` it is built here rather than taken off the client
|
|
8324
|
+
* because it also carries the host's closes-by-default setting, which withholds
|
|
8325
|
+
* the domain scopes of a document nobody has policied yet. That answer belongs
|
|
8326
|
+
* to serving alone -- replay must keep reading an uninitialized document in
|
|
8327
|
+
* full -- so the two gates are separate objects over the same model.
|
|
8328
|
+
*
|
|
8329
|
+
* Below it the registered model ignores the auth scope, so the client reads
|
|
8330
|
+
* through the policy alone, and so does serving: a policy written below the
|
|
8331
|
+
* flag still withholds, where serving ungated would sync to a peer what `get`
|
|
8332
|
+
* refuses it.
|
|
8333
|
+
*/
|
|
8334
|
+
function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
|
|
8335
|
+
if (!reactorModule) return;
|
|
8336
|
+
const model = readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry);
|
|
8337
|
+
if (!model) return new SyncScopeGate(new BareReadGate(), reactorModule.documentView, logger);
|
|
8338
|
+
return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
|
|
8339
|
+
}
|
|
8340
|
+
//#endregion
|
|
7482
8341
|
//#region src/services/renown-config.ts
|
|
7483
8342
|
/** Reading credentials from this reactor's own read model needs a host-supplied
|
|
7484
8343
|
* verifier: core has no idea which subgraph serves that read model. */
|
|
@@ -7660,10 +8519,36 @@ function assertSkipCredentialVerificationAllowed(resolvesCallerIdentity, skipCre
|
|
|
7660
8519
|
* require-authenticated-caller middleware would reject every caller —
|
|
7661
8520
|
* authenticated ones included. Refuse to boot rather than run broken.
|
|
7662
8521
|
*/
|
|
7663
|
-
function assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolvesCallerIdentity) {
|
|
8522
|
+
function assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolvesCallerIdentity, exemptPaths = []) {
|
|
8523
|
+
/**
|
|
8524
|
+
* An exemption is only ever a hole in this floor, so configuring one while
|
|
8525
|
+
* the floor is off is not a harmless no-op: it reads, to anyone auditing the
|
|
8526
|
+
* configuration, as a surface that was deliberately opened — and therefore
|
|
8527
|
+
* as a floor that exists. Refuse rather than let the two drift.
|
|
8528
|
+
*/
|
|
8529
|
+
if (!requireAuthenticatedCaller && exemptPaths.length > 0) throw new Error("REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS is set but refused: REQUIRE_AUTHENTICATED_CALLER is off, so nothing is being exempted from anything and the configuration claims a protection the server is not applying. Enable the floor, or drop the exemptions.");
|
|
8530
|
+
/**
|
|
8531
|
+
* A path that cannot match anything is worse than no path: the operator
|
|
8532
|
+
* believes the flow is reachable, and finds out when the first user cannot
|
|
8533
|
+
* claim an invitation. `URL.pathname` is always absolute, so an entry that
|
|
8534
|
+
* does not start with `/` never matches, whatever the router does.
|
|
8535
|
+
*/
|
|
8536
|
+
const relative = exemptPaths.filter((path) => !path.trim().startsWith("/"));
|
|
8537
|
+
if (relative.length > 0) throw new Error(`REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS contains ${relative.map((path) => `"${path}"`).join(", ")}, which cannot match: an exempt path is compared against the request's pathname and must start with '/' (for example '/graphql/public').`);
|
|
7664
8538
|
if (!requireAuthenticatedCaller || resolvesCallerIdentity) return;
|
|
7665
8539
|
throw new Error("REQUIRE_AUTHENTICATED_CALLER is set but refused: it rejects every request without a resolved caller, and with neither AUTH_ENABLED nor RESOLVE_CALLER_IDENTITY the server never reads a bearer, so it would reject every caller, including authenticated ones. Enable identity resolution first (RESOLVE_CALLER_IDENTITY=true or AUTH_ENABLED=true).");
|
|
7666
8540
|
}
|
|
8541
|
+
/**
|
|
8542
|
+
* Deciding an attachment read by the document's policy needs a policy model to
|
|
8543
|
+
* decide with, and that is what auth enforcement supplies. Without one there is
|
|
8544
|
+
* nothing to consult, so the setting would silently leave the permission tables
|
|
8545
|
+
* in charge — configuration that describes a protection the server is not
|
|
8546
|
+
* applying, which is worse than no configuration at all. Refuse instead.
|
|
8547
|
+
*/
|
|
8548
|
+
function assertAttachmentPolicyReadsAllowed(attachmentReadsFollowDocumentPolicy, hasDecisionModel) {
|
|
8549
|
+
if (!attachmentReadsFollowDocumentPolicy || hasDecisionModel) return;
|
|
8550
|
+
throw new Error("ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY is set but refused: deciding an attachment read by the referencing document's policy requires a policy model to evaluate, and this composition has none, so the host permission tables would keep deciding while the configuration says otherwise. Enable auth enforcement first (REACTOR_AUTH_ENFORCEMENT=true).");
|
|
8551
|
+
}
|
|
7667
8552
|
function createReadinessGate() {
|
|
7668
8553
|
let ready = false;
|
|
7669
8554
|
return {
|
|
@@ -7718,27 +8603,6 @@ function makeDbClosers(knexInstance, pglite) {
|
|
|
7718
8603
|
return closers;
|
|
7719
8604
|
}
|
|
7720
8605
|
/**
|
|
7721
|
-
* The gate sync serving evaluates a document's own policy through, or undefined
|
|
7722
|
-
* when there is none to evaluate.
|
|
7723
|
-
*
|
|
7724
|
-
* It is built here rather than taken off the reactor client because it is not
|
|
7725
|
-
* the same gate reads use: it carries the host's closes-by-default setting,
|
|
7726
|
-
* which withholds the domain scopes of a document nobody has policied yet. That
|
|
7727
|
-
* answer belongs to serving alone -- replay must keep reading an uninitialized
|
|
7728
|
-
* document in full -- so the two gates are deliberately separate objects over
|
|
7729
|
-
* the same model.
|
|
7730
|
-
*
|
|
7731
|
-
* Undefined below `authEnforcement`, where the registered model ignores the auth
|
|
7732
|
-
* scope: gating through it would serve every domain scope of a policied document
|
|
7733
|
-
* to anyone, which is worse than not gating at all.
|
|
7734
|
-
*/
|
|
7735
|
-
function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
|
|
7736
|
-
if (!reactorModule) return;
|
|
7737
|
-
const model = readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry);
|
|
7738
|
-
if (!model) return;
|
|
7739
|
-
return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
|
|
7740
|
-
}
|
|
7741
|
-
/**
|
|
7742
8606
|
* Resolves the gateway adapter type from the `GATEWAY_ADAPTER` env var.
|
|
7743
8607
|
* Defaults to "apollo" (the federation gateway, production behavior).
|
|
7744
8608
|
* "stitching" selects the in-process graphql-tools merge gateway (#1565
|
|
@@ -7853,6 +8717,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7853
8717
|
let authEnabled = false;
|
|
7854
8718
|
let configuredResolveIdentity;
|
|
7855
8719
|
let configuredRequireAuth;
|
|
8720
|
+
let configuredExemptPaths;
|
|
8721
|
+
let configuredAttachmentPolicyReads;
|
|
7856
8722
|
let configuredRenown;
|
|
7857
8723
|
if (options.configFile) {
|
|
7858
8724
|
const config = getConfig(options.configFile);
|
|
@@ -7864,8 +8730,10 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7864
8730
|
authEnabled = options.auth.enabled;
|
|
7865
8731
|
configuredResolveIdentity = options.auth.resolveIdentity;
|
|
7866
8732
|
configuredRequireAuth = options.auth.requireAuthenticatedCaller;
|
|
8733
|
+
configuredExemptPaths = options.auth.requireAuthenticatedCallerExemptPaths;
|
|
8734
|
+
configuredAttachmentPolicyReads = options.auth.attachmentReadsFollowDocumentPolicy;
|
|
7867
8735
|
}
|
|
7868
|
-
const { AUTH_ENABLED, RESOLVE_CALLER_IDENTITY, REQUIRE_AUTHENTICATED_CALLER, ADMINS, DEFAULT_PROTECTION, DOCUMENT_PERMISSIONS_ENABLED, SKIP_CREDENTIAL_VERIFICATION, CREDENTIAL_VERIFICATION_CACHE_TTL_MS } = process.env;
|
|
8736
|
+
const { AUTH_ENABLED, RESOLVE_CALLER_IDENTITY, REQUIRE_AUTHENTICATED_CALLER, REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS, ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY, ADMINS, DEFAULT_PROTECTION, DOCUMENT_PERMISSIONS_ENABLED, SKIP_CREDENTIAL_VERIFICATION, CREDENTIAL_VERIFICATION_CACHE_TTL_MS } = process.env;
|
|
7869
8737
|
if (AUTH_ENABLED !== void 0) authEnabled = AUTH_ENABLED === "true";
|
|
7870
8738
|
/**
|
|
7871
8739
|
* Whether the auth middleware reads the bearer and populates `ctx.user`,
|
|
@@ -7896,6 +8764,30 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7896
8764
|
*/
|
|
7897
8765
|
let requireAuthenticatedCaller = configuredRequireAuth ?? false;
|
|
7898
8766
|
if (REQUIRE_AUTHENTICATED_CALLER !== void 0) requireAuthenticatedCaller = REQUIRE_AUTHENTICATED_CALLER === "true";
|
|
8767
|
+
/**
|
|
8768
|
+
* The paths the floor above does not apply to. Comma-separated, like
|
|
8769
|
+
* `ADMINS`, because a deployment configures this the same way it configures
|
|
8770
|
+
* everything else: one variable, one line, however many values it has.
|
|
8771
|
+
*
|
|
8772
|
+
* Empty entries are dropped rather than refused, so a trailing comma or a
|
|
8773
|
+
* value split across lines in a manifest is not a boot failure; a value that
|
|
8774
|
+
* cannot ever match is refused, in
|
|
8775
|
+
* {@link assertRequireAuthenticatedCallerAllowed}.
|
|
8776
|
+
*/
|
|
8777
|
+
let requireAuthenticatedCallerExemptPaths = configuredExemptPaths ?? [];
|
|
8778
|
+
if (REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS !== void 0) requireAuthenticatedCallerExemptPaths = REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS.split(",").map((path) => path.trim()).filter((path) => path.length > 0);
|
|
8779
|
+
/**
|
|
8780
|
+
* Whether an attachment read is decided by the referencing document's own
|
|
8781
|
+
* policy rather than by the host's permission tables.
|
|
8782
|
+
*
|
|
8783
|
+
* Off by default, and a deployment's choice rather than something derived
|
|
8784
|
+
* from the flags around it. Which model governs those bytes is configuration
|
|
8785
|
+
* in the same sense the storage backend behind them is: a host that has said
|
|
8786
|
+
* nothing keeps exactly the behaviour it has, and one that wants the change
|
|
8787
|
+
* asks for it and can take it back without disturbing anything else.
|
|
8788
|
+
*/
|
|
8789
|
+
let attachmentReadsFollowDocumentPolicy = configuredAttachmentPolicyReads ?? false;
|
|
8790
|
+
if (ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY !== void 0) attachmentReadsFollowDocumentPolicy = ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY === "true";
|
|
7899
8791
|
if (ADMINS !== void 0) admins = ADMINS.split(",").map((a) => a.toLowerCase());
|
|
7900
8792
|
let defaultProtection = false;
|
|
7901
8793
|
if (DEFAULT_PROTECTION !== void 0) defaultProtection = DEFAULT_PROTECTION.toLowerCase() === "true";
|
|
@@ -7910,7 +8802,7 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7910
8802
|
const documentPermissionsRequested = options.documentPermissionService !== void 0 || DOCUMENT_PERMISSIONS_ENABLED === "true";
|
|
7911
8803
|
assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissionsRequested);
|
|
7912
8804
|
assertSkipCredentialVerificationAllowed(resolveCallerIdentity, skipCredentialVerification, process.env);
|
|
7913
|
-
assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolveCallerIdentity);
|
|
8805
|
+
assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolveCallerIdentity, requireAuthenticatedCallerExemptPaths);
|
|
7914
8806
|
if (authEnabled && skipCredentialVerification) logger.warn("SECURITY: SKIP_CREDENTIAL_VERIFICATION is enabled — Renown credential verification is disabled and a bearer token's claimed address is NOT cryptographically bound to its signing key. Identity is unverifiable; use only in development or test.");
|
|
7915
8807
|
httpAdapter.getRoute("/health", () => new Response("OK", { status: 200 }));
|
|
7916
8808
|
const readiness = createReadinessGate();
|
|
@@ -7946,8 +8838,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7946
8838
|
});
|
|
7947
8839
|
authFetchMiddleware = createAuthFetchMiddleware(authService);
|
|
7948
8840
|
if (requireAuthenticatedCaller) {
|
|
7949
|
-
requireAuthFetchMiddleware = createRequireAuthFetchMiddleware();
|
|
7950
|
-
logger.info("Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph");
|
|
8841
|
+
requireAuthFetchMiddleware = createRequireAuthFetchMiddleware(requireAuthenticatedCallerExemptPaths);
|
|
8842
|
+
logger.info(requireAuthenticatedCallerExemptPaths.length > 0 ? `Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph, except on ${requireAuthenticatedCallerExemptPaths.join(", ")}` : "Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph");
|
|
7951
8843
|
} 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.");
|
|
7952
8844
|
}
|
|
7953
8845
|
const dbClosers = [];
|
|
@@ -7972,7 +8864,7 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7972
8864
|
await mkdir(attachmentStoragePath, { recursive: true });
|
|
7973
8865
|
const { db: attachmentDb, knex: attachmentKnex, pglite: attachmentPglite } = getDbClient(options.dbPath, options.pgliteFactory);
|
|
7974
8866
|
dbClosers.push(...makeDbClosers(attachmentKnex, attachmentPglite));
|
|
7975
|
-
const ATTACHMENT_SWEEP_INTERVAL_MS =
|
|
8867
|
+
const ATTACHMENT_SWEEP_INTERVAL_MS = 36e5;
|
|
7976
8868
|
const attachmentBackend = await createStartupAttachmentBackend({ db: attachmentDb });
|
|
7977
8869
|
const attachmentBuilder = new AttachmentBuilder(attachmentDb, attachmentStoragePath).withReservationSweepMs(ATTACHMENT_SWEEP_INTERVAL_MS);
|
|
7978
8870
|
if (attachmentBackend) attachmentBuilder.withBackend(attachmentBackend);
|
|
@@ -8017,13 +8909,14 @@ async function _setupCommonInfrastructure(options) {
|
|
|
8017
8909
|
attachmentReferenceIndex,
|
|
8018
8910
|
packages,
|
|
8019
8911
|
dbClosers,
|
|
8020
|
-
readiness
|
|
8912
|
+
readiness,
|
|
8913
|
+
attachmentReadsFollowDocumentPolicy
|
|
8021
8914
|
};
|
|
8022
8915
|
}
|
|
8023
8916
|
/**
|
|
8024
8917
|
* Private helper function containing common setup logic for API initialization
|
|
8025
8918
|
*/
|
|
8026
|
-
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, readModels, attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers = [], reactorDriveClient, syncServingGate, httpRoutes) {
|
|
8919
|
+
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, readModels, attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers = [], reactorDriveClient, syncServingGate, httpRoutes, attachmentReadsFollowDocumentPolicy = false) {
|
|
8027
8920
|
const hostModuleBase = {
|
|
8028
8921
|
...createReactorHostModuleBase({
|
|
8029
8922
|
client: reactorClient,
|
|
@@ -8065,7 +8958,8 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
8065
8958
|
const { httpServer, wsServer } = await startServer(httpAdapter, port, options.https, logger);
|
|
8066
8959
|
const authorizationService = createAuthorizationService(authorizationConfig, documentPermissionService, createGetParentIdsFn(reactorClient));
|
|
8067
8960
|
logger.info(`Authorization service initialized (policy: ${authorizationConfig.policy})`);
|
|
8068
|
-
|
|
8961
|
+
assertAttachmentPolicyReadsAllowed(attachmentReadsFollowDocumentPolicy, syncServingGate !== void 0);
|
|
8962
|
+
const attachmentAccess = new AttachmentAccessService(createCanonicalDocumentIdResolver(reactorClient), authorizationService, attachmentReferenceIndex.store, attachmentReferenceProjection, reactorClient, attachmentReadsFollowDocumentPolicy ? syncServingGate : void 0);
|
|
8069
8963
|
const coreSubgraphs = DefaultCoreSubgraphs.slice();
|
|
8070
8964
|
coreSubgraphs.push(ReactorSubgraph);
|
|
8071
8965
|
if (documentPermissionService) {
|
|
@@ -8102,6 +8996,9 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
8102
8996
|
attachmentReferenceIndex,
|
|
8103
8997
|
attachmentAccess,
|
|
8104
8998
|
authService,
|
|
8999
|
+
requireAuthenticatedCaller: requireAuthFetchMiddleware !== void 0,
|
|
9000
|
+
authorizationService,
|
|
9001
|
+
relationalDb,
|
|
8105
9002
|
dispose
|
|
8106
9003
|
};
|
|
8107
9004
|
}
|
|
@@ -8148,7 +9045,7 @@ function buildApiDispose(args) {
|
|
|
8148
9045
|
};
|
|
8149
9046
|
}
|
|
8150
9047
|
async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
8151
|
-
const { port, httpAdapter, httpRoutes, authFetchMiddleware, requireAuthFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness } = await _setupCommonInfrastructure(options);
|
|
9048
|
+
const { port, httpAdapter, httpRoutes, authFetchMiddleware, requireAuthFetchMiddleware, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness, attachmentReadsFollowDocumentPolicy } = await _setupCommonInfrastructure(options);
|
|
8152
9049
|
const { documentModels, upgradeManifests, processors, subgraphs } = await packages.init();
|
|
8153
9050
|
const { module: reactorClientModule, reactorDriveClient, attachmentReferenceProjection = {
|
|
8154
9051
|
status: "unavailable",
|
|
@@ -8165,7 +9062,7 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
8165
9062
|
const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
|
|
8166
9063
|
if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from InProcessReactorClientModule");
|
|
8167
9064
|
return {
|
|
8168
|
-
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers, reactorDriveClient, buildSyncServingGate(reactorClientModule.reactorModule, authorizationConfig, options.logger ?? defaultLogger), httpRoutes),
|
|
9065
|
+
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, (reactorClientModule.reactorModule?.readModelCoordinator)?.readModels ?? [], attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers, reactorDriveClient, buildSyncServingGate(reactorClientModule.reactorModule, authorizationConfig, options.logger ?? defaultLogger), httpRoutes, attachmentReadsFollowDocumentPolicy),
|
|
8169
9066
|
client: reactorClient,
|
|
8170
9067
|
syncManager,
|
|
8171
9068
|
documentModelRegistry,
|
|
@@ -8298,7 +9195,7 @@ var PackageManagementService = class {
|
|
|
8298
9195
|
}
|
|
8299
9196
|
};
|
|
8300
9197
|
//#endregion
|
|
8301
|
-
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, 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 };
|
|
9198
|
+
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, SyncRefusalInputSchema, SystemSubgraph, TouchChannelDocument, TouchChannelInputSchema, UpdateRelationshipDocument, ViewFilterInputSchema, WEBHOOK_SEGMENT, WS_AUTH_CLOSE_REASONS, WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE_REASON_BEARER_REJECTED, WebhookService, assertAttachmentPolicyReadsAllowed, assertAuthRequiredForDocumentPermissions, assertCredentialVerifierForSource, assertHostMountPath, assertRequireAuthenticatedCallerAllowed, assertSkipCredentialVerificationAllowed, buildGraphQlDocument, buildGraphQlDriveDocument, buildGraphqlOperation, buildGraphqlOperations, buildSubgraphSchemaModule, callerSubject, 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, piecesBaseUrl, redactHeaders, renderGraphqlPlayground, resolvePackageName, resolveRenownConfig, verifyWebhook };
|
|
8302
9199
|
|
|
8303
9200
|
//# sourceMappingURL=index.mjs.map
|
|
8304
|
-
//# debugId=
|
|
9201
|
+
//# debugId=66ae2db7-0d74-5986-a811-364313026902
|