@powerhousedao/reactor-api 6.2.3-dev.4 → 6.2.3-dev.40
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 +1248 -593
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1784 -487
- package/dist/index.mjs.map +1 -1
- package/dist/{utils-XWVecaNn.mjs → pieces-DRY6haAO.mjs} +177 -9
- package/dist/pieces-DRY6haAO.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 +19 -9
- 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-DlTiz6AA.d.mts} +27 -4
- package/dist/types-DlTiz6AA.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]="a50faed2-4e40-54ba-873f-1808cedc1e5c")}catch(e){}}();
|
|
3
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
4
|
+
import { A as RefusalNotRecordedError, 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-DRY6haAO.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, supportsPurgeRefusals, syncOperationErrorType } from "@powerhousedao/reactor";
|
|
10
|
+
import { deserializeSignature, readPeerManifest, serializeSignature, setName, withSignaturePolicy } from "@powerhousedao/shared/document-model";
|
|
9
11
|
import { ConsoleLogger, childLogger, documentModelDocumentModelModule } from "document-model";
|
|
10
|
-
import path, { join } from "node:path";
|
|
12
|
+
import path, { isAbsolute, join } from "node:path";
|
|
11
13
|
import { match } from "path-to-regexp";
|
|
12
14
|
import posix from "node:path/posix";
|
|
13
15
|
import { Readable } from "node:stream";
|
|
14
|
-
import fs, { readFileSync, watchFile } from "node:fs";
|
|
16
|
+
import fs, { existsSync, readFileSync, watchFile } from "node:fs";
|
|
15
17
|
import { pipeline } from "node:stream/promises";
|
|
16
18
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
19
|
+
import { pathToFileURL } from "node:url";
|
|
17
20
|
import { buildSubgraphSchema } from "@apollo/subgraph";
|
|
18
21
|
import { typeDefs } from "@powerhousedao/document-engineering/graphql";
|
|
19
22
|
import { camelCase, kebabCase, pascalCase } from "change-case";
|
|
20
23
|
import { GraphQLJSON, GraphQLJSONObject } from "graphql-type-json";
|
|
21
|
-
import { deserializeSignature, serializeSignature, setName } 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";
|
|
@@ -32,7 +34,7 @@ import { ReactorGroupV1 } from "@powerhousedao/reactor-group";
|
|
|
32
34
|
import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
|
|
33
35
|
import EventEmitter from "node:events";
|
|
34
36
|
import { PostgresAnalyticsStore } from "@powerhousedao/analytics-engine-pg";
|
|
35
|
-
import { AttachmentBuilder, AttachmentReferenceIndexBuilder, createRef, createS3AttachmentBackend, parseAttachmentStorageConfig, parseRef } from "@powerhousedao/reactor-attachments";
|
|
37
|
+
import { AttachmentBuilder, AttachmentNotFound, AttachmentReferenceIndexBuilder, createRef, createS3AttachmentBackend, parseAttachmentStorageConfig, parseRef, validateReserveMetadata } from "@powerhousedao/reactor-attachments";
|
|
36
38
|
import { createAttachmentClient } from "@powerhousedao/reactor-attachments/client";
|
|
37
39
|
import { setupMcpServer } from "@powerhousedao/reactor-mcp";
|
|
38
40
|
import { mkdir } from "node:fs/promises";
|
|
@@ -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,17 +57,60 @@ 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
|
|
79
|
-
var schema_default$2 = "# Auth Subgraph Schema\n# Contains all document permission and authorization related types\n\nscalar DateTime\n\n# Permission levels for documents\nenum DocumentPermissionLevel {\n # Can fetch and read the document\n READ\n # Can push updates and modify the document\n WRITE\n # Can manage document permissions and settings\n ADMIN\n}\n\n# Document permission entry\ntype DocumentPermissionEntry {\n documentId: String!\n userAddress: String!\n permission: DocumentPermissionLevel!\n grantedBy: String!\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\n# Operation user permission entry\ntype OperationUserPermission {\n documentId: String!\n operationType: String!\n userAddress: String!\n grantedBy: String!\n createdAt: DateTime!\n}\n\n# Combined document access info\ntype DocumentAccessInfo {\n documentId: String!\n permissions: [DocumentPermissionEntry!]!\n}\n\n# Operation permissions info\ntype OperationPermissionsInfo {\n documentId: String!\n operationType: String!\n userPermissions: [OperationUserPermission!]!\n}\n\n# Document protection info\ntype DocumentProtectionInfo {\n documentId: String!\n protected: Boolean!\n ownerAddress: String\n}\n\ntype Query {\n # Get permissions for a document (requires ADMIN permission or global admin)\n documentAccess(documentId: String
|
|
90
|
+
var schema_default$2 = "# Auth Subgraph Schema\n# Contains all document permission and authorization related types\n\nscalar DateTime\n\n# Permission levels for documents\nenum DocumentPermissionLevel {\n # Can fetch and read the document\n READ\n # Can push updates and modify the document\n WRITE\n # Can manage document permissions and settings\n ADMIN\n}\n\n# Document permission entry\ntype DocumentPermissionEntry {\n documentId: String!\n userAddress: String!\n permission: DocumentPermissionLevel!\n grantedBy: String!\n createdAt: DateTime!\n updatedAt: DateTime!\n}\n\n# Operation user permission entry\ntype OperationUserPermission {\n documentId: String!\n operationType: String!\n userAddress: String!\n grantedBy: String!\n createdAt: DateTime!\n}\n\n# Combined document access info\ntype DocumentAccessInfo {\n documentId: String!\n permissions: [DocumentPermissionEntry!]!\n}\n\n# Operation permissions info\ntype OperationPermissionsInfo {\n documentId: String!\n operationType: String!\n userPermissions: [OperationUserPermission!]!\n}\n\n# Document protection info\ntype DocumentProtectionInfo {\n documentId: String!\n protected: Boolean!\n ownerAddress: String\n}\n\ntype Query {\n # Get permissions for a document (requires ADMIN permission or global admin)\n documentAccess(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n ): DocumentAccessInfo!\n\n # Get protection status for a document (requires ADMIN permission or global admin)\n documentProtection(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n ): DocumentProtectionInfo!\n\n # Get all documents the current user has explicit access to\n userDocumentPermissions: [DocumentPermissionEntry!]!\n\n # Get operation permissions for a document and operation type\n # (requires ADMIN permission or global admin)\n operationPermissions(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n operationType: String!\n ): OperationPermissionsInfo!\n\n # Check if the current user can execute a specific operation on a document\n canExecuteOperation(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n operationType: String!\n ): Boolean!\n}\n\ntype Mutation {\n # Set document protection status (requires ADMIN permission or global admin)\n setDocumentProtection(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n protected: Boolean!\n ): DocumentProtectionInfo!\n\n # Transfer document ownership (requires ADMIN permission or global admin)\n transferDocumentOwnership(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n newOwnerAddress: String!\n ): DocumentProtectionInfo!\n\n # Grant a user permission on a document (requires ADMIN permission or global admin)\n grantDocumentPermission(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n userAddress: String!\n permission: DocumentPermissionLevel!\n ): DocumentPermissionEntry!\n\n # Revoke a user's permission on a document (requires ADMIN permission or global admin)\n revokeDocumentPermission(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n userAddress: String!\n ): Boolean!\n\n # --- Operation Permissions ---\n\n # Grant a user permission to execute an operation on a document\n grantOperationPermission(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n operationType: String!\n userAddress: String!\n ): OperationUserPermission!\n\n # Revoke a user's permission to execute an operation\n revokeOperationPermission(\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\n operationType: String!\n userAddress: String!\n ): Boolean!\n}\n";
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/graphql/argument-aliases.ts
|
|
93
|
+
/** A renamed argument given under both names, or a required one under neither. */
|
|
94
|
+
var ArgumentAliasError = class extends GraphQLError {
|
|
95
|
+
constructor(message) {
|
|
96
|
+
super(message, { extensions: { code: "BAD_USER_INPUT" } });
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
/** The value of a required argument that may still arrive under its old name. */
|
|
100
|
+
function requireOneOf(args, name, deprecatedName) {
|
|
101
|
+
const current = args[name] ?? void 0;
|
|
102
|
+
const deprecated = args[deprecatedName] ?? void 0;
|
|
103
|
+
if (current !== void 0 && deprecated !== void 0) throw new ArgumentAliasError(`Pass ${name} or ${deprecatedName}, not both.`);
|
|
104
|
+
if (current === void 0 && deprecated === void 0) throw new ArgumentAliasError(`${name} is required.`);
|
|
105
|
+
return current ?? deprecated;
|
|
106
|
+
}
|
|
107
|
+
/** The value of an optional argument that may still arrive under its old name. */
|
|
108
|
+
function optionalOneOf(args, name, deprecatedName) {
|
|
109
|
+
const current = args[name] ?? void 0;
|
|
110
|
+
const deprecated = args[deprecatedName] ?? void 0;
|
|
111
|
+
if (current !== void 0 && deprecated !== void 0) throw new ArgumentAliasError(`Pass ${name} or ${deprecatedName}, not both.`);
|
|
112
|
+
return current ?? deprecated;
|
|
113
|
+
}
|
|
80
114
|
//#endregion
|
|
81
115
|
//#region src/graphql/auth/resolvers.ts
|
|
82
116
|
async function documentAccess(service, authorizationService, args, userAddress) {
|
|
@@ -172,7 +206,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
172
206
|
this.logger.debug("documentAccess(@args)", args);
|
|
173
207
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
174
208
|
try {
|
|
175
|
-
return await documentAccess(this.documentPermissionService, this.authorizationService, await this
|
|
209
|
+
return await documentAccess(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
176
210
|
} catch (error) {
|
|
177
211
|
this.logger.error("Error in documentAccess: @error", error);
|
|
178
212
|
throw error;
|
|
@@ -193,7 +227,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
193
227
|
this.logger.debug("operationPermissions(@args)", args);
|
|
194
228
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
195
229
|
try {
|
|
196
|
-
return await operationPermissions(this.documentPermissionService, this.authorizationService, await this
|
|
230
|
+
return await operationPermissions(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
197
231
|
} catch (error) {
|
|
198
232
|
this.logger.error("Error in operationPermissions: @error", error);
|
|
199
233
|
throw error;
|
|
@@ -202,7 +236,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
202
236
|
canExecuteOperation: async (_parent, args, ctx) => {
|
|
203
237
|
this.logger.debug("canExecuteOperation(@args)", args);
|
|
204
238
|
try {
|
|
205
|
-
return await canExecuteOperation(this.authorizationService, await this
|
|
239
|
+
return await canExecuteOperation(this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
206
240
|
} catch (error) {
|
|
207
241
|
this.logger.error("Error in canExecuteOperation: @error", error);
|
|
208
242
|
throw error;
|
|
@@ -212,7 +246,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
212
246
|
this.logger.debug("documentProtection(@args)", args);
|
|
213
247
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
214
248
|
try {
|
|
215
|
-
return await documentProtection(this.documentPermissionService, this.authorizationService, await this
|
|
249
|
+
return await documentProtection(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
216
250
|
} catch (error) {
|
|
217
251
|
this.logger.error("Error in documentProtection: @error", error);
|
|
218
252
|
throw error;
|
|
@@ -224,7 +258,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
224
258
|
this.logger.debug("setDocumentProtection(@args)", args);
|
|
225
259
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
226
260
|
try {
|
|
227
|
-
return await setDocumentProtection(this.documentPermissionService, this.authorizationService, await this
|
|
261
|
+
return await setDocumentProtection(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
228
262
|
} catch (error) {
|
|
229
263
|
this.logger.error("Error in setDocumentProtection: @error", error);
|
|
230
264
|
throw error;
|
|
@@ -234,7 +268,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
234
268
|
this.logger.debug("transferDocumentOwnership(@args)", args);
|
|
235
269
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
236
270
|
try {
|
|
237
|
-
return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, await this
|
|
271
|
+
return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
238
272
|
} catch (error) {
|
|
239
273
|
this.logger.error("Error in transferDocumentOwnership: @error", error);
|
|
240
274
|
throw error;
|
|
@@ -244,7 +278,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
244
278
|
this.logger.debug("grantDocumentPermission(@args)", args);
|
|
245
279
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
246
280
|
try {
|
|
247
|
-
const resolved = await this
|
|
281
|
+
const resolved = await this.#withCanonicalDocumentArgs(args, ctx);
|
|
248
282
|
return await grantDocumentPermission(this.documentPermissionService, this.authorizationService, {
|
|
249
283
|
...resolved,
|
|
250
284
|
permission: resolved.permission
|
|
@@ -258,7 +292,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
258
292
|
this.logger.debug("revokeDocumentPermission(@args)", args);
|
|
259
293
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
260
294
|
try {
|
|
261
|
-
return await revokeDocumentPermission(this.documentPermissionService, this.authorizationService, await this
|
|
295
|
+
return await revokeDocumentPermission(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
262
296
|
} catch (error) {
|
|
263
297
|
this.logger.error("Error in revokeDocumentPermission: @error", error);
|
|
264
298
|
throw error;
|
|
@@ -268,7 +302,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
268
302
|
this.logger.debug("grantOperationPermission(@args)", args);
|
|
269
303
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
270
304
|
try {
|
|
271
|
-
return await grantOperationPermission(this.documentPermissionService, this.authorizationService, await this
|
|
305
|
+
return await grantOperationPermission(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
272
306
|
} catch (error) {
|
|
273
307
|
this.logger.error("Error in grantOperationPermission: @error", error);
|
|
274
308
|
throw error;
|
|
@@ -278,7 +312,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
278
312
|
this.logger.debug("revokeOperationPermission(@args)", args);
|
|
279
313
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
280
314
|
try {
|
|
281
|
-
return await revokeOperationPermission(this.documentPermissionService, this.authorizationService, await this
|
|
315
|
+
return await revokeOperationPermission(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
282
316
|
} catch (error) {
|
|
283
317
|
this.logger.error("Error in revokeOperationPermission: @error", error);
|
|
284
318
|
throw error;
|
|
@@ -286,6 +320,16 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
286
320
|
}
|
|
287
321
|
}
|
|
288
322
|
};
|
|
323
|
+
#withCanonicalDocumentArgs(args, ctx) {
|
|
324
|
+
const { documentIdOrSlug, documentId, ...rest } = args;
|
|
325
|
+
return this.withCanonicalDocumentId({
|
|
326
|
+
...rest,
|
|
327
|
+
documentId: requireOneOf({
|
|
328
|
+
documentIdOrSlug,
|
|
329
|
+
documentId
|
|
330
|
+
}, "documentIdOrSlug", "documentId")
|
|
331
|
+
}, ctx);
|
|
332
|
+
}
|
|
289
333
|
onSetup() {
|
|
290
334
|
this.logger.debug("Setting up AuthSubgraph");
|
|
291
335
|
return Promise.resolve();
|
|
@@ -311,15 +355,15 @@ function createAuthFetchMiddleware(authService) {
|
|
|
311
355
|
async function createGatewayAdapter(type, logger) {
|
|
312
356
|
switch (type) {
|
|
313
357
|
case "apollo": {
|
|
314
|
-
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-
|
|
358
|
+
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-D2DNlPY0.mjs");
|
|
315
359
|
return new ApolloGatewayAdapter(logger);
|
|
316
360
|
}
|
|
317
361
|
case "mercurius": {
|
|
318
|
-
const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-
|
|
362
|
+
const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-rbKlwF9m.mjs");
|
|
319
363
|
return new MercuriusGatewayAdapter(logger);
|
|
320
364
|
}
|
|
321
365
|
case "stitching": {
|
|
322
|
-
const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-
|
|
366
|
+
const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-Dzv10u8D.mjs");
|
|
323
367
|
return new StitchingGatewayAdapter(logger);
|
|
324
368
|
}
|
|
325
369
|
}
|
|
@@ -327,11 +371,11 @@ async function createGatewayAdapter(type, logger) {
|
|
|
327
371
|
async function createHttpAdapter(type) {
|
|
328
372
|
switch (type) {
|
|
329
373
|
case "express": {
|
|
330
|
-
const { createExpressHttpAdapter } = await import("./adapter-http-express-
|
|
374
|
+
const { createExpressHttpAdapter } = await import("./adapter-http-express-CWqziB1p.mjs");
|
|
331
375
|
return createExpressHttpAdapter();
|
|
332
376
|
}
|
|
333
377
|
case "fastify": {
|
|
334
|
-
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-
|
|
378
|
+
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-CYZQ6FFv.mjs");
|
|
335
379
|
return createFastifyHttpAdapter();
|
|
336
380
|
}
|
|
337
381
|
}
|
|
@@ -359,7 +403,7 @@ const WS_AUTH_CLOSE_REASONS = [WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE
|
|
|
359
403
|
*/
|
|
360
404
|
const SEGMENT = /^[a-z0-9][-a-z0-9._]*$/i;
|
|
361
405
|
/** Segments that would read as path traversal rather than as a name. */
|
|
362
|
-
const TRAVERSAL = new Set([".", ".."]);
|
|
406
|
+
const TRAVERSAL = /* @__PURE__ */ new Set([".", ".."]);
|
|
363
407
|
var InvalidNamespaceError = class extends Error {};
|
|
364
408
|
/**
|
|
365
409
|
* Turns a package name into the path segments it occupies.
|
|
@@ -845,27 +889,29 @@ var HttpScope = class {
|
|
|
845
889
|
headers: fetchHeaders(req),
|
|
846
890
|
signal: controller.signal
|
|
847
891
|
};
|
|
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 = {
|
|
892
|
+
if (method !== "GET" && method !== "HEAD" && body !== "none") {
|
|
893
|
+
if (body === "stream") init = {
|
|
866
894
|
...init,
|
|
867
|
-
body:
|
|
895
|
+
body: Readable.toWeb(req),
|
|
896
|
+
duplex: "half"
|
|
868
897
|
};
|
|
898
|
+
else {
|
|
899
|
+
try {
|
|
900
|
+
raw = await readBody(req, spec.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES$1);
|
|
901
|
+
} catch (error) {
|
|
902
|
+
if (error instanceof BodyTooLargeError) {
|
|
903
|
+
res.setHeader("connection", "close");
|
|
904
|
+
res.once("finish", () => req.destroy());
|
|
905
|
+
writeError(res, 413, "Payload too large");
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
throw error;
|
|
909
|
+
}
|
|
910
|
+
if (raw.length) init = {
|
|
911
|
+
...init,
|
|
912
|
+
body: new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength)
|
|
913
|
+
};
|
|
914
|
+
}
|
|
869
915
|
}
|
|
870
916
|
await writeResponse(res, await spec.handler(new Request(url, init), {
|
|
871
917
|
...ctx,
|
|
@@ -1042,7 +1088,7 @@ function verifyWebhook(options) {
|
|
|
1042
1088
|
}
|
|
1043
1089
|
/** Headers that would put a credential in a log or run journal. The configured signature
|
|
1044
1090
|
* header joins them: it is an HMAC of the body under a live secret. */
|
|
1045
|
-
const REDACTED_HEADERS = new Set([
|
|
1091
|
+
const REDACTED_HEADERS = /* @__PURE__ */ new Set([
|
|
1046
1092
|
"authorization",
|
|
1047
1093
|
"proxy-authorization",
|
|
1048
1094
|
"cookie",
|
|
@@ -1315,6 +1361,7 @@ function newWebhookToken() {
|
|
|
1315
1361
|
const NAMESPACE = "reactor_webhooks";
|
|
1316
1362
|
/** Relational implementation, over the reactor's own database. */
|
|
1317
1363
|
var RelationalWebhookStore = class {
|
|
1364
|
+
relationalDb;
|
|
1318
1365
|
#db;
|
|
1319
1366
|
constructor(relationalDb) {
|
|
1320
1367
|
this.relationalDb = relationalDb;
|
|
@@ -1329,7 +1376,11 @@ var RelationalWebhookStore = class {
|
|
|
1329
1376
|
"owner_key"
|
|
1330
1377
|
]).unique().execute();
|
|
1331
1378
|
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();
|
|
1379
|
+
try {
|
|
1380
|
+
await db.schema.alterTable("webhook_deliveries").addColumn("expires_at", "text").execute();
|
|
1381
|
+
} catch {}
|
|
1332
1382
|
await db.schema.createIndex("webhook_deliveries_key").ifNotExists().on("webhook_deliveries").columns(["token", "dedupe_key"]).unique().execute();
|
|
1383
|
+
await db.schema.createIndex("webhook_deliveries_expires_at").ifNotExists().on("webhook_deliveries").columns(["expires_at"]).execute();
|
|
1333
1384
|
await db.schema.createIndex("webhook_deliveries_seen_at").ifNotExists().on("webhook_deliveries").columns(["seen_at"]).execute();
|
|
1334
1385
|
this.#db = db;
|
|
1335
1386
|
}
|
|
@@ -1370,13 +1421,16 @@ var RelationalWebhookStore = class {
|
|
|
1370
1421
|
}
|
|
1371
1422
|
async seen(token, key, ttlSeconds) {
|
|
1372
1423
|
const now = Date.now();
|
|
1424
|
+
const nowIso = new Date(now).toISOString();
|
|
1373
1425
|
const cutoff = (/* @__PURE__ */ new Date(now - ttlSeconds * 1e3)).toISOString();
|
|
1374
|
-
await this.#handle.deleteFrom("webhook_deliveries").where("seen_at", "<", cutoff).execute();
|
|
1426
|
+
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();
|
|
1427
|
+
await this.#handle.deleteFrom("webhook_deliveries").where("token", "=", token).where("dedupe_key", "=", key).where("seen_at", "<", cutoff).execute();
|
|
1375
1428
|
const claimId = randomBytes(16).toString("hex");
|
|
1376
1429
|
await this.#handle.insertInto("webhook_deliveries").values({
|
|
1377
1430
|
token,
|
|
1378
1431
|
dedupe_key: key,
|
|
1379
|
-
seen_at:
|
|
1432
|
+
seen_at: nowIso,
|
|
1433
|
+
expires_at: new Date(now + ttlSeconds * 1e3).toISOString(),
|
|
1380
1434
|
claim_id: claimId
|
|
1381
1435
|
}).onConflict((oc) => oc.columns(["token", "dedupe_key"]).doNothing()).execute();
|
|
1382
1436
|
return (await this.#handle.selectFrom("webhook_deliveries").select("claim_id").where("token", "=", token).where("dedupe_key", "=", key).executeTakeFirst())?.claim_id !== claimId;
|
|
@@ -1422,10 +1476,14 @@ var MemoryWebhookStore = class {
|
|
|
1422
1476
|
}
|
|
1423
1477
|
seen(token, key, ttlSeconds) {
|
|
1424
1478
|
const now = Date.now();
|
|
1425
|
-
for (const [k,
|
|
1479
|
+
for (const [k, entry] of this.#deliveries) if (entry.expiresAt < now) this.#deliveries.delete(k);
|
|
1426
1480
|
const composite = `${token}|${key}`;
|
|
1427
|
-
|
|
1428
|
-
|
|
1481
|
+
const existing = this.#deliveries.get(composite);
|
|
1482
|
+
if (existing && existing.seenAt >= now - ttlSeconds * 1e3) return Promise.resolve(true);
|
|
1483
|
+
this.#deliveries.set(composite, {
|
|
1484
|
+
seenAt: now,
|
|
1485
|
+
expiresAt: now + ttlSeconds * 1e3
|
|
1486
|
+
});
|
|
1429
1487
|
return Promise.resolve(false);
|
|
1430
1488
|
}
|
|
1431
1489
|
};
|
|
@@ -1460,7 +1518,7 @@ const stripScalarDefinitions = (doc) => {
|
|
|
1460
1518
|
* whole gateway. We dedupe these by name; field/operation/extension nodes are
|
|
1461
1519
|
* left untouched.
|
|
1462
1520
|
*/
|
|
1463
|
-
const TYPE_DEFINITION_KINDS = new Set([
|
|
1521
|
+
const TYPE_DEFINITION_KINDS = /* @__PURE__ */ new Set([
|
|
1464
1522
|
Kind.OBJECT_TYPE_DEFINITION,
|
|
1465
1523
|
Kind.ENUM_TYPE_DEFINITION,
|
|
1466
1524
|
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
@@ -1496,6 +1554,98 @@ const dedupeTypeDefinitions = (doc) => {
|
|
|
1496
1554
|
definitions
|
|
1497
1555
|
};
|
|
1498
1556
|
};
|
|
1557
|
+
const EMPTY_DOCUMENT = {
|
|
1558
|
+
kind: Kind.DOCUMENT,
|
|
1559
|
+
definitions: []
|
|
1560
|
+
};
|
|
1561
|
+
const OUTPUT_TYPE_KINDS = [
|
|
1562
|
+
Kind.OBJECT_TYPE_DEFINITION,
|
|
1563
|
+
Kind.OBJECT_TYPE_EXTENSION,
|
|
1564
|
+
Kind.ENUM_TYPE_DEFINITION,
|
|
1565
|
+
Kind.ENUM_TYPE_EXTENSION,
|
|
1566
|
+
Kind.UNION_TYPE_DEFINITION,
|
|
1567
|
+
Kind.UNION_TYPE_EXTENSION,
|
|
1568
|
+
Kind.INTERFACE_TYPE_DEFINITION,
|
|
1569
|
+
Kind.INTERFACE_TYPE_EXTENSION
|
|
1570
|
+
];
|
|
1571
|
+
const STATE_PREFIX_KINDS = new Set(OUTPUT_TYPE_KINDS);
|
|
1572
|
+
const ALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
|
|
1573
|
+
...OUTPUT_TYPE_KINDS,
|
|
1574
|
+
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
1575
|
+
Kind.INPUT_OBJECT_TYPE_EXTENSION
|
|
1576
|
+
]);
|
|
1577
|
+
const TYPE_NAME_KINDS = /* @__PURE__ */ new Set([
|
|
1578
|
+
...ALL_PREFIX_KINDS,
|
|
1579
|
+
Kind.SCALAR_TYPE_DEFINITION,
|
|
1580
|
+
Kind.SCALAR_TYPE_EXTENSION,
|
|
1581
|
+
Kind.NAMED_TYPE
|
|
1582
|
+
]);
|
|
1583
|
+
const LOCAL_STATE_STUBS = /* @__PURE__ */ new Set([
|
|
1584
|
+
"AccountSnapshotLocalState",
|
|
1585
|
+
"BudgetStatementLocalState",
|
|
1586
|
+
"ScopeFrameworkLocalState"
|
|
1587
|
+
]);
|
|
1588
|
+
/** Parse SDL; blank or comment-only input yields an empty document. */
|
|
1589
|
+
function parseSdl(sdl) {
|
|
1590
|
+
const source = new Source(sdl);
|
|
1591
|
+
if (new Lexer(source).advance().kind === TokenKind.EOF) return EMPTY_DOCUMENT;
|
|
1592
|
+
try {
|
|
1593
|
+
return parse(source, { noLocation: true });
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
const withoutSemicolons = sdl.replaceAll(";", "");
|
|
1596
|
+
if (withoutSemicolons === sdl) throw error;
|
|
1597
|
+
return parse(withoutSemicolons, { noLocation: true });
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
function definedTypeNames(doc, kinds) {
|
|
1601
|
+
return doc.definitions.filter((def) => kinds.has(def.kind)).map((def) => def.name.value);
|
|
1602
|
+
}
|
|
1603
|
+
/** Prefix type definition names and type references found in `names`. */
|
|
1604
|
+
function prefixTypeNames(doc, prefix, names) {
|
|
1605
|
+
return visit(doc, { enter(node) {
|
|
1606
|
+
if (!TYPE_NAME_KINDS.has(node.kind)) return void 0;
|
|
1607
|
+
const { name } = node;
|
|
1608
|
+
if (!names.has(name.value)) return void 0;
|
|
1609
|
+
return {
|
|
1610
|
+
...node,
|
|
1611
|
+
name: {
|
|
1612
|
+
...name,
|
|
1613
|
+
value: `${prefix}_${name.value}`
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
} });
|
|
1617
|
+
}
|
|
1618
|
+
/** State schema definitions without inputs, `DateTime` or the given object types. */
|
|
1619
|
+
function stateTypeDefinitions(sdl, droppedObjectTypes = /* @__PURE__ */ new Set()) {
|
|
1620
|
+
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)));
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Prefixed operation schemas per module. The first definition of each module
|
|
1624
|
+
* carries a "Module: <Name>" description unless it has its own.
|
|
1625
|
+
*/
|
|
1626
|
+
function buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema) {
|
|
1627
|
+
const names = new Set(allTypeNames);
|
|
1628
|
+
return specification?.modules.map((module) => {
|
|
1629
|
+
const definitions = module.operations.filter((op) => hasValidSchema(op.schema)).flatMap((op) => {
|
|
1630
|
+
const doc = parseSdl(op.schema ?? "");
|
|
1631
|
+
return prefixTypeNames(doc, documentName, /* @__PURE__ */ new Set([...names, ...definedTypeNames(doc, ALL_PREFIX_KINDS)])).definitions;
|
|
1632
|
+
});
|
|
1633
|
+
if (definitions.length === 0) return "";
|
|
1634
|
+
const [first, ...rest] = definitions;
|
|
1635
|
+
const header = "description" in first && !first.description ? {
|
|
1636
|
+
...first,
|
|
1637
|
+
description: {
|
|
1638
|
+
kind: Kind.STRING,
|
|
1639
|
+
value: `Module: ${pascalCase(module.name)}`,
|
|
1640
|
+
block: true
|
|
1641
|
+
}
|
|
1642
|
+
} : first;
|
|
1643
|
+
return print({
|
|
1644
|
+
kind: Kind.DOCUMENT,
|
|
1645
|
+
definitions: [header, ...rest]
|
|
1646
|
+
});
|
|
1647
|
+
}).filter(Boolean).join("\n") ?? "";
|
|
1648
|
+
}
|
|
1499
1649
|
const buildSubgraphSchemaModule = (documentModels, resolvers, typeDefs) => {
|
|
1500
1650
|
const newResolvers = {
|
|
1501
1651
|
...resolvers,
|
|
@@ -1534,20 +1684,13 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1534
1684
|
const latestSpec = documentModel.global.specifications.at(-1);
|
|
1535
1685
|
const globalSchema = latestSpec?.state.global.schema ?? "";
|
|
1536
1686
|
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;
|
|
1687
|
+
const definitions = [...stateTypeDefinitions(globalSchema), ...stateTypeDefinitions(localSchema, LOCAL_STATE_STUBS)];
|
|
1688
|
+
const stateDoc = {
|
|
1689
|
+
kind: Kind.DOCUMENT,
|
|
1690
|
+
definitions
|
|
1691
|
+
};
|
|
1692
|
+
const prefixed = prefixTypeNames(stateDoc, dmSchemaName, new Set(definedTypeNames(stateDoc, STATE_PREFIX_KINDS)));
|
|
1693
|
+
if (definitions.length > 0) dmSchema += `${print(prefixed)}\n`;
|
|
1551
1694
|
dmSchema += `
|
|
1552
1695
|
type ${dmSchemaName} implements IDocument {
|
|
1553
1696
|
id: String!
|
|
@@ -1562,7 +1705,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1562
1705
|
stateJSON: JSONObject
|
|
1563
1706
|
}\n`;
|
|
1564
1707
|
});
|
|
1565
|
-
|
|
1708
|
+
const schema = gql`
|
|
1566
1709
|
scalar JSONObject
|
|
1567
1710
|
scalar AttachmentRef
|
|
1568
1711
|
# Codegen scalars not in the document-engineering set.
|
|
@@ -1613,7 +1756,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1613
1756
|
operations(first: Int, skip: Int): [Operation!]!
|
|
1614
1757
|
stateJSON: JSONObject
|
|
1615
1758
|
}
|
|
1616
|
-
${dmSchema
|
|
1759
|
+
${dmSchema}
|
|
1617
1760
|
|
|
1618
1761
|
type GqlDocument implements IDocument {
|
|
1619
1762
|
id: String!
|
|
@@ -1638,34 +1781,32 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1638
1781
|
}
|
|
1639
1782
|
|
|
1640
1783
|
${stripScalarDefinitions(typeDefs$1)}
|
|
1641
|
-
|
|
1784
|
+
`;
|
|
1785
|
+
return dedupeTypeDefinitions(schema);
|
|
1642
1786
|
};
|
|
1643
1787
|
/**
|
|
1644
|
-
* Extract
|
|
1645
|
-
*
|
|
1646
|
-
* @returns {string[]} Array of type names
|
|
1788
|
+
* Extract the names of types defined in a GraphQL schema.
|
|
1789
|
+
* Unparseable schemas yield no names.
|
|
1647
1790
|
*/
|
|
1648
1791
|
function extractTypeNames(schema) {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1792
|
+
try {
|
|
1793
|
+
return definedTypeNames(parseSdl(schema), ALL_PREFIX_KINDS);
|
|
1794
|
+
} catch (error) {
|
|
1795
|
+
logger.debug(`Skipping unparseable schema: ${String(error)}`);
|
|
1796
|
+
return [];
|
|
1797
|
+
}
|
|
1652
1798
|
}
|
|
1653
1799
|
/**
|
|
1654
|
-
*
|
|
1655
|
-
* @param
|
|
1656
|
-
* @param {Set<string>} excludeTypeNames - Type names to exclude from extraction
|
|
1657
|
-
* @returns {string} All input type definitions as a string
|
|
1800
|
+
* Print the input type definitions of a GraphQL schema.
|
|
1801
|
+
* @param excludeTypeNames - Input type names to leave out
|
|
1658
1802
|
*/
|
|
1659
1803
|
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");
|
|
1804
|
+
const definitions = parseSdl(schema).definitions.filter((def) => def.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION && !excludeTypeNames.has(def.name.value));
|
|
1805
|
+
if (definitions.length === 0) return "";
|
|
1806
|
+
return print({
|
|
1807
|
+
kind: Kind.DOCUMENT,
|
|
1808
|
+
definitions
|
|
1809
|
+
});
|
|
1669
1810
|
}
|
|
1670
1811
|
/**
|
|
1671
1812
|
* Extract the root state type name from a scope's GraphQL schema.
|
|
@@ -1734,9 +1875,7 @@ function generateStateInputTypes(stateSchema, excludeTypeNames) {
|
|
|
1734
1875
|
case Kind.OBJECT_TYPE_DEFINITION:
|
|
1735
1876
|
objectNames.add(def.name.value);
|
|
1736
1877
|
break;
|
|
1737
|
-
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
|
|
1738
|
-
existingInputNames.add(def.name.value);
|
|
1739
|
-
break;
|
|
1878
|
+
case Kind.INPUT_OBJECT_TYPE_DEFINITION: existingInputNames.add(def.name.value);
|
|
1740
1879
|
}
|
|
1741
1880
|
const inputTypes = [];
|
|
1742
1881
|
for (const def of ast.definitions) {
|
|
@@ -1763,18 +1902,10 @@ function generateStateInputTypes(stateSchema, excludeTypeNames) {
|
|
|
1763
1902
|
*/
|
|
1764
1903
|
function applyGraphQLTypePrefixes(schema, prefix, externalTypeNames = []) {
|
|
1765
1904
|
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;
|
|
1905
|
+
const doc = parseSdl(schema);
|
|
1906
|
+
const names = /* @__PURE__ */ new Set([...definedTypeNames(doc, ALL_PREFIX_KINDS), ...externalTypeNames]);
|
|
1907
|
+
if (names.size === 0) return schema;
|
|
1908
|
+
return print(prefixTypeNames(doc, prefix, names));
|
|
1778
1909
|
}
|
|
1779
1910
|
/**
|
|
1780
1911
|
* Generate a GraphQL schema for a document model.
|
|
@@ -1793,11 +1924,11 @@ function generateDocumentModelSchema(documentModel, options = {}) {
|
|
|
1793
1924
|
const localStateTypeNames = extractTypeNames(localStateSchema ?? "");
|
|
1794
1925
|
const stateTypeNames = [...globalStateTypeNames, ...localStateTypeNames];
|
|
1795
1926
|
const allOperationTypeNames = specification?.modules.flatMap((module) => module.operations.flatMap((op) => extractTypeNames(op.schema ?? ""))) ?? [];
|
|
1796
|
-
const allTypeNames = [
|
|
1797
|
-
const
|
|
1927
|
+
const allTypeNames = [.../* @__PURE__ */ new Set([...stateTypeNames, ...allOperationTypeNames])];
|
|
1928
|
+
const operationInputTypeNames = new Set(allOperationTypeNames);
|
|
1929
|
+
const prefixedStateInputTypes = applyGraphQLTypePrefixes(extractInputTypeDefinitions(globalStateSchema ?? "", operationInputTypeNames), documentName, allTypeNames);
|
|
1798
1930
|
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);
|
|
1931
|
+
if (useNewApi) return generateNewApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema);
|
|
1801
1932
|
return generateLegacyApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema);
|
|
1802
1933
|
}
|
|
1803
1934
|
/**
|
|
@@ -1807,10 +1938,7 @@ function generateLegacyApiSchema(documentName, specification, prefixedStateInput
|
|
|
1807
1938
|
const createDocumentMutation = `${documentName}_createDocument(name:String!, driveId:String): String`;
|
|
1808
1939
|
const operationMutations = specification?.modules.flatMap((module) => module.operations.filter((op) => op.name && hasValidSchema(op.schema)).map((op) => `${documentName}_${camelCase(op.name)}(
|
|
1809
1940
|
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") ?? "";
|
|
1941
|
+
const moduleSchemas = buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema);
|
|
1814
1942
|
return gql`
|
|
1815
1943
|
"""
|
|
1816
1944
|
Queries: ${documentName} Document
|
|
@@ -1847,7 +1975,7 @@ function generateLegacyApiSchema(documentName, specification, prefixedStateInput
|
|
|
1847
1975
|
* in getDocumentModelTypeDefs() which is used during schema composition.
|
|
1848
1976
|
* Including them here would cause duplicate type definitions.
|
|
1849
1977
|
*/
|
|
1850
|
-
function generateNewApiSchema(documentName, specification,
|
|
1978
|
+
function generateNewApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema) {
|
|
1851
1979
|
const stateType = `${documentName}_FullState!`;
|
|
1852
1980
|
const sharedBaseTypes = `
|
|
1853
1981
|
"""Hash configuration for document state"""
|
|
@@ -1890,7 +2018,7 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1890
2018
|
|
|
1891
2019
|
input ${documentName}_SearchFilterInput {
|
|
1892
2020
|
parentId: String
|
|
1893
|
-
identifiers: [String!]
|
|
2021
|
+
identifiers: [String!] @deprecated(reason: "Ignored. Filter by parentId.")
|
|
1894
2022
|
}
|
|
1895
2023
|
`;
|
|
1896
2024
|
const revisionType = RevisionType;
|
|
@@ -1924,7 +2052,6 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1924
2052
|
"""
|
|
1925
2053
|
type ${documentName}_DocumentResultPage {
|
|
1926
2054
|
items: [${documentName}MutationResult!]!
|
|
1927
|
-
totalCount: Int!
|
|
1928
2055
|
hasNextPage: Boolean!
|
|
1929
2056
|
hasPreviousPage: Boolean!
|
|
1930
2057
|
cursor: String
|
|
@@ -1933,7 +2060,7 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1933
2060
|
const queries = `
|
|
1934
2061
|
type ${documentName}Queries {
|
|
1935
2062
|
"""Get a specific ${documentName} document by identifier"""
|
|
1936
|
-
document(identifier: String
|
|
2063
|
+
document(idOrSlug: String, identifier: String @deprecated(reason: "Use idOrSlug."), view: ${documentName}_ViewFilterInput): ${documentName}_DocumentWithChildren
|
|
1937
2064
|
|
|
1938
2065
|
"""Get all ${documentName} documents (paged)"""
|
|
1939
2066
|
documents(paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
|
|
@@ -1942,10 +2069,10 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1942
2069
|
findDocuments(search: ${documentName}_SearchFilterInput, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
|
|
1943
2070
|
|
|
1944
2071
|
"""Get outgoing relationships of a ${documentName} document"""
|
|
1945
|
-
documentOutgoingRelationships(sourceIdentifier: String
|
|
2072
|
+
documentOutgoingRelationships(sourceIdOrSlug: String, sourceIdentifier: String @deprecated(reason: "Use sourceIdOrSlug."), relationshipType: String!, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
|
|
1946
2073
|
|
|
1947
2074
|
"""Get incoming relationships to a ${documentName} document"""
|
|
1948
|
-
documentIncomingRelationships(targetIdentifier: String
|
|
2075
|
+
documentIncomingRelationships(targetIdOrSlug: String, targetIdentifier: String @deprecated(reason: "Use targetIdOrSlug."), relationshipType: String!, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
|
|
1949
2076
|
}
|
|
1950
2077
|
`;
|
|
1951
2078
|
let initialStateInputSchema = "";
|
|
@@ -1976,13 +2103,10 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1976
2103
|
initialStateInputSchema = inputTypeDefs ? `${inputTypeDefs}\n\n${wrapper}` : wrapper;
|
|
1977
2104
|
}
|
|
1978
2105
|
}
|
|
1979
|
-
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
|
-
const createEmptyDocumentMutation = `createEmptyDocument(parentIdentifier: String): ${documentName}MutationResult!`;
|
|
1981
|
-
const operationMutations = specification?.modules.flatMap((module) => module.operations.filter((op) => op.name && hasValidSchema(op.schema)).flatMap((op) => [`${camelCase(op.name)}(docId: PHID
|
|
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") ?? "";
|
|
2106
|
+
const createDocumentMutation = initialStateInputSchema ? `createDocument(name: String!, parentIdOrSlug: String, parentIdentifier: String @deprecated(reason: "Use parentIdOrSlug."), slug: String, preferredEditor: String, initialState: ${documentName}_InitialStateInput): ${documentName}MutationResult!` : `createDocument(name: String!, parentIdOrSlug: String, parentIdentifier: String @deprecated(reason: "Use parentIdOrSlug."), preferredEditor: String): ${documentName}MutationResult!`;
|
|
2107
|
+
const createEmptyDocumentMutation = `createEmptyDocument(parentIdOrSlug: String, parentIdentifier: String @deprecated(reason: "Use parentIdOrSlug.")): ${documentName}MutationResult!`;
|
|
2108
|
+
const operationMutations = specification?.modules.flatMap((module) => module.operations.filter((op) => op.name && hasValidSchema(op.schema)).flatMap((op) => [`${camelCase(op.name)}(documentIdOrSlug: String, docId: PHID @deprecated(reason: "Use documentIdOrSlug."), input: ${documentName}_${pascalCase(op.name)}Input!): ${documentName}MutationResult!`, `${camelCase(op.name)}Async(documentIdOrSlug: String, docId: PHID @deprecated(reason: "Use documentIdOrSlug."), input: ${documentName}_${pascalCase(op.name)}Input!): String!`])).join("\n ") ?? "";
|
|
2109
|
+
const moduleSchemas = buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema);
|
|
1986
2110
|
return gql`
|
|
1987
2111
|
scalar DateTime
|
|
1988
2112
|
scalar JSONObject
|
|
@@ -2135,7 +2259,8 @@ function OperationsFilterInputSchema() {
|
|
|
2135
2259
|
return z$1.object({
|
|
2136
2260
|
actionTypes: z$1.array(z$1.string()).nullish(),
|
|
2137
2261
|
branch: z$1.string().nullish(),
|
|
2138
|
-
documentId: z$1.string(),
|
|
2262
|
+
documentId: z$1.string().nullish(),
|
|
2263
|
+
documentIdOrSlug: z$1.string().nullish(),
|
|
2139
2264
|
scopes: z$1.array(z$1.string()).nullish(),
|
|
2140
2265
|
sinceRevision: z$1.number().nullish(),
|
|
2141
2266
|
timestampFrom: z$1.string().nullish(),
|
|
@@ -2200,11 +2325,19 @@ function SyncEnvelopeInputSchema() {
|
|
|
2200
2325
|
type: SyncEnvelopeTypeSchema
|
|
2201
2326
|
});
|
|
2202
2327
|
}
|
|
2328
|
+
function SyncRefusalInputSchema() {
|
|
2329
|
+
return z$1.object({
|
|
2330
|
+
branch: z$1.string(),
|
|
2331
|
+
documentId: z$1.string(),
|
|
2332
|
+
kind: z$1.string().nullish()
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2203
2335
|
function TouchChannelInputSchema() {
|
|
2204
2336
|
return z$1.object({
|
|
2205
2337
|
collectionId: z$1.string(),
|
|
2206
2338
|
filter: z$1.lazy(() => RemoteFilterInputSchema()),
|
|
2207
2339
|
id: z$1.string(),
|
|
2340
|
+
manifest: z$1.custom((v) => v != null).nullish(),
|
|
2208
2341
|
name: z$1.string(),
|
|
2209
2342
|
sinceTimestampUtcMs: z$1.string()
|
|
2210
2343
|
});
|
|
@@ -2230,6 +2363,16 @@ const PhDocumentFieldsFragmentDoc = gql`
|
|
|
2230
2363
|
lastModifiedAtUtcIso
|
|
2231
2364
|
}
|
|
2232
2365
|
`;
|
|
2366
|
+
const DocumentRelationshipFieldsFragmentDoc = gql`
|
|
2367
|
+
fragment DocumentRelationshipFields on DocumentRelationship {
|
|
2368
|
+
sourceId
|
|
2369
|
+
targetId
|
|
2370
|
+
relationshipType
|
|
2371
|
+
metadata
|
|
2372
|
+
createdAt
|
|
2373
|
+
updatedAt
|
|
2374
|
+
}
|
|
2375
|
+
`;
|
|
2233
2376
|
const GetDocumentModelsDocument = gql`
|
|
2234
2377
|
query GetDocumentModels($namespace: String, $paging: PagingInput) {
|
|
2235
2378
|
documentModels(namespace: $namespace, paging: $paging) {
|
|
@@ -2240,7 +2383,6 @@ const GetDocumentModelsDocument = gql`
|
|
|
2240
2383
|
version
|
|
2241
2384
|
specification
|
|
2242
2385
|
}
|
|
2243
|
-
totalCount
|
|
2244
2386
|
hasNextPage
|
|
2245
2387
|
hasPreviousPage
|
|
2246
2388
|
cursor
|
|
@@ -2299,7 +2441,6 @@ const GetDocumentWithOperationsDocument = gql`
|
|
|
2299
2441
|
}
|
|
2300
2442
|
}
|
|
2301
2443
|
}
|
|
2302
|
-
totalCount
|
|
2303
2444
|
hasNextPage
|
|
2304
2445
|
hasPreviousPage
|
|
2305
2446
|
cursor
|
|
@@ -2326,7 +2467,6 @@ const GetDocumentOutgoingRelationshipsDocument = gql`
|
|
|
2326
2467
|
items {
|
|
2327
2468
|
...PHDocumentFields
|
|
2328
2469
|
}
|
|
2329
|
-
totalCount
|
|
2330
2470
|
hasNextPage
|
|
2331
2471
|
hasPreviousPage
|
|
2332
2472
|
cursor
|
|
@@ -2350,7 +2490,6 @@ const GetDocumentIncomingRelationshipsDocument = gql`
|
|
|
2350
2490
|
items {
|
|
2351
2491
|
...PHDocumentFields
|
|
2352
2492
|
}
|
|
2353
|
-
totalCount
|
|
2354
2493
|
hasNextPage
|
|
2355
2494
|
hasPreviousPage
|
|
2356
2495
|
cursor
|
|
@@ -2358,6 +2497,52 @@ const GetDocumentIncomingRelationshipsDocument = gql`
|
|
|
2358
2497
|
}
|
|
2359
2498
|
${PhDocumentFieldsFragmentDoc}
|
|
2360
2499
|
`;
|
|
2500
|
+
const GetDocumentOutgoingRelationshipEdgesDocument = gql`
|
|
2501
|
+
query GetDocumentOutgoingRelationshipEdges(
|
|
2502
|
+
$sourceIdentifier: String!
|
|
2503
|
+
$relationshipType: String
|
|
2504
|
+
$view: ViewFilterInput
|
|
2505
|
+
$paging: PagingInput
|
|
2506
|
+
) {
|
|
2507
|
+
documentOutgoingRelationshipEdges(
|
|
2508
|
+
sourceIdentifier: $sourceIdentifier
|
|
2509
|
+
relationshipType: $relationshipType
|
|
2510
|
+
view: $view
|
|
2511
|
+
paging: $paging
|
|
2512
|
+
) {
|
|
2513
|
+
items {
|
|
2514
|
+
...DocumentRelationshipFields
|
|
2515
|
+
}
|
|
2516
|
+
hasNextPage
|
|
2517
|
+
hasPreviousPage
|
|
2518
|
+
cursor
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
${DocumentRelationshipFieldsFragmentDoc}
|
|
2522
|
+
`;
|
|
2523
|
+
const GetDocumentIncomingRelationshipEdgesDocument = gql`
|
|
2524
|
+
query GetDocumentIncomingRelationshipEdges(
|
|
2525
|
+
$targetIdentifier: String!
|
|
2526
|
+
$relationshipType: String
|
|
2527
|
+
$view: ViewFilterInput
|
|
2528
|
+
$paging: PagingInput
|
|
2529
|
+
) {
|
|
2530
|
+
documentIncomingRelationshipEdges(
|
|
2531
|
+
targetIdentifier: $targetIdentifier
|
|
2532
|
+
relationshipType: $relationshipType
|
|
2533
|
+
view: $view
|
|
2534
|
+
paging: $paging
|
|
2535
|
+
) {
|
|
2536
|
+
items {
|
|
2537
|
+
...DocumentRelationshipFields
|
|
2538
|
+
}
|
|
2539
|
+
hasNextPage
|
|
2540
|
+
hasPreviousPage
|
|
2541
|
+
cursor
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
${DocumentRelationshipFieldsFragmentDoc}
|
|
2545
|
+
`;
|
|
2361
2546
|
const FindDocumentsDocument = gql`
|
|
2362
2547
|
query FindDocuments(
|
|
2363
2548
|
$search: SearchFilterInput
|
|
@@ -2368,7 +2553,6 @@ const FindDocumentsDocument = gql`
|
|
|
2368
2553
|
items {
|
|
2369
2554
|
...PHDocumentFields
|
|
2370
2555
|
}
|
|
2371
|
-
totalCount
|
|
2372
2556
|
hasNextPage
|
|
2373
2557
|
hasPreviousPage
|
|
2374
2558
|
cursor
|
|
@@ -2412,7 +2596,6 @@ const GetDocumentOperationsDocument = gql`
|
|
|
2412
2596
|
}
|
|
2413
2597
|
}
|
|
2414
2598
|
}
|
|
2415
|
-
totalCount
|
|
2416
2599
|
hasNextPage
|
|
2417
2600
|
hasPreviousPage
|
|
2418
2601
|
cursor
|
|
@@ -2548,12 +2731,34 @@ const AddRelationshipDocument = gql`
|
|
|
2548
2731
|
$sourceIdentifier: String!
|
|
2549
2732
|
$targetIdentifier: String!
|
|
2550
2733
|
$relationshipType: String!
|
|
2734
|
+
$metadata: JSONObject
|
|
2551
2735
|
$branch: String
|
|
2552
2736
|
) {
|
|
2553
2737
|
addRelationship(
|
|
2554
2738
|
sourceIdentifier: $sourceIdentifier
|
|
2555
2739
|
targetIdentifier: $targetIdentifier
|
|
2556
2740
|
relationshipType: $relationshipType
|
|
2741
|
+
metadata: $metadata
|
|
2742
|
+
branch: $branch
|
|
2743
|
+
) {
|
|
2744
|
+
...PHDocumentFields
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
${PhDocumentFieldsFragmentDoc}
|
|
2748
|
+
`;
|
|
2749
|
+
const UpdateRelationshipDocument = gql`
|
|
2750
|
+
mutation UpdateRelationship(
|
|
2751
|
+
$sourceIdentifier: String!
|
|
2752
|
+
$targetIdentifier: String!
|
|
2753
|
+
$relationshipType: String!
|
|
2754
|
+
$metadata: JSONObject
|
|
2755
|
+
$branch: String
|
|
2756
|
+
) {
|
|
2757
|
+
updateRelationship(
|
|
2758
|
+
sourceIdentifier: $sourceIdentifier
|
|
2759
|
+
targetIdentifier: $targetIdentifier
|
|
2760
|
+
relationshipType: $relationshipType
|
|
2761
|
+
metadata: $metadata
|
|
2557
2762
|
branch: $branch
|
|
2558
2763
|
) {
|
|
2559
2764
|
...PHDocumentFields
|
|
@@ -2650,11 +2855,15 @@ const PollSyncEnvelopesDocument = gql`
|
|
|
2650
2855
|
$channelId: String!
|
|
2651
2856
|
$outboxAck: Int!
|
|
2652
2857
|
$outboxLatest: Int!
|
|
2858
|
+
$manifestRevision: String
|
|
2859
|
+
$refusals: [SyncRefusalInput!]
|
|
2653
2860
|
) {
|
|
2654
2861
|
pollSyncEnvelopes(
|
|
2655
2862
|
channelId: $channelId
|
|
2656
2863
|
outboxAck: $outboxAck
|
|
2657
2864
|
outboxLatest: $outboxLatest
|
|
2865
|
+
manifestRevision: $manifestRevision
|
|
2866
|
+
refusals: $refusals
|
|
2658
2867
|
) {
|
|
2659
2868
|
envelopes {
|
|
2660
2869
|
type
|
|
@@ -2714,6 +2923,8 @@ const PollSyncEnvelopesDocument = gql`
|
|
|
2714
2923
|
errorType
|
|
2715
2924
|
}
|
|
2716
2925
|
hasMore
|
|
2926
|
+
manifestRevision
|
|
2927
|
+
peerManifestRevision
|
|
2717
2928
|
}
|
|
2718
2929
|
}
|
|
2719
2930
|
`;
|
|
@@ -2722,12 +2933,19 @@ const TouchChannelDocument = gql`
|
|
|
2722
2933
|
touchChannel(input: $input) {
|
|
2723
2934
|
success
|
|
2724
2935
|
ackOrdinal
|
|
2936
|
+
manifest
|
|
2725
2937
|
}
|
|
2726
2938
|
}
|
|
2727
2939
|
`;
|
|
2728
2940
|
const PushSyncEnvelopesDocument = gql`
|
|
2729
|
-
mutation PushSyncEnvelopes(
|
|
2730
|
-
|
|
2941
|
+
mutation PushSyncEnvelopes(
|
|
2942
|
+
$envelopes: [SyncEnvelopeInput!]!
|
|
2943
|
+
$peerManifestRevision: String
|
|
2944
|
+
) {
|
|
2945
|
+
pushSyncEnvelopes(
|
|
2946
|
+
envelopes: $envelopes
|
|
2947
|
+
peerManifestRevision: $peerManifestRevision
|
|
2948
|
+
)
|
|
2731
2949
|
}
|
|
2732
2950
|
`;
|
|
2733
2951
|
function getSdk(requester) {
|
|
@@ -2747,6 +2965,12 @@ function getSdk(requester) {
|
|
|
2747
2965
|
GetDocumentIncomingRelationships(variables, options) {
|
|
2748
2966
|
return requester(GetDocumentIncomingRelationshipsDocument, variables, options);
|
|
2749
2967
|
},
|
|
2968
|
+
GetDocumentOutgoingRelationshipEdges(variables, options) {
|
|
2969
|
+
return requester(GetDocumentOutgoingRelationshipEdgesDocument, variables, options);
|
|
2970
|
+
},
|
|
2971
|
+
GetDocumentIncomingRelationshipEdges(variables, options) {
|
|
2972
|
+
return requester(GetDocumentIncomingRelationshipEdgesDocument, variables, options);
|
|
2973
|
+
},
|
|
2750
2974
|
FindDocuments(variables, options) {
|
|
2751
2975
|
return requester(FindDocumentsDocument, variables, options);
|
|
2752
2976
|
},
|
|
@@ -2780,6 +3004,9 @@ function getSdk(requester) {
|
|
|
2780
3004
|
AddRelationship(variables, options) {
|
|
2781
3005
|
return requester(AddRelationshipDocument, variables, options);
|
|
2782
3006
|
},
|
|
3007
|
+
UpdateRelationship(variables, options) {
|
|
3008
|
+
return requester(UpdateRelationshipDocument, variables, options);
|
|
3009
|
+
},
|
|
2783
3010
|
RemoveRelationship(variables, options) {
|
|
2784
3011
|
return requester(RemoveRelationshipDocument, variables, options);
|
|
2785
3012
|
},
|
|
@@ -2820,8 +3047,7 @@ function toDocumentModelResultPage(result) {
|
|
|
2820
3047
|
cursor: result.nextCursor ?? null,
|
|
2821
3048
|
hasNextPage: !!result.nextCursor,
|
|
2822
3049
|
hasPreviousPage: !!result.options.cursor,
|
|
2823
|
-
items: models.map(toGqlDocumentModelState)
|
|
2824
|
-
totalCount: result.results.length
|
|
3050
|
+
items: models.map(toGqlDocumentModelState)
|
|
2825
3051
|
};
|
|
2826
3052
|
}
|
|
2827
3053
|
/**
|
|
@@ -2853,8 +3079,32 @@ function toPhDocumentResultPage(result) {
|
|
|
2853
3079
|
cursor: result.nextCursor ?? null,
|
|
2854
3080
|
hasNextPage: !!result.nextCursor,
|
|
2855
3081
|
hasPreviousPage: !!result.options.cursor,
|
|
2856
|
-
items: result.results.map(toGqlPhDocument)
|
|
2857
|
-
|
|
3082
|
+
items: result.results.map(toGqlPhDocument)
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
/**
|
|
3086
|
+
* Converts a relationship edge from the indexer to GraphQL format.
|
|
3087
|
+
*/
|
|
3088
|
+
function toGqlDocumentRelationship(relationship) {
|
|
3089
|
+
return {
|
|
3090
|
+
sourceId: relationship.sourceId,
|
|
3091
|
+
targetId: relationship.targetId,
|
|
3092
|
+
relationshipType: relationship.relationshipType,
|
|
3093
|
+
metadata: relationship.metadata ?? null,
|
|
3094
|
+
createdAt: relationship.createdAt,
|
|
3095
|
+
updatedAt: relationship.updatedAt
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
/**
|
|
3099
|
+
* Converts a PagedResults of relationship edges to GraphQL
|
|
3100
|
+
* DocumentRelationshipResultPage format
|
|
3101
|
+
*/
|
|
3102
|
+
function toDocumentRelationshipResultPage(result) {
|
|
3103
|
+
return {
|
|
3104
|
+
cursor: result.nextCursor ?? null,
|
|
3105
|
+
hasNextPage: !!result.nextCursor,
|
|
3106
|
+
hasPreviousPage: !!result.options.cursor,
|
|
3107
|
+
items: result.results.map(toGqlDocumentRelationship)
|
|
2858
3108
|
};
|
|
2859
3109
|
}
|
|
2860
3110
|
/**
|
|
@@ -2904,8 +3154,8 @@ function fromInputMaybe(value) {
|
|
|
2904
3154
|
function toReactorPropagationMode(gqlMode) {
|
|
2905
3155
|
if (gqlMode == null) return;
|
|
2906
3156
|
switch (gqlMode) {
|
|
2907
|
-
case
|
|
2908
|
-
case
|
|
3157
|
+
case "CASCADE": return PropagationMode$1.Cascade;
|
|
3158
|
+
case "ORPHAN": return PropagationMode$1.None;
|
|
2909
3159
|
}
|
|
2910
3160
|
}
|
|
2911
3161
|
/**
|
|
@@ -2915,10 +3165,10 @@ function toReactorPropagationMode(gqlMode) {
|
|
|
2915
3165
|
*/
|
|
2916
3166
|
function toGqlActionEvaluation(evaluation) {
|
|
2917
3167
|
return evaluation.decision === "allow" ? {
|
|
2918
|
-
decision:
|
|
3168
|
+
decision: "ALLOW",
|
|
2919
3169
|
reason: null
|
|
2920
3170
|
} : {
|
|
2921
|
-
decision:
|
|
3171
|
+
decision: "DENY",
|
|
2922
3172
|
reason: evaluation.reason
|
|
2923
3173
|
};
|
|
2924
3174
|
}
|
|
@@ -3123,8 +3373,7 @@ function toOperationResultPage(result) {
|
|
|
3123
3373
|
cursor: result.nextCursor ?? null,
|
|
3124
3374
|
hasNextPage: !!result.nextCursor,
|
|
3125
3375
|
hasPreviousPage: !!result.options.cursor && result.options.cursor !== "0",
|
|
3126
|
-
items: result.results.map(serializeOperationForGraphQL)
|
|
3127
|
-
totalCount: result.results.length
|
|
3376
|
+
items: result.results.map(serializeOperationForGraphQL)
|
|
3128
3377
|
};
|
|
3129
3378
|
}
|
|
3130
3379
|
function toGqlDocumentChangeEvent(event) {
|
|
@@ -3196,6 +3445,21 @@ function pickDriveClient(reactorClient, driveContainerType, reactorDriveClient)
|
|
|
3196
3445
|
}
|
|
3197
3446
|
return reactorClient.drives;
|
|
3198
3447
|
}
|
|
3448
|
+
/**
|
|
3449
|
+
* A mutation's document re-read as the caller, since the client reads it back
|
|
3450
|
+
* as its signer. A caller who may write but not read gets it stripped.
|
|
3451
|
+
*/
|
|
3452
|
+
async function readAsCaller(reactorClient, document, subject, branch) {
|
|
3453
|
+
if (!subject) return document;
|
|
3454
|
+
try {
|
|
3455
|
+
return await reactorClient.get(document.header.id, {
|
|
3456
|
+
subject,
|
|
3457
|
+
branch
|
|
3458
|
+
});
|
|
3459
|
+
} catch (error) {
|
|
3460
|
+
throw new GraphQLError(`Failed to read the document back: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3199
3463
|
async function documentModels(reactorClient, args) {
|
|
3200
3464
|
const namespace = fromInputMaybe(args.namespace);
|
|
3201
3465
|
let paging;
|
|
@@ -3228,13 +3492,13 @@ async function document(reactorClient, args, subject) {
|
|
|
3228
3492
|
};
|
|
3229
3493
|
let result;
|
|
3230
3494
|
try {
|
|
3231
|
-
result = await reactorClient.get(args.
|
|
3495
|
+
result = await reactorClient.get(args.idOrSlug, view);
|
|
3232
3496
|
} catch (error) {
|
|
3233
3497
|
throw new GraphQLError(`Failed to fetch document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3234
3498
|
}
|
|
3235
3499
|
let children;
|
|
3236
3500
|
try {
|
|
3237
|
-
children = await reactorClient.getOutgoingRelationships(args.
|
|
3501
|
+
children = await reactorClient.getOutgoingRelationships(args.idOrSlug, "child", view);
|
|
3238
3502
|
} catch (error) {
|
|
3239
3503
|
throw new GraphQLError(`Failed to fetch children: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3240
3504
|
}
|
|
@@ -3265,7 +3529,7 @@ async function documentOutgoingRelationships(reactorClient, args, subject) {
|
|
|
3265
3529
|
}
|
|
3266
3530
|
let result;
|
|
3267
3531
|
try {
|
|
3268
|
-
result = await reactorClient.getOutgoingRelationships(args.
|
|
3532
|
+
result = await reactorClient.getOutgoingRelationships(args.sourceIdOrSlug, args.relationshipType, view, paging);
|
|
3269
3533
|
} catch (error) {
|
|
3270
3534
|
throw new GraphQLError(`Failed to fetch outgoing relationships: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3271
3535
|
}
|
|
@@ -3293,7 +3557,7 @@ async function documentIncomingRelationships(reactorClient, args, subject) {
|
|
|
3293
3557
|
}
|
|
3294
3558
|
let result;
|
|
3295
3559
|
try {
|
|
3296
|
-
result = await reactorClient.getIncomingRelationships(args.
|
|
3560
|
+
result = await reactorClient.getIncomingRelationships(args.targetIdOrSlug, args.relationshipType, view, paging);
|
|
3297
3561
|
} catch (error) {
|
|
3298
3562
|
throw new GraphQLError(`Failed to fetch incoming relationships: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3299
3563
|
}
|
|
@@ -3303,6 +3567,54 @@ async function documentIncomingRelationships(reactorClient, args, subject) {
|
|
|
3303
3567
|
throw new GraphQLError(`Failed to convert incoming relationships to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3304
3568
|
}
|
|
3305
3569
|
}
|
|
3570
|
+
async function documentOutgoingRelationshipEdges(reactorClient, args, subject) {
|
|
3571
|
+
const view = toRelationshipViewFilter(args.view, subject);
|
|
3572
|
+
const paging = toRelationshipPagingOptions(args.paging);
|
|
3573
|
+
let result;
|
|
3574
|
+
try {
|
|
3575
|
+
result = await reactorClient.getOutgoingRelationshipEdges(args.sourceIdOrSlug, fromInputMaybe(args.relationshipType), view, paging);
|
|
3576
|
+
} catch (error) {
|
|
3577
|
+
throw new GraphQLError(`Failed to fetch outgoing relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3578
|
+
}
|
|
3579
|
+
try {
|
|
3580
|
+
return toDocumentRelationshipResultPage(result);
|
|
3581
|
+
} catch (error) {
|
|
3582
|
+
throw new GraphQLError(`Failed to convert outgoing relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
async function documentIncomingRelationshipEdges(reactorClient, args, subject) {
|
|
3586
|
+
const view = toRelationshipViewFilter(args.view, subject);
|
|
3587
|
+
const paging = toRelationshipPagingOptions(args.paging);
|
|
3588
|
+
let result;
|
|
3589
|
+
try {
|
|
3590
|
+
result = await reactorClient.getIncomingRelationshipEdges(args.targetIdOrSlug, fromInputMaybe(args.relationshipType), view, paging);
|
|
3591
|
+
} catch (error) {
|
|
3592
|
+
throw new GraphQLError(`Failed to fetch incoming relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3593
|
+
}
|
|
3594
|
+
try {
|
|
3595
|
+
return toDocumentRelationshipResultPage(result);
|
|
3596
|
+
} catch (error) {
|
|
3597
|
+
throw new GraphQLError(`Failed to convert incoming relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
function toRelationshipViewFilter(input, subject) {
|
|
3601
|
+
if (!input) return subject ? { subject } : void 0;
|
|
3602
|
+
return {
|
|
3603
|
+
subject,
|
|
3604
|
+
branch: fromInputMaybe(input.branch),
|
|
3605
|
+
scopes: toMutableArray(fromInputMaybe(input.scopes))
|
|
3606
|
+
};
|
|
3607
|
+
}
|
|
3608
|
+
function toRelationshipPagingOptions(input) {
|
|
3609
|
+
if (!input) return;
|
|
3610
|
+
const cursor = fromInputMaybe(input.cursor);
|
|
3611
|
+
const limit = fromInputMaybe(input.limit);
|
|
3612
|
+
if (!cursor && !limit) return;
|
|
3613
|
+
return {
|
|
3614
|
+
cursor: cursor || "",
|
|
3615
|
+
limit: limit || 10
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3306
3618
|
async function findDocuments(reactorClient, args, subject) {
|
|
3307
3619
|
let view = subject ? { subject } : void 0;
|
|
3308
3620
|
if (args.view) view = {
|
|
@@ -3335,13 +3647,42 @@ async function findDocuments(reactorClient, args, subject) {
|
|
|
3335
3647
|
throw new GraphQLError(`Failed to convert documents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3336
3648
|
}
|
|
3337
3649
|
}
|
|
3338
|
-
|
|
3650
|
+
/**
|
|
3651
|
+
* The answer for a job the caller may not see, shaped as the reactor answers
|
|
3652
|
+
* an unknown one so that the two cannot be told apart.
|
|
3653
|
+
*/
|
|
3654
|
+
function unknownJob(jobId) {
|
|
3655
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3656
|
+
return {
|
|
3657
|
+
id: jobId,
|
|
3658
|
+
documentId: "",
|
|
3659
|
+
status: JobStatus.FAILED,
|
|
3660
|
+
createdAtUtcIso: now,
|
|
3661
|
+
completedAtUtcIso: now,
|
|
3662
|
+
error: {
|
|
3663
|
+
name: "Error",
|
|
3664
|
+
message: "Job not found",
|
|
3665
|
+
stack: ""
|
|
3666
|
+
},
|
|
3667
|
+
consistencyToken: {
|
|
3668
|
+
version: 1,
|
|
3669
|
+
createdAtUtcIso: now,
|
|
3670
|
+
coordinates: []
|
|
3671
|
+
},
|
|
3672
|
+
meta: {
|
|
3673
|
+
batchId: jobId,
|
|
3674
|
+
batchJobIds: [jobId]
|
|
3675
|
+
}
|
|
3676
|
+
};
|
|
3677
|
+
}
|
|
3678
|
+
async function jobStatus(reactorClient, args, serves) {
|
|
3339
3679
|
let result;
|
|
3340
3680
|
try {
|
|
3341
3681
|
result = await reactorClient.getJobStatus(args.jobId);
|
|
3342
3682
|
} catch (error) {
|
|
3343
3683
|
throw new GraphQLError(`Failed to fetch job status: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3344
3684
|
}
|
|
3685
|
+
if (!await serves(result.documentId)) result = unknownJob(args.jobId);
|
|
3345
3686
|
try {
|
|
3346
3687
|
return toGqlJobInfo(result);
|
|
3347
3688
|
} catch (error) {
|
|
@@ -3379,7 +3720,7 @@ async function documentOperations(reactorClient, args, subject) {
|
|
|
3379
3720
|
}
|
|
3380
3721
|
let result;
|
|
3381
3722
|
try {
|
|
3382
|
-
result = await reactorClient.getOperations(args.filter.
|
|
3723
|
+
result = await reactorClient.getOperations(args.filter.documentIdOrSlug, view, operationFilter, paging);
|
|
3383
3724
|
} catch (error) {
|
|
3384
3725
|
throw new GraphQLError(`Failed to fetch document operations: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3385
3726
|
}
|
|
@@ -3411,7 +3752,7 @@ async function evaluateActions(reactorClient, args, subject) {
|
|
|
3411
3752
|
}));
|
|
3412
3753
|
let result;
|
|
3413
3754
|
try {
|
|
3414
|
-
result = await reactorClient.evaluateActions(args.
|
|
3755
|
+
result = await reactorClient.evaluateActions(args.documentIdOrSlug, branch, candidates, subject);
|
|
3415
3756
|
} catch (error) {
|
|
3416
3757
|
if (AuthEnforcementDisabledError.isError(error)) throw new AuthEvaluationUnsupportedError();
|
|
3417
3758
|
throw new GraphQLError(`Failed to evaluate actions: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -3424,51 +3765,54 @@ async function evaluateActions(reactorClient, args, subject) {
|
|
|
3424
3765
|
anyDenied: result.anyDenied
|
|
3425
3766
|
};
|
|
3426
3767
|
}
|
|
3427
|
-
async function createDocument(reactorClient, args, reactorDriveClient) {
|
|
3768
|
+
async function createDocument(reactorClient, args, reactorDriveClient, subject) {
|
|
3428
3769
|
if (!args.document || typeof args.document !== "object") throw new GraphQLError("Invalid document: must be an object");
|
|
3429
3770
|
const document = args.document;
|
|
3430
3771
|
if (!document.header || typeof document.header !== "object") throw new GraphQLError("Invalid document: missing or invalid header");
|
|
3431
|
-
const
|
|
3772
|
+
const parentIdOrSlug = fromInputMaybe(args.parentIdOrSlug);
|
|
3432
3773
|
let result;
|
|
3433
3774
|
try {
|
|
3434
|
-
if (
|
|
3435
|
-
const parent = await reactorClient.get(
|
|
3436
|
-
if (isDriveContainerType(parent.header.documentType)) result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(
|
|
3437
|
-
else result = await reactorClient.create(document,
|
|
3775
|
+
if (parentIdOrSlug) {
|
|
3776
|
+
const parent = await reactorClient.get(parentIdOrSlug, subject && { subject });
|
|
3777
|
+
if (isDriveContainerType(parent.header.documentType)) result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parent.header.id, document);
|
|
3778
|
+
else result = await reactorClient.create(document, parentIdOrSlug);
|
|
3438
3779
|
} else result = await reactorClient.create(document);
|
|
3439
3780
|
} catch (error) {
|
|
3440
3781
|
throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3441
3782
|
}
|
|
3783
|
+
result = await readAsCaller(reactorClient, result, subject);
|
|
3442
3784
|
try {
|
|
3443
3785
|
return toGqlPhDocument(result);
|
|
3444
3786
|
} catch (error) {
|
|
3445
3787
|
throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3446
3788
|
}
|
|
3447
3789
|
}
|
|
3448
|
-
async function createEmptyDocument(reactorClient, args, reactorDriveClient) {
|
|
3449
|
-
const
|
|
3790
|
+
async function createEmptyDocument(reactorClient, args, reactorDriveClient, subject) {
|
|
3791
|
+
const parentIdOrSlug = fromInputMaybe(args.parentIdOrSlug);
|
|
3450
3792
|
const name = fromInputMaybe(args.name);
|
|
3451
3793
|
let result;
|
|
3452
3794
|
try {
|
|
3453
|
-
if (
|
|
3454
|
-
const parent = await reactorClient.get(
|
|
3795
|
+
if (parentIdOrSlug) {
|
|
3796
|
+
const parent = await reactorClient.get(parentIdOrSlug, subject && { subject });
|
|
3455
3797
|
if (isDriveContainerType(parent.header.documentType)) {
|
|
3456
|
-
const
|
|
3798
|
+
const module = await reactorClient.getDocumentModelModule(args.documentType);
|
|
3799
|
+
const document = withSignaturePolicy(module.utils.createDocument(), await reactorClient.getCreateSignaturePolicy());
|
|
3457
3800
|
if (name) document.header.name = name;
|
|
3458
|
-
result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(
|
|
3459
|
-
} else result = await reactorClient.createEmpty(args.documentType, { parentIdentifier });
|
|
3801
|
+
result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parent.header.id, document);
|
|
3802
|
+
} else result = await reactorClient.createEmpty(args.documentType, { parentIdentifier: parentIdOrSlug });
|
|
3460
3803
|
} else result = await reactorClient.createEmpty(args.documentType, {});
|
|
3461
3804
|
} catch (error) {
|
|
3462
3805
|
throw new GraphQLError(`Failed to create empty document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3463
3806
|
}
|
|
3807
|
+
result = await readAsCaller(reactorClient, result, subject);
|
|
3464
3808
|
try {
|
|
3465
3809
|
return toGqlPhDocument(result);
|
|
3466
3810
|
} catch (error) {
|
|
3467
3811
|
throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3468
3812
|
}
|
|
3469
3813
|
}
|
|
3470
|
-
async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient) {
|
|
3471
|
-
const
|
|
3814
|
+
async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient, subject) {
|
|
3815
|
+
const parentIdOrSlug = fromInputMaybe(args.parentIdOrSlug);
|
|
3472
3816
|
const name = fromInputMaybe(args.name);
|
|
3473
3817
|
const slug = fromInputMaybe(args.slug);
|
|
3474
3818
|
const preferredEditor = fromInputMaybe(args.preferredEditor);
|
|
@@ -3478,7 +3822,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
|
|
|
3478
3822
|
} catch (error) {
|
|
3479
3823
|
throw new GraphQLError(`Document model not found for type ${args.documentType}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3480
3824
|
}
|
|
3481
|
-
const document = module.utils.createDocument();
|
|
3825
|
+
const document = withSignaturePolicy(module.utils.createDocument(), await reactorClient.getCreateSignaturePolicy());
|
|
3482
3826
|
const allowedScopes = new Set(Object.keys(module.documentModel.global.specifications.at(-1)?.state ?? {}));
|
|
3483
3827
|
const state = document.state;
|
|
3484
3828
|
for (const [scope, scopeState] of Object.entries(args.initialState)) if (allowedScopes.has(scope) && scope in state) state[scope] = {
|
|
@@ -3492,22 +3836,22 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
|
|
|
3492
3836
|
preferredEditor
|
|
3493
3837
|
};
|
|
3494
3838
|
let result;
|
|
3495
|
-
if (
|
|
3839
|
+
if (parentIdOrSlug) {
|
|
3496
3840
|
let parent;
|
|
3497
3841
|
try {
|
|
3498
|
-
parent = await reactorClient.get(
|
|
3842
|
+
parent = await reactorClient.get(parentIdOrSlug, subject && { subject });
|
|
3499
3843
|
} catch (error) {
|
|
3500
3844
|
throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3501
3845
|
}
|
|
3502
3846
|
if (isDriveContainerType(parent.header.documentType)) {
|
|
3503
3847
|
const driveClient = pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient);
|
|
3504
3848
|
try {
|
|
3505
|
-
result = await driveClient.addFile(
|
|
3849
|
+
result = await driveClient.addFile(parent.header.id, document);
|
|
3506
3850
|
} catch (error) {
|
|
3507
3851
|
throw new GraphQLError(`Failed to create document in drive: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3508
3852
|
}
|
|
3509
3853
|
} else try {
|
|
3510
|
-
result = await reactorClient.create(document,
|
|
3854
|
+
result = await reactorClient.create(document, parentIdOrSlug);
|
|
3511
3855
|
} catch (error) {
|
|
3512
3856
|
throw new GraphQLError(`Failed to create document with parent: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3513
3857
|
}
|
|
@@ -3516,6 +3860,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
|
|
|
3516
3860
|
} catch (error) {
|
|
3517
3861
|
throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3518
3862
|
}
|
|
3863
|
+
result = await readAsCaller(reactorClient, result, subject);
|
|
3519
3864
|
try {
|
|
3520
3865
|
return toGqlPhDocument(result);
|
|
3521
3866
|
} catch (error) {
|
|
@@ -3532,12 +3877,12 @@ const DEFAULT_BRANCH = "main";
|
|
|
3532
3877
|
* the schema cannot express: a signature travels as one string, and verification
|
|
3533
3878
|
* reads it as the tuple it was.
|
|
3534
3879
|
*/
|
|
3535
|
-
async function execute(reactorClient, args) {
|
|
3880
|
+
async function execute(reactorClient, args, subject) {
|
|
3536
3881
|
const actions = toSubmittableActions(args.actions);
|
|
3537
3882
|
const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
|
|
3538
3883
|
let result;
|
|
3539
3884
|
try {
|
|
3540
|
-
result = await reactorClient.execute(args.
|
|
3885
|
+
result = await reactorClient.execute(args.documentIdOrSlug, branch, actions, void 0, subject);
|
|
3541
3886
|
} catch (error) {
|
|
3542
3887
|
throw new GraphQLError(`Failed to execute actions: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3543
3888
|
}
|
|
@@ -3559,13 +3904,13 @@ async function executeAsync(reactorClient, args) {
|
|
|
3559
3904
|
const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
|
|
3560
3905
|
let job;
|
|
3561
3906
|
try {
|
|
3562
|
-
job = await reactorClient.executeAsync(args.
|
|
3907
|
+
job = await reactorClient.executeAsync(args.documentIdOrSlug, branch, actions);
|
|
3563
3908
|
} catch (error) {
|
|
3564
3909
|
throw new GraphQLError(`Failed to submit actions: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3565
3910
|
}
|
|
3566
3911
|
return toGqlJobInfo(job);
|
|
3567
3912
|
}
|
|
3568
|
-
async function mutateDocument(reactorClient, args) {
|
|
3913
|
+
async function mutateDocument(reactorClient, args, subject) {
|
|
3569
3914
|
let validatedActions;
|
|
3570
3915
|
try {
|
|
3571
3916
|
validatedActions = validateActions(args.actions);
|
|
@@ -3576,7 +3921,7 @@ async function mutateDocument(reactorClient, args) {
|
|
|
3576
3921
|
const branch = args.view?.branch ?? "main";
|
|
3577
3922
|
let result;
|
|
3578
3923
|
try {
|
|
3579
|
-
result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions);
|
|
3924
|
+
result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions, void 0, subject);
|
|
3580
3925
|
} catch (error) {
|
|
3581
3926
|
throw new GraphQLError(`Failed to mutate document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3582
3927
|
}
|
|
@@ -3603,71 +3948,96 @@ async function mutateDocumentAsync(reactorClient, args) {
|
|
|
3603
3948
|
}
|
|
3604
3949
|
return result.id;
|
|
3605
3950
|
}
|
|
3606
|
-
async function renameDocument(reactorClient, args, signal) {
|
|
3951
|
+
async function renameDocument(reactorClient, args, signal, subject) {
|
|
3607
3952
|
const branch = fromInputMaybe(args.branch);
|
|
3608
3953
|
let result;
|
|
3609
3954
|
try {
|
|
3610
|
-
result = await reactorClient.rename(args.
|
|
3955
|
+
result = await reactorClient.rename(args.documentIdOrSlug, args.name, branch, signal);
|
|
3611
3956
|
} catch (error) {
|
|
3612
3957
|
throw new GraphQLError(`Failed to rename document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3613
3958
|
}
|
|
3959
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3614
3960
|
try {
|
|
3615
3961
|
return toGqlPhDocument(result);
|
|
3616
3962
|
} catch (error) {
|
|
3617
3963
|
throw new GraphQLError(`Failed to convert renamed document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3618
3964
|
}
|
|
3619
3965
|
}
|
|
3620
|
-
async function setPreferredEditor(reactorClient, args, signal) {
|
|
3966
|
+
async function setPreferredEditor(reactorClient, args, signal, subject) {
|
|
3621
3967
|
const branch = fromInputMaybe(args.branch);
|
|
3622
3968
|
const preferredEditor = fromInputMaybe(args.preferredEditor) ?? null;
|
|
3623
3969
|
let result;
|
|
3624
3970
|
try {
|
|
3625
|
-
result = await reactorClient.setPreferredEditor(args.
|
|
3971
|
+
result = await reactorClient.setPreferredEditor(args.documentIdOrSlug, preferredEditor, branch, signal);
|
|
3626
3972
|
} catch (error) {
|
|
3627
3973
|
throw new GraphQLError(`Failed to set preferred editor: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3628
3974
|
}
|
|
3975
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3629
3976
|
try {
|
|
3630
3977
|
return toGqlPhDocument(result);
|
|
3631
3978
|
} catch (error) {
|
|
3632
3979
|
throw new GraphQLError(`Failed to convert updated document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3633
3980
|
}
|
|
3634
3981
|
}
|
|
3635
|
-
async function addRelationship(reactorClient, args) {
|
|
3982
|
+
async function addRelationship(reactorClient, args, subject) {
|
|
3636
3983
|
const branch = fromInputMaybe(args.branch);
|
|
3984
|
+
const metadata = fromInputMaybe(args.metadata);
|
|
3637
3985
|
let result;
|
|
3638
3986
|
try {
|
|
3639
|
-
result = await reactorClient.addRelationship(args.
|
|
3987
|
+
result = await reactorClient.addRelationship(args.sourceIdOrSlug, args.targetIdOrSlug, args.relationshipType, metadata, branch);
|
|
3640
3988
|
} catch (error) {
|
|
3641
3989
|
throw new GraphQLError(`Failed to add relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3642
3990
|
}
|
|
3991
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3992
|
+
try {
|
|
3993
|
+
return toGqlPhDocument(result);
|
|
3994
|
+
} catch (error) {
|
|
3995
|
+
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
async function updateRelationship(reactorClient, args, subject) {
|
|
3999
|
+
const branch = fromInputMaybe(args.branch);
|
|
4000
|
+
const metadata = fromInputMaybe(args.metadata) ?? null;
|
|
4001
|
+
let result;
|
|
4002
|
+
try {
|
|
4003
|
+
result = await reactorClient.updateRelationship(args.sourceIdOrSlug, args.targetIdOrSlug, args.relationshipType, metadata, branch);
|
|
4004
|
+
} catch (error) {
|
|
4005
|
+
throw new GraphQLError(`Failed to update relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4006
|
+
}
|
|
4007
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3643
4008
|
try {
|
|
3644
4009
|
return toGqlPhDocument(result);
|
|
3645
4010
|
} catch (error) {
|
|
3646
4011
|
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3647
4012
|
}
|
|
3648
4013
|
}
|
|
3649
|
-
async function removeRelationship(reactorClient, args) {
|
|
4014
|
+
async function removeRelationship(reactorClient, args, subject) {
|
|
3650
4015
|
const branch = fromInputMaybe(args.branch);
|
|
3651
4016
|
let result;
|
|
3652
4017
|
try {
|
|
3653
|
-
result = await reactorClient.removeRelationship(args.
|
|
4018
|
+
result = await reactorClient.removeRelationship(args.sourceIdOrSlug, args.targetIdOrSlug, args.relationshipType, branch);
|
|
3654
4019
|
} catch (error) {
|
|
3655
4020
|
throw new GraphQLError(`Failed to remove relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3656
4021
|
}
|
|
4022
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3657
4023
|
try {
|
|
3658
4024
|
return toGqlPhDocument(result);
|
|
3659
4025
|
} catch (error) {
|
|
3660
4026
|
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3661
4027
|
}
|
|
3662
4028
|
}
|
|
3663
|
-
async function moveRelationship(reactorClient, args) {
|
|
4029
|
+
async function moveRelationship(reactorClient, args, subject) {
|
|
3664
4030
|
const branch = fromInputMaybe(args.branch);
|
|
3665
4031
|
let result;
|
|
3666
4032
|
try {
|
|
3667
|
-
result = await reactorClient.moveRelationship(args.
|
|
4033
|
+
result = await reactorClient.moveRelationship(args.sourceParentIdOrSlug, args.targetParentIdOrSlug, args.targetIdOrSlug, args.relationshipType, branch);
|
|
3668
4034
|
} catch (error) {
|
|
3669
4035
|
throw new GraphQLError(`Failed to move relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3670
4036
|
}
|
|
4037
|
+
result = {
|
|
4038
|
+
source: await readAsCaller(reactorClient, result.source, subject, branch),
|
|
4039
|
+
target: await readAsCaller(reactorClient, result.target, subject, branch)
|
|
4040
|
+
};
|
|
3671
4041
|
try {
|
|
3672
4042
|
return {
|
|
3673
4043
|
source: toGqlPhDocument(result.source),
|
|
@@ -3680,9 +4050,12 @@ async function moveRelationship(reactorClient, args) {
|
|
|
3680
4050
|
async function deleteDocument(reactorClient, args, reactorDriveClient) {
|
|
3681
4051
|
const propagate = toReactorPropagationMode(args.propagate);
|
|
3682
4052
|
try {
|
|
3683
|
-
const driveParent = (await reactorClient.getIncomingRelationships(args.
|
|
3684
|
-
if (driveParent)
|
|
3685
|
-
|
|
4053
|
+
const driveParent = (await reactorClient.getIncomingRelationships(args.idOrSlug, "child")).results.find((p) => isDriveContainerType(p.header.documentType));
|
|
4054
|
+
if (driveParent) {
|
|
4055
|
+
const driveClient = pickDriveClient(reactorClient, driveParent.header.documentType, reactorDriveClient);
|
|
4056
|
+
const documentId = await reactorClient.resolveIdOrSlug(args.idOrSlug);
|
|
4057
|
+
await driveClient.removeNode(driveParent.header.id, documentId);
|
|
4058
|
+
} else await reactorClient.deleteDocument(args.idOrSlug, propagate);
|
|
3686
4059
|
return true;
|
|
3687
4060
|
} catch (error) {
|
|
3688
4061
|
throw new GraphQLError(`Failed to delete document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -3690,9 +4063,9 @@ async function deleteDocument(reactorClient, args, reactorDriveClient) {
|
|
|
3690
4063
|
}
|
|
3691
4064
|
async function deleteDocuments(reactorClient, args) {
|
|
3692
4065
|
const propagate = toReactorPropagationMode(args.propagate);
|
|
3693
|
-
const
|
|
4066
|
+
const idsOrSlugs = [...args.idsOrSlugs];
|
|
3694
4067
|
try {
|
|
3695
|
-
await reactorClient.deleteDocuments(
|
|
4068
|
+
await reactorClient.deleteDocuments(idsOrSlugs, propagate);
|
|
3696
4069
|
return true;
|
|
3697
4070
|
} catch (error) {
|
|
3698
4071
|
throw new GraphQLError(`Failed to delete documents: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -3706,12 +4079,19 @@ async function deleteDocuments(reactorClient, args) {
|
|
|
3706
4079
|
* an unanswered question: the first authenticated subject to use it adopts it.
|
|
3707
4080
|
*/
|
|
3708
4081
|
async function touchChannel(syncManager, args, boundAddress) {
|
|
4082
|
+
const peer = readPeerManifest(args.input.manifest);
|
|
4083
|
+
let existing;
|
|
3709
4084
|
try {
|
|
4085
|
+
existing = syncManager.getById(args.input.id);
|
|
4086
|
+
} catch {}
|
|
4087
|
+
if (existing) {
|
|
4088
|
+
await syncManager.setPeerManifest(args.input.id, peer);
|
|
3710
4089
|
return {
|
|
3711
4090
|
success: true,
|
|
3712
|
-
ackOrdinal:
|
|
4091
|
+
ackOrdinal: existing.channel.inbox.ackOrdinal,
|
|
4092
|
+
manifest: syncManager.localManifest()
|
|
3713
4093
|
};
|
|
3714
|
-
}
|
|
4094
|
+
}
|
|
3715
4095
|
const filter = {
|
|
3716
4096
|
documentId: [...args.input.filter.documentId],
|
|
3717
4097
|
scope: [...args.input.filter.scope],
|
|
@@ -3725,13 +4105,45 @@ async function touchChannel(syncManager, args, boundAddress) {
|
|
|
3725
4105
|
await syncManager.add(args.input.name, DriveCollectionId.fromKey(args.input.collectionId), {
|
|
3726
4106
|
type: "polling",
|
|
3727
4107
|
parameters: {}
|
|
3728
|
-
}, filter, options, args.input.id);
|
|
4108
|
+
}, filter, options, args.input.id, peer);
|
|
3729
4109
|
} catch (error) {
|
|
3730
4110
|
throw new GraphQLError(`Failed to create channel: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3731
4111
|
}
|
|
3732
4112
|
return {
|
|
3733
4113
|
success: true,
|
|
3734
|
-
ackOrdinal: 0
|
|
4114
|
+
ackOrdinal: 0,
|
|
4115
|
+
manifest: syncManager.localManifest()
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4118
|
+
async function syncHolds(syncManager, args) {
|
|
4119
|
+
return (await syncManager.listHolds({
|
|
4120
|
+
remoteName: args.remoteName ?? void 0,
|
|
4121
|
+
documentId: args.documentId ?? void 0
|
|
4122
|
+
})).map((hold) => ({
|
|
4123
|
+
...hold,
|
|
4124
|
+
reason: {
|
|
4125
|
+
...hold.reason,
|
|
4126
|
+
peerSupports: [...hold.reason.peerSupports]
|
|
4127
|
+
},
|
|
4128
|
+
heldAtUtcMs: String(hold.heldAtUtcMs)
|
|
4129
|
+
}));
|
|
4130
|
+
}
|
|
4131
|
+
function peerAgreement(syncManager, args) {
|
|
4132
|
+
const agreement = syncManager.agreement();
|
|
4133
|
+
const members = [...agreement.members([args.collectionId])].map(([remoteName, supports]) => ({
|
|
4134
|
+
remoteName,
|
|
4135
|
+
announced: "revision" in supports,
|
|
4136
|
+
protocols: supports.protocols,
|
|
4137
|
+
features: supports.features
|
|
4138
|
+
}));
|
|
4139
|
+
return {
|
|
4140
|
+
collectionId: args.collectionId,
|
|
4141
|
+
local: agreement.local(),
|
|
4142
|
+
members,
|
|
4143
|
+
limitedBy: Object.keys(agreement.basis().wanted).map((protocol) => ({
|
|
4144
|
+
protocol,
|
|
4145
|
+
remoteNames: agreement.limitedBy(args.collectionId, protocol)
|
|
4146
|
+
}))
|
|
3735
4147
|
};
|
|
3736
4148
|
}
|
|
3737
4149
|
/**
|
|
@@ -3786,7 +4198,8 @@ async function collectHeldSyncOperations(syncOps, gate, subject, signal) {
|
|
|
3786
4198
|
* client ordinal the switchboard has successfully applied, so the client
|
|
3787
4199
|
* can trim its own outbox)
|
|
3788
4200
|
*/
|
|
3789
|
-
function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set()) {
|
|
4201
|
+
function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set(), gatedOpIds) {
|
|
4202
|
+
const isHeld = (syncOp) => heldOpIds.has(syncOp.id) || gatedOpIds !== void 0 && !gatedOpIds.has(syncOp.id);
|
|
3790
4203
|
let remote;
|
|
3791
4204
|
try {
|
|
3792
4205
|
remote = syncManager.getById(args.channelId);
|
|
@@ -3794,7 +4207,11 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3794
4207
|
throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3795
4208
|
}
|
|
3796
4209
|
remote.channel.notePoll();
|
|
3797
|
-
const
|
|
4210
|
+
const revisions = {
|
|
4211
|
+
manifestRevision: syncManager.localManifest().revision,
|
|
4212
|
+
peerManifestRevision: remote.meta.peer?.manifest?.revision ?? null
|
|
4213
|
+
};
|
|
4214
|
+
const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId) && !isHeld(syncOp)).map((syncOp) => ({
|
|
3798
4215
|
documentId: syncOp.documentId,
|
|
3799
4216
|
error: syncOp.error?.message ?? "Unknown error",
|
|
3800
4217
|
errorType: syncOperationErrorType(syncOp.error),
|
|
@@ -3829,7 +4246,8 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3829
4246
|
envelopes: [],
|
|
3830
4247
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
3831
4248
|
deadLetters,
|
|
3832
|
-
hasMore: false
|
|
4249
|
+
hasMore: false,
|
|
4250
|
+
...revisions
|
|
3833
4251
|
};
|
|
3834
4252
|
const sorted = [...operations].sort((a, b) => {
|
|
3835
4253
|
return (a.operations[0]?.context.ordinal ?? 0) - (b.operations[0]?.context.ordinal ?? 0);
|
|
@@ -3843,9 +4261,10 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3843
4261
|
hasMore = true;
|
|
3844
4262
|
break;
|
|
3845
4263
|
}
|
|
3846
|
-
if (
|
|
4264
|
+
if (isHeld(syncOp)) continue;
|
|
3847
4265
|
syncOp.deliveredCount ??= 0;
|
|
3848
4266
|
syncOp.emittedCount ??= 0;
|
|
4267
|
+
if (!forbiddenIds.has(syncOp.documentId)) while (syncOp.deliveredCount > 0 && syncOp.operations[syncOp.deliveredCount - 1].context.ordinal > args.outboxLatest) syncOp.deliveredCount -= 1;
|
|
3849
4268
|
while (syncOp.deliveredCount < syncOp.emittedCount && syncOp.operations[syncOp.deliveredCount].context.ordinal <= args.outboxLatest) syncOp.deliveredCount += 1;
|
|
3850
4269
|
const remaining = syncOp.operations.slice(syncOp.deliveredCount);
|
|
3851
4270
|
if (remaining.length === 0) continue;
|
|
@@ -3897,9 +4316,71 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3897
4316
|
envelopes,
|
|
3898
4317
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
3899
4318
|
deadLetters,
|
|
3900
|
-
hasMore
|
|
4319
|
+
hasMore,
|
|
4320
|
+
...revisions
|
|
3901
4321
|
};
|
|
3902
4322
|
}
|
|
4323
|
+
const MARKER_REFUSAL = "marker";
|
|
4324
|
+
/**
|
|
4325
|
+
* The client's UNSUPPORTED_PROTOCOL refusals of polled rows. Each becomes a hold
|
|
4326
|
+
* for that client, as a pushed refusal does, rather than counting as delivered.
|
|
4327
|
+
* A refused marker is not a hold: {@link recordPollMarkerRefusals} takes it.
|
|
4328
|
+
* A kind this server does not know is ignored.
|
|
4329
|
+
*/
|
|
4330
|
+
function holdPollRefusals(syncManager, channelId, all) {
|
|
4331
|
+
const refusals = all?.filter((refusal) => refusal.kind === void 0 || refusal.kind === null);
|
|
4332
|
+
if (!refusals?.length) return;
|
|
4333
|
+
let remote;
|
|
4334
|
+
try {
|
|
4335
|
+
remote = syncManager.getById(channelId);
|
|
4336
|
+
} catch {
|
|
4337
|
+
return;
|
|
4338
|
+
}
|
|
4339
|
+
const refused = refusals.map((refusal) => {
|
|
4340
|
+
const syncOp = new SyncOperation(crypto.randomUUID(), "", [], remote.meta.name, refusal.documentId, [], refusal.branch, []);
|
|
4341
|
+
syncOp.failed(new ChannelError(ChannelErrorSource.Outbox, /* @__PURE__ */ new Error(`Refused by ${remote.meta.name}`), "UNSUPPORTED_PROTOCOL"));
|
|
4342
|
+
return syncOp;
|
|
4343
|
+
});
|
|
4344
|
+
remote.channel.deadLetter.add(...refused);
|
|
4345
|
+
}
|
|
4346
|
+
/**
|
|
4347
|
+
* The client's refusals of purge markers it polled. Persisted before the poll is
|
|
4348
|
+
* served; a failure fails the poll, and the client reports them again. The sync
|
|
4349
|
+
* manager keeps only refusals of markers this remote was owed.
|
|
4350
|
+
*/
|
|
4351
|
+
async function recordPollMarkerRefusals(syncManager, channelId, all) {
|
|
4352
|
+
const refusals = all?.filter((refusal) => refusal.kind === MARKER_REFUSAL);
|
|
4353
|
+
if (!refusals?.length) return;
|
|
4354
|
+
if (!supportsPurgeRefusals(syncManager)) return;
|
|
4355
|
+
let remote;
|
|
4356
|
+
try {
|
|
4357
|
+
remote = syncManager.getById(channelId);
|
|
4358
|
+
} catch {
|
|
4359
|
+
return;
|
|
4360
|
+
}
|
|
4361
|
+
try {
|
|
4362
|
+
await syncManager.recordPolledMarkerRefusals(remote.meta.name, refusals.map(({ documentId, branch }) => ({
|
|
4363
|
+
documentId,
|
|
4364
|
+
branch
|
|
4365
|
+
})));
|
|
4366
|
+
} catch (error) {
|
|
4367
|
+
throw new RefusalNotRecordedError(error);
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
/**
|
|
4371
|
+
* A poll naming no revision is from a client without peer agreement: it is
|
|
4372
|
+
* recorded silent, and what it can no longer run is held, before it is served.
|
|
4373
|
+
*/
|
|
4374
|
+
async function silenceUnversionedPoll(syncManager, channelId, manifestRevision) {
|
|
4375
|
+
if (typeof manifestRevision === "string") return;
|
|
4376
|
+
let remote;
|
|
4377
|
+
try {
|
|
4378
|
+
remote = syncManager.getById(channelId);
|
|
4379
|
+
} catch {
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
4382
|
+
if (remote.meta.peer?.manifest) await syncManager.setPeerManifest(channelId, null);
|
|
4383
|
+
}
|
|
3903
4384
|
/**
|
|
3904
4385
|
* Receives sync envelopes pushed by a client and adds them to the
|
|
3905
4386
|
* appropriate channel inboxes.
|
|
@@ -3908,7 +4389,7 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3908
4389
|
* It must be preserved because the inbox mailbox tracks applied ordinals
|
|
3909
4390
|
* and returns the highest one as `ackOrdinal` in pollSyncEnvelopes.
|
|
3910
4391
|
*/
|
|
3911
|
-
function pushSyncEnvelopes(syncManager, args) {
|
|
4392
|
+
async function pushSyncEnvelopes(syncManager, args) {
|
|
3912
4393
|
const remoteSyncOps = /* @__PURE__ */ new Map();
|
|
3913
4394
|
for (const envelope of args.envelopes) {
|
|
3914
4395
|
let remote;
|
|
@@ -3929,7 +4410,12 @@ function pushSyncEnvelopes(syncManager, args) {
|
|
|
3929
4410
|
for (const syncOp of consolidated) syncOp.jobDependencies = syncOp.jobDependencies.filter((dep) => validKeys.has(dep));
|
|
3930
4411
|
remote.channel.inbox.add(...consolidated);
|
|
3931
4412
|
}
|
|
3932
|
-
|
|
4413
|
+
try {
|
|
4414
|
+
await syncManager.receiptsStored?.([...remoteSyncOps.keys()].map((remote) => remote.meta.name));
|
|
4415
|
+
} catch (error) {
|
|
4416
|
+
throw new GraphQLError(`Failed to store received markers: ${error instanceof Error ? error.message : String(error)}`);
|
|
4417
|
+
}
|
|
4418
|
+
return true;
|
|
3933
4419
|
}
|
|
3934
4420
|
//#endregion
|
|
3935
4421
|
//#region src/graphql/document-model-subgraph.ts
|
|
@@ -3956,6 +4442,16 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
3956
4442
|
const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
|
|
3957
4443
|
return this.resolvers[`${documentName}Mutations`];
|
|
3958
4444
|
}
|
|
4445
|
+
/** Drops the items the host's own ACL refuses the caller. */
|
|
4446
|
+
async #readableItems(page, ctx) {
|
|
4447
|
+
if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
|
|
4448
|
+
const items = [];
|
|
4449
|
+
for (const item of page.items) if (await this.canReadDocument(item.id, ctx)) items.push(item);
|
|
4450
|
+
return {
|
|
4451
|
+
...page,
|
|
4452
|
+
items
|
|
4453
|
+
};
|
|
4454
|
+
}
|
|
3959
4455
|
/**
|
|
3960
4456
|
* Generate __resolveType functions for union types found in the document model schema.
|
|
3961
4457
|
* Parses the state schema to find union definitions and their member types,
|
|
@@ -4011,13 +4507,14 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4011
4507
|
Query: { [documentName]: () => ({}) },
|
|
4012
4508
|
[`${documentName}Queries`]: {
|
|
4013
4509
|
document: async (_, args, ctx) => {
|
|
4014
|
-
const
|
|
4015
|
-
|
|
4510
|
+
const idOrSlug = requireOneOf(args, "idOrSlug", "identifier");
|
|
4511
|
+
const { view } = args;
|
|
4512
|
+
if (!idOrSlug) throw new GraphQLError("Document identifier is required");
|
|
4016
4513
|
const result = await document(this.reactorClient, {
|
|
4017
|
-
|
|
4514
|
+
idOrSlug,
|
|
4018
4515
|
view
|
|
4019
|
-
});
|
|
4020
|
-
if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${
|
|
4516
|
+
}, this.viewSubject(ctx));
|
|
4517
|
+
if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${idOrSlug} is not of type ${documentType}`);
|
|
4021
4518
|
await this.assertCanReadCanonical(result.document.id, ctx);
|
|
4022
4519
|
return result;
|
|
4023
4520
|
},
|
|
@@ -4026,17 +4523,8 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4026
4523
|
const result = await findDocuments(this.reactorClient, {
|
|
4027
4524
|
search: { type: documentType },
|
|
4028
4525
|
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;
|
|
4526
|
+
}, this.viewSubject(ctx));
|
|
4527
|
+
return this.#readableItems(result, ctx);
|
|
4040
4528
|
},
|
|
4041
4529
|
findDocuments: async (_, args, ctx) => {
|
|
4042
4530
|
const { search, view, paging } = args;
|
|
@@ -4047,98 +4535,94 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4047
4535
|
},
|
|
4048
4536
|
view,
|
|
4049
4537
|
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;
|
|
4538
|
+
}, this.viewSubject(ctx));
|
|
4539
|
+
return this.#readableItems(result, ctx);
|
|
4061
4540
|
},
|
|
4062
4541
|
documentOutgoingRelationships: async (_, args, ctx) => {
|
|
4542
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
4063
4543
|
const { relationshipType, view, paging } = args;
|
|
4064
|
-
const handle = await this.assertCanRead(
|
|
4544
|
+
const handle = await this.assertCanRead(sourceIdOrSlug, ctx);
|
|
4065
4545
|
const result = await documentOutgoingRelationships(this.reactorClient, {
|
|
4066
|
-
|
|
4546
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
4067
4547
|
relationshipType,
|
|
4068
4548
|
view,
|
|
4069
4549
|
paging
|
|
4070
|
-
});
|
|
4071
|
-
const
|
|
4550
|
+
}, this.viewSubject(ctx));
|
|
4551
|
+
const readable = await this.#readableItems(result, ctx);
|
|
4552
|
+
const filteredItems = readable.items.filter((item) => item.documentType === documentType);
|
|
4072
4553
|
return {
|
|
4073
|
-
...
|
|
4074
|
-
items: filteredItems
|
|
4075
|
-
totalCount: filteredItems.length
|
|
4554
|
+
...readable,
|
|
4555
|
+
items: filteredItems
|
|
4076
4556
|
};
|
|
4077
4557
|
},
|
|
4078
4558
|
documentIncomingRelationships: async (_, args, ctx) => {
|
|
4559
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
4079
4560
|
const { relationshipType, view, paging } = args;
|
|
4080
|
-
const handle = await this.assertCanRead(
|
|
4081
|
-
|
|
4082
|
-
|
|
4561
|
+
const handle = await this.assertCanRead(targetIdOrSlug, ctx);
|
|
4562
|
+
const result = await documentIncomingRelationships(this.reactorClient, {
|
|
4563
|
+
targetIdOrSlug: handle.fetchIdentifier,
|
|
4083
4564
|
relationshipType,
|
|
4084
4565
|
view,
|
|
4085
4566
|
paging
|
|
4086
|
-
});
|
|
4567
|
+
}, this.viewSubject(ctx));
|
|
4568
|
+
return this.#readableItems(result, ctx);
|
|
4087
4569
|
}
|
|
4088
4570
|
},
|
|
4089
4571
|
Mutation: { [documentName]: () => ({}) },
|
|
4090
4572
|
[`${documentName}Mutations`]: {
|
|
4091
4573
|
createDocument: async (_, args, ctx) => {
|
|
4092
4574
|
const { name, slug, preferredEditor, initialState } = args;
|
|
4093
|
-
let
|
|
4094
|
-
if (
|
|
4575
|
+
let parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
|
|
4576
|
+
if (parentIdOrSlug) parentIdOrSlug = (await this.assertCanWrite(parentIdOrSlug, ctx)).fetchIdentifier;
|
|
4095
4577
|
else this.assertCanCreate(ctx);
|
|
4096
4578
|
let createdDoc;
|
|
4097
4579
|
if (initialState || preferredEditor) createdDoc = await createDocumentWithInitialState(this.reactorClient, {
|
|
4098
4580
|
documentType,
|
|
4099
|
-
|
|
4581
|
+
parentIdOrSlug,
|
|
4100
4582
|
name,
|
|
4101
4583
|
slug,
|
|
4102
4584
|
preferredEditor,
|
|
4103
4585
|
initialState: initialState ?? {}
|
|
4104
|
-
}, this.graphqlManager.reactorDriveClient);
|
|
4586
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
4105
4587
|
else createdDoc = await createEmptyDocument(this.reactorClient, {
|
|
4106
4588
|
documentType,
|
|
4107
|
-
|
|
4589
|
+
parentIdOrSlug,
|
|
4108
4590
|
name
|
|
4109
|
-
}, this.graphqlManager.reactorDriveClient);
|
|
4591
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
4110
4592
|
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)]));
|
|
4593
|
+
if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)], void 0, this.viewSubject(ctx)));
|
|
4112
4594
|
return createdDoc;
|
|
4113
4595
|
},
|
|
4114
4596
|
createEmptyDocument: async (_, args, ctx) => {
|
|
4115
|
-
let
|
|
4116
|
-
if (
|
|
4597
|
+
let parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
|
|
4598
|
+
if (parentIdOrSlug) parentIdOrSlug = (await this.assertCanWrite(parentIdOrSlug, ctx)).fetchIdentifier;
|
|
4117
4599
|
else this.assertCanCreate(ctx);
|
|
4118
4600
|
const result = await createEmptyDocument(this.reactorClient, {
|
|
4119
4601
|
documentType,
|
|
4120
|
-
|
|
4121
|
-
}, this.graphqlManager.reactorDriveClient);
|
|
4602
|
+
parentIdOrSlug
|
|
4603
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
4122
4604
|
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4123
4605
|
return result;
|
|
4124
4606
|
},
|
|
4125
4607
|
...operations.reduce((mutations, op) => {
|
|
4126
4608
|
mutations[camelCase(op.name)] = async (_, args, ctx) => {
|
|
4127
|
-
const
|
|
4128
|
-
const
|
|
4129
|
-
|
|
4609
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "docId");
|
|
4610
|
+
const { input } = args;
|
|
4611
|
+
const effectiveDocId = (await this.assertCanExecuteOperation(documentIdOrSlug, op.name, ctx)).fetchIdentifier;
|
|
4612
|
+
if ((await this.reactorClient.get(effectiveDocId, { subject: this.viewSubject(ctx) })).header.documentType !== documentType) throw new GraphQLError(`Document with id ${documentIdOrSlug} is not of type ${documentType}`);
|
|
4130
4613
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
4131
4614
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
4132
4615
|
try {
|
|
4133
|
-
return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)]));
|
|
4616
|
+
return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)], void 0, this.viewSubject(ctx)));
|
|
4134
4617
|
} catch (error) {
|
|
4135
4618
|
throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
|
|
4136
4619
|
}
|
|
4137
4620
|
};
|
|
4138
4621
|
mutations[`${camelCase(op.name)}Async`] = async (_, args, ctx) => {
|
|
4139
|
-
const
|
|
4140
|
-
const
|
|
4141
|
-
|
|
4622
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "docId");
|
|
4623
|
+
const { input } = args;
|
|
4624
|
+
const effectiveDocId = (await this.assertCanExecuteOperation(documentIdOrSlug, op.name, ctx)).fetchIdentifier;
|
|
4625
|
+
if ((await this.reactorClient.get(effectiveDocId, { subject: this.viewSubject(ctx) })).header.documentType !== documentType) throw new GraphQLError(`Document with id ${documentIdOrSlug} is not of type ${documentType}`);
|
|
4142
4626
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
4143
4627
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
4144
4628
|
try {
|
|
@@ -4162,7 +4646,7 @@ const DRIVE_ID_HEADER = "drive-id";
|
|
|
4162
4646
|
* in the ownership set yet); other create-shaped operations that may
|
|
4163
4647
|
* synthesize a drive must be added here too.
|
|
4164
4648
|
*/
|
|
4165
|
-
const CACHE_BYPASS_OPERATIONS = new Set(["createDocument", "createEmptyDocument"]);
|
|
4649
|
+
const CACHE_BYPASS_OPERATIONS = /* @__PURE__ */ new Set(["createDocument", "createEmptyDocument"]);
|
|
4166
4650
|
const driveIdMap = /* @__PURE__ */ new WeakMap();
|
|
4167
4651
|
/** Internal — only `graphql-manager.ts` should call this. */
|
|
4168
4652
|
function getRequestDriveId(request) {
|
|
@@ -4232,6 +4716,7 @@ function wrongShardResponse(driveId) {
|
|
|
4232
4716
|
* 421 response.
|
|
4233
4717
|
*/
|
|
4234
4718
|
var DriveOwnershipCache = class {
|
|
4719
|
+
reactorClient;
|
|
4235
4720
|
drives = /* @__PURE__ */ new Set();
|
|
4236
4721
|
constructor(reactorClient) {
|
|
4237
4722
|
this.reactorClient = reactorClient;
|
|
@@ -4363,8 +4848,8 @@ var GraphQLManager = class {
|
|
|
4363
4848
|
subgraphHandlerCache = /* @__PURE__ */ new Map();
|
|
4364
4849
|
/** subgraphPath → the http adapter handle of its mounted route. */
|
|
4365
4850
|
subgraphRouteHandles = /* @__PURE__ */ new Map();
|
|
4366
|
-
/**
|
|
4367
|
-
|
|
4851
|
+
/** Mounted SSE routes, keyed by path: the supergraph's and each subgraph's. */
|
|
4852
|
+
sseRouteHandles = /* @__PURE__ */ new Map();
|
|
4368
4853
|
/**
|
|
4369
4854
|
* Package name → the subgraph instances registered from it, keyed by
|
|
4370
4855
|
* subgraph name. Tracks the package association so that removed packages
|
|
@@ -4383,26 +4868,43 @@ var GraphQLManager = class {
|
|
|
4383
4868
|
authorizationService;
|
|
4384
4869
|
/** Route service in use: the injected one, or one built on first need. */
|
|
4385
4870
|
#fallbackRoutes;
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
this.
|
|
4405
|
-
this.
|
|
4871
|
+
path;
|
|
4872
|
+
httpServer;
|
|
4873
|
+
wsServer;
|
|
4874
|
+
reactorClient;
|
|
4875
|
+
relationalDb;
|
|
4876
|
+
analyticsStore;
|
|
4877
|
+
syncManager;
|
|
4878
|
+
logger;
|
|
4879
|
+
httpAdapter;
|
|
4880
|
+
gatewayAdapter;
|
|
4881
|
+
authService;
|
|
4882
|
+
documentPermissionService;
|
|
4883
|
+
featureFlags;
|
|
4884
|
+
port;
|
|
4885
|
+
syncServingGate;
|
|
4886
|
+
httpRoutes;
|
|
4887
|
+
attachments;
|
|
4888
|
+
constructor(options) {
|
|
4889
|
+
this.path = options.path;
|
|
4890
|
+
this.httpServer = options.httpServer;
|
|
4891
|
+
this.wsServer = options.wsServer;
|
|
4892
|
+
this.reactorClient = options.reactorClient;
|
|
4893
|
+
this.relationalDb = options.relationalDb;
|
|
4894
|
+
this.analyticsStore = options.analyticsStore;
|
|
4895
|
+
this.syncManager = options.syncManager;
|
|
4896
|
+
this.logger = options.logger;
|
|
4897
|
+
this.httpAdapter = options.httpAdapter;
|
|
4898
|
+
this.gatewayAdapter = options.gatewayAdapter;
|
|
4899
|
+
this.authService = options.authService;
|
|
4900
|
+
this.documentPermissionService = options.documentPermissionService;
|
|
4901
|
+
this.featureFlags = options.featureFlags ?? DefaultFeatureFlags;
|
|
4902
|
+
this.port = options.port ?? 4001;
|
|
4903
|
+
this.authorizationService = options.authorizationService;
|
|
4904
|
+
this.reactorDriveClient = options.reactorDriveClient;
|
|
4905
|
+
this.syncServingGate = options.syncServingGate;
|
|
4906
|
+
this.httpRoutes = options.httpRoutes;
|
|
4907
|
+
this.attachments = options.attachments;
|
|
4406
4908
|
this.driveOwnershipCache = new DriveOwnershipCache(this.reactorClient);
|
|
4407
4909
|
this.wsServer.setMaxListeners(0);
|
|
4408
4910
|
}
|
|
@@ -4425,23 +4927,25 @@ var GraphQLManager = class {
|
|
|
4425
4927
|
const matched = driveMatcher(url.pathname);
|
|
4426
4928
|
const driveIdOrSlug = matched ? matched.params.drive : void 0;
|
|
4427
4929
|
if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
|
|
4930
|
+
const user = getAuthContext(request)?.user;
|
|
4428
4931
|
try {
|
|
4429
|
-
const driveDoc = await this.reactorClient.get(driveIdOrSlug);
|
|
4430
|
-
if (!await this.authorizationService.canRead(driveDoc.header.id,
|
|
4932
|
+
const driveDoc = await this.reactorClient.get(driveIdOrSlug, { subject: callerSubject(user) });
|
|
4933
|
+
if (!await this.authorizationService.canRead(driveDoc.header.id, user?.address)) {
|
|
4431
4934
|
this.logger.debug(`Drive read refused: ${driveIdOrSlug}`);
|
|
4432
4935
|
return Response.json({ error: "Drive not found" }, { status: 404 });
|
|
4433
4936
|
}
|
|
4434
4937
|
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`;
|
|
4938
|
+
const global = driveDoc.state.global;
|
|
4435
4939
|
return Response.json({
|
|
4436
4940
|
id: driveDoc.header.id,
|
|
4437
4941
|
slug: driveDoc.header.slug,
|
|
4438
4942
|
meta: driveDoc.header.meta,
|
|
4439
|
-
name:
|
|
4440
|
-
icon:
|
|
4943
|
+
name: global?.name || driveDoc.header.name,
|
|
4944
|
+
icon: global?.icon ?? void 0,
|
|
4441
4945
|
...graphqlEndpoint && { graphqlEndpoint }
|
|
4442
4946
|
});
|
|
4443
4947
|
} catch (error) {
|
|
4444
|
-
this.logger.debug(
|
|
4948
|
+
this.logger.debug("Drive not found: @drive (@error)", driveIdOrSlug, error);
|
|
4445
4949
|
return Response.json({ error: "Drive not found" }, { status: 404 });
|
|
4446
4950
|
}
|
|
4447
4951
|
};
|
|
@@ -4474,7 +4978,7 @@ var GraphQLManager = class {
|
|
|
4474
4978
|
for (const subgraph of coreSubgraphs) try {
|
|
4475
4979
|
await this.registerSubgraph(subgraph, supergraph, true);
|
|
4476
4980
|
} catch (error) {
|
|
4477
|
-
this.logger.error(
|
|
4981
|
+
this.logger.error("Failed to setup core subgraph @subgraph: @error", subgraph.name, error);
|
|
4478
4982
|
}
|
|
4479
4983
|
return this.#setupSubgraphs(this.coreSubgraphsMap);
|
|
4480
4984
|
}
|
|
@@ -4495,12 +4999,13 @@ var GraphQLManager = class {
|
|
|
4495
4999
|
path: this.path,
|
|
4496
5000
|
documentPermissionService: this.documentPermissionService,
|
|
4497
5001
|
authorizationService: this.authorizationService,
|
|
4498
|
-
syncServingGate: this.syncServingGate
|
|
5002
|
+
syncServingGate: this.syncServingGate,
|
|
5003
|
+
attachments: this.attachments
|
|
4499
5004
|
});
|
|
4500
5005
|
await this.#addSubgraphInstance(subgraphInstance, supergraph, false, DOCUMENT_MODEL_SUBGRAPH_SOURCE);
|
|
4501
5006
|
registeredNames.add(subgraphInstance.name);
|
|
4502
5007
|
} catch (error) {
|
|
4503
|
-
this.logger.error(
|
|
5008
|
+
this.logger.error("Failed to setup document model subgraph for @documentType: @error", documentModel.documentModel.global.id, error instanceof Error ? error.message : error);
|
|
4504
5009
|
this.logger.debug("@error", error);
|
|
4505
5010
|
}
|
|
4506
5011
|
}
|
|
@@ -4562,6 +5067,7 @@ var GraphQLManager = class {
|
|
|
4562
5067
|
routeHandle.dispose();
|
|
4563
5068
|
this.subgraphRouteHandles.delete(subgraphPath);
|
|
4564
5069
|
}
|
|
5070
|
+
this.#disposeSSERoute(subgraphPath);
|
|
4565
5071
|
const wsDisposer = this.subgraphWsDisposers.get(subgraphPath);
|
|
4566
5072
|
if (wsDisposer) {
|
|
4567
5073
|
this.subgraphWsDisposers.delete(subgraphPath);
|
|
@@ -4631,7 +5137,8 @@ var GraphQLManager = class {
|
|
|
4631
5137
|
path: this.path,
|
|
4632
5138
|
documentPermissionService: this.documentPermissionService,
|
|
4633
5139
|
authorizationService: this.authorizationService,
|
|
4634
|
-
syncServingGate: this.syncServingGate
|
|
5140
|
+
syncServingGate: this.syncServingGate,
|
|
5141
|
+
attachments: this.attachments
|
|
4635
5142
|
});
|
|
4636
5143
|
return this.#addSubgraphInstance(subgraphInstance, supergraph, core, packageName);
|
|
4637
5144
|
}
|
|
@@ -4658,7 +5165,7 @@ var GraphQLManager = class {
|
|
|
4658
5165
|
try {
|
|
4659
5166
|
routes?.disposeScope(packageName);
|
|
4660
5167
|
} catch (error) {
|
|
4661
|
-
this.logger.warn(`Failed to release HTTP routes for "
|
|
5168
|
+
this.logger.warn(`Failed to release HTTP routes for "@package": @error`, packageName, error);
|
|
4662
5169
|
}
|
|
4663
5170
|
}
|
|
4664
5171
|
/**
|
|
@@ -4758,15 +5265,12 @@ var GraphQLManager = class {
|
|
|
4758
5265
|
authService: this.authService,
|
|
4759
5266
|
requireAuthenticatedCaller: () => this.#requireAuthMiddleware !== void 0,
|
|
4760
5267
|
logger: this.logger,
|
|
4761
|
-
buildContext: (connectionParams, user) => {
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
if (user) context.user = user;
|
|
4768
|
-
return context;
|
|
4769
|
-
}
|
|
5268
|
+
buildContext: (connectionParams, user) => ({
|
|
5269
|
+
headers: connectionParams,
|
|
5270
|
+
db: this.relationalDb,
|
|
5271
|
+
...this.getAdditionalContextFields(),
|
|
5272
|
+
user
|
|
5273
|
+
})
|
|
4770
5274
|
});
|
|
4771
5275
|
}
|
|
4772
5276
|
setSupergraph(supergraph, subgraphs) {
|
|
@@ -4901,10 +5405,7 @@ var GraphQLManager = class {
|
|
|
4901
5405
|
const allSubgraphs = this.#getAllSubgraphs();
|
|
4902
5406
|
const modules = Array.from(allSubgraphs.values()).filter((subgraph) => subgraph.hasSubscriptions).map((subgraph) => this.#buildSubgraphSchemaModule(subgraph));
|
|
4903
5407
|
if (modules.length === 0) {
|
|
4904
|
-
|
|
4905
|
-
this.sseRouteHandle.dispose();
|
|
4906
|
-
this.sseRouteHandle = void 0;
|
|
4907
|
-
}
|
|
5408
|
+
this.#disposeSSERoute(superGraphPath);
|
|
4908
5409
|
return;
|
|
4909
5410
|
}
|
|
4910
5411
|
try {
|
|
@@ -4930,8 +5431,16 @@ var GraphQLManager = class {
|
|
|
4930
5431
|
contextFactory: this.#makeContextFactory()
|
|
4931
5432
|
});
|
|
4932
5433
|
const handler = this.#composeFetchMiddleware(rawHandler);
|
|
4933
|
-
|
|
4934
|
-
this.
|
|
5434
|
+
this.#disposeSSERoute(basePath);
|
|
5435
|
+
this.sseRouteHandles.set(ssePath, this.httpAdapter.mount(ssePath, handler, { exact: true }));
|
|
5436
|
+
}
|
|
5437
|
+
#disposeSSERoute(basePath) {
|
|
5438
|
+
const ssePath = basePath + "/stream";
|
|
5439
|
+
const handle = this.sseRouteHandles.get(ssePath);
|
|
5440
|
+
if (handle !== void 0) {
|
|
5441
|
+
handle.dispose();
|
|
5442
|
+
this.sseRouteHandles.delete(ssePath);
|
|
5443
|
+
}
|
|
4935
5444
|
}
|
|
4936
5445
|
/**
|
|
4937
5446
|
* Compose the request-level fetch middleware chain. Auth runs first
|
|
@@ -5215,7 +5724,6 @@ const DocumentModelStateDTO = z.object({
|
|
|
5215
5724
|
}).strip();
|
|
5216
5725
|
const DocumentModelResultPageDTO = z.object({
|
|
5217
5726
|
items: z.array(DocumentModelStateDTO),
|
|
5218
|
-
totalCount: z.number().int(),
|
|
5219
5727
|
hasNextPage: z.boolean(),
|
|
5220
5728
|
hasPreviousPage: z.boolean(),
|
|
5221
5729
|
cursor: z.string().nullable().optional()
|
|
@@ -5236,7 +5744,20 @@ const PHDocumentDTO = z.object({
|
|
|
5236
5744
|
}).strip();
|
|
5237
5745
|
const PHDocumentResultPageDTO = z.object({
|
|
5238
5746
|
items: z.array(PHDocumentDTO),
|
|
5239
|
-
|
|
5747
|
+
hasNextPage: z.boolean(),
|
|
5748
|
+
hasPreviousPage: z.boolean(),
|
|
5749
|
+
cursor: z.string().nullable().optional()
|
|
5750
|
+
}).strip();
|
|
5751
|
+
const DocumentRelationshipDTO = z.object({
|
|
5752
|
+
sourceId: z.string(),
|
|
5753
|
+
targetId: z.string(),
|
|
5754
|
+
relationshipType: z.string(),
|
|
5755
|
+
metadata: JSONObjectDTO.nullable().optional(),
|
|
5756
|
+
createdAt: DateTimeDTO,
|
|
5757
|
+
updatedAt: DateTimeDTO
|
|
5758
|
+
}).strip();
|
|
5759
|
+
const DocumentRelationshipResultPageDTO = z.object({
|
|
5760
|
+
items: z.array(DocumentRelationshipDTO),
|
|
5240
5761
|
hasNextPage: z.boolean(),
|
|
5241
5762
|
hasPreviousPage: z.boolean(),
|
|
5242
5763
|
cursor: z.string().nullable().optional()
|
|
@@ -5291,7 +5812,6 @@ const OperationDTO = z.object({
|
|
|
5291
5812
|
}).strip();
|
|
5292
5813
|
const OperationResultPageDTO = z.object({
|
|
5293
5814
|
items: z.array(OperationDTO),
|
|
5294
|
-
totalCount: z.number().int(),
|
|
5295
5815
|
hasNextPage: z.boolean(),
|
|
5296
5816
|
hasPreviousPage: z.boolean(),
|
|
5297
5817
|
cursor: z.string().nullable().optional()
|
|
@@ -5344,6 +5864,12 @@ const operationValidators = {
|
|
|
5344
5864
|
GetDocumentIncomingRelationships: (data) => {
|
|
5345
5865
|
if (data.documentIncomingRelationships) PHDocumentResultPageDTO.parse(data.documentIncomingRelationships);
|
|
5346
5866
|
},
|
|
5867
|
+
GetDocumentOutgoingRelationshipEdges: (data) => {
|
|
5868
|
+
if (data.documentOutgoingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentOutgoingRelationshipEdges);
|
|
5869
|
+
},
|
|
5870
|
+
GetDocumentIncomingRelationshipEdges: (data) => {
|
|
5871
|
+
if (data.documentIncomingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentIncomingRelationshipEdges);
|
|
5872
|
+
},
|
|
5347
5873
|
FindDocuments: (data) => {
|
|
5348
5874
|
if (data.findDocuments) PHDocumentResultPageDTO.parse(data.findDocuments);
|
|
5349
5875
|
},
|
|
@@ -5377,6 +5903,9 @@ const operationValidators = {
|
|
|
5377
5903
|
AddRelationship: (data) => {
|
|
5378
5904
|
if (data.addRelationship) PHDocumentDTO.parse(data.addRelationship);
|
|
5379
5905
|
},
|
|
5906
|
+
UpdateRelationship: (data) => {
|
|
5907
|
+
if (data.updateRelationship) PHDocumentDTO.parse(data.updateRelationship);
|
|
5908
|
+
},
|
|
5380
5909
|
RemoveRelationship: (data) => {
|
|
5381
5910
|
if (data.removeRelationship) PHDocumentDTO.parse(data.removeRelationship);
|
|
5382
5911
|
},
|
|
@@ -5418,7 +5947,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
|
|
|
5418
5947
|
}
|
|
5419
5948
|
//#endregion
|
|
5420
5949
|
//#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";
|
|
5950
|
+
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 @deprecated(reason: \"Ignored. Filter by type or parentId.\")\n}\n\ninput OperationsFilterInput {\n documentIdOrSlug: String\n documentId: String @deprecated(reason: \"Use documentIdOrSlug.\")\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 # \"marker\" for a refused purge marker; absent for UNSUPPORTED_PROTOCOL\n kind: 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(\n idOrSlug: String\n identifier: String @deprecated(reason: \"Use idOrSlug.\")\n view: ViewFilterInput\n ): DocumentWithChildren\n\n # Get outgoing relationships of a given type from a source document\n documentOutgoingRelationships(\n sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\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 targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\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 sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\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 targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\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 documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\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 from earlier polls: held, or a refused marker\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(\n document: JSONObject!\n parentIdOrSlug: String\n parentIdentifier: String @deprecated(reason: \"Use parentIdOrSlug.\")\n ): PHDocument!\n\n # Create an empty document of specified type\n createEmptyDocument(\n documentType: String!\n parentIdOrSlug: String\n parentIdentifier: String @deprecated(reason: \"Use parentIdOrSlug.\")\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 documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\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 documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\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 documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\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 documentIdOrSlug: String\n documentIdentifier: String @deprecated(reason: \"Use documentIdOrSlug.\")\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 sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\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 sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\n relationshipType: String!\n metadata: JSONObject\n branch: String\n ): PHDocument!\n\n # Remove a relationship between two documents\n removeRelationship(\n sourceIdOrSlug: String\n sourceIdentifier: String @deprecated(reason: \"Use sourceIdOrSlug.\")\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\n relationshipType: String!\n branch: String\n ): PHDocument!\n\n # Move a relationship from one source to another\n moveRelationship(\n sourceParentIdOrSlug: String\n sourceParentIdentifier: String\n @deprecated(reason: \"Use sourceParentIdOrSlug.\")\n targetParentIdOrSlug: String\n targetParentIdentifier: String\n @deprecated(reason: \"Use targetParentIdOrSlug.\")\n targetIdOrSlug: String\n targetIdentifier: String @deprecated(reason: \"Use targetIdOrSlug.\")\n relationshipType: String!\n branch: String\n ): MoveRelationshipResult!\n\n # Delete a single document\n deleteDocument(\n idOrSlug: String\n identifier: String @deprecated(reason: \"Use idOrSlug.\")\n propagate: PropagationMode\n ): Boolean!\n\n # Delete multiple documents\n deleteDocuments(\n idsOrSlugs: [String!]\n identifiers: [String!] @deprecated(reason: \"Use idsOrSlugs.\")\n propagate: PropagationMode\n ): 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
5951
|
//#endregion
|
|
5423
5952
|
//#region src/graphql/reactor/pubsub.ts
|
|
5424
5953
|
const pubSub = new PubSub();
|
|
@@ -5429,26 +5958,79 @@ const SUBSCRIPTION_TRIGGERS = {
|
|
|
5429
5958
|
DOCUMENT_CHANGES: "DOCUMENT_CHANGES",
|
|
5430
5959
|
JOB_CHANGES: "JOB_CHANGES"
|
|
5431
5960
|
};
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5961
|
+
function subjectKey(subject) {
|
|
5962
|
+
return `${subject.address ?? ""}|${subject.key ?? ""}`;
|
|
5963
|
+
}
|
|
5964
|
+
/**
|
|
5965
|
+
* The reactor's change feed, read once per distinct subject and shared by that
|
|
5966
|
+
* subject's GraphQL subscriptions. Each feed is read as its subject, so the
|
|
5967
|
+
* client withholds what that subject may not read; one feed read with no view
|
|
5968
|
+
* would gate every subscriber as the host.
|
|
5969
|
+
*/
|
|
5970
|
+
var DocumentChangeFeed = class {
|
|
5971
|
+
reactorClient;
|
|
5972
|
+
#pubSub = new PubSub();
|
|
5973
|
+
#feeds = /* @__PURE__ */ new Map();
|
|
5974
|
+
constructor(reactorClient) {
|
|
5975
|
+
this.reactorClient = reactorClient;
|
|
5976
|
+
}
|
|
5977
|
+
subscribe(subject) {
|
|
5978
|
+
const key = subjectKey(subject);
|
|
5979
|
+
const topic = `${SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES}:${key}`;
|
|
5980
|
+
const iterator = this.#pubSub.asyncIterableIterator(topic);
|
|
5981
|
+
this.#acquire(key, topic, subject);
|
|
5982
|
+
let released = false;
|
|
5983
|
+
const release = () => {
|
|
5984
|
+
if (!released) {
|
|
5985
|
+
released = true;
|
|
5986
|
+
this.#release(key);
|
|
5987
|
+
}
|
|
5440
5988
|
};
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5989
|
+
return {
|
|
5990
|
+
next: () => iterator.next(),
|
|
5991
|
+
return: () => {
|
|
5992
|
+
release();
|
|
5993
|
+
return iterator.return();
|
|
5994
|
+
},
|
|
5995
|
+
throw: (error) => {
|
|
5996
|
+
release();
|
|
5997
|
+
return iterator.throw(error);
|
|
5998
|
+
},
|
|
5999
|
+
[Symbol.asyncIterator]() {
|
|
6000
|
+
return this;
|
|
6001
|
+
}
|
|
6002
|
+
};
|
|
6003
|
+
}
|
|
6004
|
+
#acquire(key, topic, subject) {
|
|
6005
|
+
const feed = this.#feeds.get(key);
|
|
6006
|
+
if (feed) {
|
|
6007
|
+
feed.subscribers++;
|
|
6008
|
+
return;
|
|
5449
6009
|
}
|
|
5450
|
-
|
|
5451
|
-
}
|
|
6010
|
+
const view = { subject };
|
|
6011
|
+
const unsubscribe = this.reactorClient.subscribe({}, (event) => {
|
|
6012
|
+
const payload = {
|
|
6013
|
+
documentChanges: event,
|
|
6014
|
+
search: {},
|
|
6015
|
+
view
|
|
6016
|
+
};
|
|
6017
|
+
this.#pubSub.publish(topic, payload);
|
|
6018
|
+
}, view);
|
|
6019
|
+
this.#feeds.set(key, {
|
|
6020
|
+
unsubscribe,
|
|
6021
|
+
subscribers: 1
|
|
6022
|
+
});
|
|
6023
|
+
}
|
|
6024
|
+
#release(key) {
|
|
6025
|
+
const feed = this.#feeds.get(key);
|
|
6026
|
+
if (!feed) return;
|
|
6027
|
+
feed.subscribers--;
|
|
6028
|
+
if (feed.subscribers === 0) {
|
|
6029
|
+
this.#feeds.delete(key);
|
|
6030
|
+
feed.unsubscribe();
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
};
|
|
5452
6034
|
const activeJobSubscriptions = /* @__PURE__ */ new Map();
|
|
5453
6035
|
function ensureJobSubscription(reactorClient, jobId) {
|
|
5454
6036
|
let subscription = activeJobSubscriptions.get(jobId);
|
|
@@ -5465,7 +6047,7 @@ function ensureJobSubscription(reactorClient, jobId) {
|
|
|
5465
6047
|
createdAt: jobInfo.createdAtUtcIso,
|
|
5466
6048
|
completedAt: jobInfo.completedAtUtcIso ?? null,
|
|
5467
6049
|
error: jobInfo.error?.message ?? null,
|
|
5468
|
-
result: jobInfo.result ??
|
|
6050
|
+
result: jobInfo.result ?? null
|
|
5469
6051
|
},
|
|
5470
6052
|
jobId,
|
|
5471
6053
|
documentId: jobInfo.documentId
|
|
@@ -5513,6 +6095,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5513
6095
|
}
|
|
5514
6096
|
name = "r";
|
|
5515
6097
|
hasSubscriptions = true;
|
|
6098
|
+
#documentChanges;
|
|
6099
|
+
get documentChanges() {
|
|
6100
|
+
this.#documentChanges ??= new DocumentChangeFeed(this.reactorClient);
|
|
6101
|
+
return this.#documentChanges;
|
|
6102
|
+
}
|
|
5516
6103
|
/**
|
|
5517
6104
|
* Check operation-level permissions for an array of actions.
|
|
5518
6105
|
* Delegates to base assertCanExecuteOperation for each action.
|
|
@@ -5543,6 +6130,38 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5543
6130
|
}
|
|
5544
6131
|
}
|
|
5545
6132
|
/**
|
|
6133
|
+
* Drops the relationship edges whose far-end document the caller cannot read.
|
|
6134
|
+
*
|
|
6135
|
+
* An edge names two documents, so returning it discloses the far end's id. The
|
|
6136
|
+
* document-shaped relationship queries already withhold far-end documents the
|
|
6137
|
+
* caller cannot read; the edge-shaped queries have to withhold the same ones,
|
|
6138
|
+
* or they become a way to enumerate around that check. Supreme admins read
|
|
6139
|
+
* everything, so the per-edge check is skipped for them.
|
|
6140
|
+
*
|
|
6141
|
+
* The cursor describes where to resume in the underlying unfiltered stream,
|
|
6142
|
+
* so it is left as it came. A filtered page is therefore often shorter than
|
|
6143
|
+
* the requested limit, and that is the stream continuing rather than a sign
|
|
6144
|
+
* it ended.
|
|
6145
|
+
*/
|
|
6146
|
+
async filterRelationshipEdges(page, farEnd, ctx) {
|
|
6147
|
+
if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
|
|
6148
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
6149
|
+
const items = [];
|
|
6150
|
+
for (const edge of page.items) {
|
|
6151
|
+
const documentId = edge[farEnd];
|
|
6152
|
+
let canRead = decisions.get(documentId);
|
|
6153
|
+
if (canRead === void 0) {
|
|
6154
|
+
canRead = await this.canReadDocument(documentId, ctx);
|
|
6155
|
+
decisions.set(documentId, canRead);
|
|
6156
|
+
}
|
|
6157
|
+
if (canRead) items.push(edge);
|
|
6158
|
+
}
|
|
6159
|
+
return {
|
|
6160
|
+
...page,
|
|
6161
|
+
items
|
|
6162
|
+
};
|
|
6163
|
+
}
|
|
6164
|
+
/**
|
|
5546
6165
|
* Adds to `forbidden` the canonical document ids in `syncOps` that the caller
|
|
5547
6166
|
* cannot read, checking each distinct id once. Sync operation document ids are
|
|
5548
6167
|
* canonical (never slugs), so no resolution is needed.
|
|
@@ -5575,9 +6194,9 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5575
6194
|
* reports the liveness that keeps it from being reclaimed -- but they never
|
|
5576
6195
|
* adopt, since the push path has no drive check to clear the claimant.
|
|
5577
6196
|
*
|
|
5578
|
-
* Nothing is enforced or adopted without a serving gate
|
|
5579
|
-
*
|
|
5580
|
-
* belong to
|
|
6197
|
+
* Nothing is enforced or adopted without a serving gate: with no gate the
|
|
6198
|
+
* channel serves every subject alike, so there is no subject for it to
|
|
6199
|
+
* belong to.
|
|
5581
6200
|
*/
|
|
5582
6201
|
async #bindOrRefuseChannel(channelId, ctx, options = {}) {
|
|
5583
6202
|
if (!this.syncServingGate) return;
|
|
@@ -5607,7 +6226,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5607
6226
|
await this.assertCanReadCanonical(parent.id, ctx);
|
|
5608
6227
|
try {
|
|
5609
6228
|
const filter = {
|
|
5610
|
-
|
|
6229
|
+
documentIdOrSlug: parent.id,
|
|
5611
6230
|
branch: args.filter?.branch,
|
|
5612
6231
|
scopes: args.filter?.scopes,
|
|
5613
6232
|
actionTypes: args.filter?.actionTypes,
|
|
@@ -5637,10 +6256,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5637
6256
|
document: async (_parent, args, ctx) => {
|
|
5638
6257
|
this.logger.debug("document(@args)", args);
|
|
5639
6258
|
try {
|
|
5640
|
-
const
|
|
6259
|
+
const idOrSlug = requireOneOf(args, "idOrSlug", "identifier");
|
|
6260
|
+
const handle = await this.assertCanRead(idOrSlug, ctx);
|
|
5641
6261
|
return await document(this.reactorClient, {
|
|
5642
|
-
|
|
5643
|
-
|
|
6262
|
+
idOrSlug: handle.fetchIdentifier,
|
|
6263
|
+
view: args.view
|
|
5644
6264
|
}, this.viewSubject(ctx));
|
|
5645
6265
|
} catch (error) {
|
|
5646
6266
|
this.logger.error("Error in document: @Error", error);
|
|
@@ -5650,10 +6270,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5650
6270
|
documentOutgoingRelationships: async (_parent, args, ctx) => {
|
|
5651
6271
|
this.logger.debug("documentOutgoingRelationships(@args)", args);
|
|
5652
6272
|
try {
|
|
5653
|
-
const
|
|
6273
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6274
|
+
const handle = await this.assertCanRead(sourceIdOrSlug, ctx);
|
|
5654
6275
|
return await documentOutgoingRelationships(this.reactorClient, {
|
|
5655
|
-
|
|
5656
|
-
|
|
6276
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6277
|
+
relationshipType: args.relationshipType,
|
|
6278
|
+
view: args.view,
|
|
6279
|
+
paging: args.paging
|
|
5657
6280
|
}, this.viewSubject(ctx));
|
|
5658
6281
|
} catch (error) {
|
|
5659
6282
|
this.logger.error("Error in documentOutgoingRelationships: @Error", error);
|
|
@@ -5663,10 +6286,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5663
6286
|
documentIncomingRelationships: async (_parent, args, ctx) => {
|
|
5664
6287
|
this.logger.debug("documentIncomingRelationships(@args)", args);
|
|
5665
6288
|
try {
|
|
5666
|
-
const
|
|
6289
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6290
|
+
const handle = await this.assertCanRead(targetIdOrSlug, ctx);
|
|
5667
6291
|
const result = await documentIncomingRelationships(this.reactorClient, {
|
|
5668
|
-
|
|
5669
|
-
|
|
6292
|
+
targetIdOrSlug: handle.fetchIdentifier,
|
|
6293
|
+
relationshipType: args.relationshipType,
|
|
6294
|
+
view: args.view,
|
|
6295
|
+
paging: args.paging
|
|
5670
6296
|
}, this.viewSubject(ctx));
|
|
5671
6297
|
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
5672
6298
|
const filteredItems = [];
|
|
@@ -5682,6 +6308,40 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5682
6308
|
throw error;
|
|
5683
6309
|
}
|
|
5684
6310
|
},
|
|
6311
|
+
documentOutgoingRelationshipEdges: async (_parent, args, ctx) => {
|
|
6312
|
+
this.logger.debug("documentOutgoingRelationshipEdges(@args)", args);
|
|
6313
|
+
try {
|
|
6314
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6315
|
+
const handle = await this.assertCanRead(sourceIdOrSlug, ctx);
|
|
6316
|
+
const result = await documentOutgoingRelationshipEdges(this.reactorClient, {
|
|
6317
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6318
|
+
relationshipType: args.relationshipType,
|
|
6319
|
+
view: args.view,
|
|
6320
|
+
paging: args.paging
|
|
6321
|
+
}, this.viewSubject(ctx));
|
|
6322
|
+
return await this.filterRelationshipEdges(result, "targetId", ctx);
|
|
6323
|
+
} catch (error) {
|
|
6324
|
+
this.logger.error("Error in documentOutgoingRelationshipEdges: @Error", error);
|
|
6325
|
+
throw error;
|
|
6326
|
+
}
|
|
6327
|
+
},
|
|
6328
|
+
documentIncomingRelationshipEdges: async (_parent, args, ctx) => {
|
|
6329
|
+
this.logger.debug("documentIncomingRelationshipEdges(@args)", args);
|
|
6330
|
+
try {
|
|
6331
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6332
|
+
const handle = await this.assertCanRead(targetIdOrSlug, ctx);
|
|
6333
|
+
const result = await documentIncomingRelationshipEdges(this.reactorClient, {
|
|
6334
|
+
targetIdOrSlug: handle.fetchIdentifier,
|
|
6335
|
+
relationshipType: args.relationshipType,
|
|
6336
|
+
view: args.view,
|
|
6337
|
+
paging: args.paging
|
|
6338
|
+
}, this.viewSubject(ctx));
|
|
6339
|
+
return await this.filterRelationshipEdges(result, "sourceId", ctx);
|
|
6340
|
+
} catch (error) {
|
|
6341
|
+
this.logger.error("Error in documentIncomingRelationshipEdges: @Error", error);
|
|
6342
|
+
throw error;
|
|
6343
|
+
}
|
|
6344
|
+
},
|
|
5685
6345
|
findDocuments: async (_parent, args, ctx) => {
|
|
5686
6346
|
this.logger.debug("findDocuments(@args)", args);
|
|
5687
6347
|
try {
|
|
@@ -5703,10 +6363,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5703
6363
|
throw error;
|
|
5704
6364
|
}
|
|
5705
6365
|
},
|
|
5706
|
-
jobStatus: async (_parent, args) => {
|
|
6366
|
+
jobStatus: async (_parent, args, ctx) => {
|
|
5707
6367
|
this.logger.debug("jobStatus(@args)", args);
|
|
5708
6368
|
try {
|
|
5709
|
-
return await jobStatus(this.reactorClient, args);
|
|
6369
|
+
return await jobStatus(this.reactorClient, args, (id) => this.servesDocument(id, ctx));
|
|
5710
6370
|
} catch (error) {
|
|
5711
6371
|
this.logger.error("Error in jobStatus: @Error", error);
|
|
5712
6372
|
throw error;
|
|
@@ -5715,32 +6375,63 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5715
6375
|
documentOperations: async (_parent, args, ctx) => {
|
|
5716
6376
|
this.logger.debug("documentOperations(@args)", args);
|
|
5717
6377
|
try {
|
|
5718
|
-
const
|
|
6378
|
+
const { documentIdOrSlug, documentId, ...filter } = args.filter;
|
|
6379
|
+
const handle = await this.assertCanRead(requireOneOf({
|
|
6380
|
+
documentIdOrSlug,
|
|
6381
|
+
documentId
|
|
6382
|
+
}, "documentIdOrSlug", "documentId"), ctx);
|
|
5719
6383
|
return await documentOperations(this.reactorClient, {
|
|
5720
|
-
...args,
|
|
5721
6384
|
filter: {
|
|
5722
|
-
...
|
|
5723
|
-
|
|
5724
|
-
}
|
|
6385
|
+
...filter,
|
|
6386
|
+
documentIdOrSlug: handle.fetchIdentifier
|
|
6387
|
+
},
|
|
6388
|
+
paging: args.paging
|
|
5725
6389
|
}, this.viewSubject(ctx));
|
|
5726
6390
|
} catch (error) {
|
|
5727
6391
|
this.logger.error("Error in documentOperations: @Error", error);
|
|
5728
6392
|
throw error;
|
|
5729
6393
|
}
|
|
5730
6394
|
},
|
|
6395
|
+
/**
|
|
6396
|
+
* Gated on reading the document, the same gate every other read here
|
|
6397
|
+
* carries: a verdict is derived from the document's policy, so a caller
|
|
6398
|
+
* who may not read the document may not preflight against it either.
|
|
6399
|
+
* That gate decides whether the query runs; the answer comes entirely
|
|
6400
|
+
* from the reactor's decision model.
|
|
6401
|
+
*/
|
|
5731
6402
|
evaluateActions: async (_parent, args, ctx) => {
|
|
5732
6403
|
this.logger.debug("evaluateActions(@args)", args);
|
|
5733
6404
|
try {
|
|
5734
|
-
const
|
|
6405
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6406
|
+
const handle = await this.assertCanRead(documentIdOrSlug, ctx);
|
|
5735
6407
|
return await evaluateActions(this.reactorClient, {
|
|
5736
|
-
|
|
5737
|
-
|
|
6408
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6409
|
+
branch: args.branch,
|
|
6410
|
+
candidates: args.candidates
|
|
5738
6411
|
}, this.viewSubject(ctx));
|
|
5739
6412
|
} catch (error) {
|
|
5740
6413
|
this.logger.error("Error in evaluateActions: @Error", error);
|
|
5741
6414
|
throw error;
|
|
5742
6415
|
}
|
|
5743
6416
|
},
|
|
6417
|
+
syncHolds: async (_parent, args, ctx) => {
|
|
6418
|
+
const address = ctx.user?.address;
|
|
6419
|
+
if (!this.authorizationService.isSupremeAdmin(address)) {
|
|
6420
|
+
let bound;
|
|
6421
|
+
try {
|
|
6422
|
+
bound = args.remoteName ? this.syncManager.getByName(args.remoteName).meta.options.boundAddress : void 0;
|
|
6423
|
+
} catch {
|
|
6424
|
+
bound = void 0;
|
|
6425
|
+
}
|
|
6426
|
+
if (address === void 0 || bound !== address) throw new ForbiddenError("to list sync holds");
|
|
6427
|
+
}
|
|
6428
|
+
return syncHolds(this.syncManager, args);
|
|
6429
|
+
},
|
|
6430
|
+
peerAgreement: (_parent, args, ctx) => {
|
|
6431
|
+
const address = ctx.user?.address;
|
|
6432
|
+
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");
|
|
6433
|
+
return peerAgreement(this.syncManager, args);
|
|
6434
|
+
},
|
|
5744
6435
|
pollSyncEnvelopes: async (_parent, args, ctx) => {
|
|
5745
6436
|
this.logger.debug("pollSyncEnvelopes(@args)", args);
|
|
5746
6437
|
try {
|
|
@@ -5754,19 +6445,18 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5754
6445
|
const isAdmin = this.authorizationService.isSupremeAdmin(ctx.user?.address);
|
|
5755
6446
|
if (!isAdmin) await this.assertCanReadCanonical(driveId, ctx);
|
|
5756
6447
|
await this.#bindOrRefuseChannel(args.channelId, ctx);
|
|
6448
|
+
holdPollRefusals(this.syncManager, args.channelId, args.refusals);
|
|
6449
|
+
await recordPollMarkerRefusals(this.syncManager, args.channelId, args.refusals);
|
|
6450
|
+
await silenceUnversionedPoll(this.syncManager, args.channelId, args.manifestRevision);
|
|
5757
6451
|
const forbiddenIds = /* @__PURE__ */ new Set();
|
|
5758
6452
|
if (!isAdmin) {
|
|
5759
6453
|
if (await this.authorizationService.canRead(driveId, void 0)) await this.#collectForbiddenDocuments(remote.channel.outbox.items, forbiddenIds, ctx);
|
|
5760
6454
|
await this.#collectForbiddenDocuments(remote.channel.deadLetter.items, forbiddenIds, ctx);
|
|
5761
6455
|
}
|
|
5762
|
-
const
|
|
5763
|
-
const
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
ackOrdinal,
|
|
5767
|
-
deadLetters,
|
|
5768
|
-
hasMore
|
|
5769
|
-
};
|
|
6456
|
+
const gate = this.syncServingGate;
|
|
6457
|
+
const gated = gate ? [...remote.channel.outbox.items, ...remote.channel.deadLetter.items] : void 0;
|
|
6458
|
+
const heldOpIds = gate && gated ? await collectHeldSyncOperations(gated, gate, this.viewSubject(ctx)) : /* @__PURE__ */ new Set();
|
|
6459
|
+
return pollSyncEnvelopes(this.syncManager, args, forbiddenIds, heldOpIds, gated && new Set(gated.map((syncOp) => syncOp.id)));
|
|
5770
6460
|
} catch (error) {
|
|
5771
6461
|
this.logger.error("Error in pollSyncEnvelopes(@args): @Error", args, error);
|
|
5772
6462
|
throw error;
|
|
@@ -5777,11 +6467,15 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5777
6467
|
createDocument: async (_parent, args, ctx) => {
|
|
5778
6468
|
this.logger.debug("createDocument(@args)", args);
|
|
5779
6469
|
try {
|
|
5780
|
-
|
|
5781
|
-
|
|
6470
|
+
const parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
|
|
6471
|
+
if (parentIdOrSlug) {
|
|
6472
|
+
const parent = await document(this.reactorClient, { idOrSlug: parentIdOrSlug });
|
|
5782
6473
|
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
5783
6474
|
} else this.assertCanCreate(ctx);
|
|
5784
|
-
const result = await createDocument(this.reactorClient,
|
|
6475
|
+
const result = await createDocument(this.reactorClient, {
|
|
6476
|
+
document: args.document,
|
|
6477
|
+
parentIdOrSlug
|
|
6478
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
5785
6479
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
5786
6480
|
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
5787
6481
|
return result;
|
|
@@ -5793,11 +6487,15 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5793
6487
|
createEmptyDocument: async (_parent, args, ctx) => {
|
|
5794
6488
|
this.logger.debug("createEmptyDocument(@args)", args);
|
|
5795
6489
|
try {
|
|
5796
|
-
|
|
5797
|
-
|
|
6490
|
+
const parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
|
|
6491
|
+
if (parentIdOrSlug) {
|
|
6492
|
+
const parent = await document(this.reactorClient, { idOrSlug: parentIdOrSlug });
|
|
5798
6493
|
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
5799
6494
|
} else this.assertCanCreate(ctx);
|
|
5800
|
-
const result = await createEmptyDocument(this.reactorClient,
|
|
6495
|
+
const result = await createEmptyDocument(this.reactorClient, {
|
|
6496
|
+
documentType: args.documentType,
|
|
6497
|
+
parentIdOrSlug
|
|
6498
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
5801
6499
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
5802
6500
|
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
5803
6501
|
return result;
|
|
@@ -5809,11 +6507,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5809
6507
|
execute: async (_parent, args, ctx) => {
|
|
5810
6508
|
this.logger.debug("execute(@args)", args);
|
|
5811
6509
|
try {
|
|
5812
|
-
const
|
|
6510
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6511
|
+
const handle = await this.assertCanExecuteOperations(documentIdOrSlug, args.actions, ctx);
|
|
5813
6512
|
return await execute(this.reactorClient, {
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
6513
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6514
|
+
actions: args.actions,
|
|
6515
|
+
branch: args.branch
|
|
6516
|
+
}, this.viewSubject(ctx));
|
|
5817
6517
|
} catch (error) {
|
|
5818
6518
|
this.logger.error("Error in execute(@args): @Error", args, error);
|
|
5819
6519
|
throw error;
|
|
@@ -5822,10 +6522,12 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5822
6522
|
executeAsync: async (_parent, args, ctx) => {
|
|
5823
6523
|
this.logger.debug("executeAsync(@args)", args);
|
|
5824
6524
|
try {
|
|
5825
|
-
const
|
|
6525
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6526
|
+
const handle = await this.assertCanExecuteOperations(documentIdOrSlug, args.actions, ctx);
|
|
5826
6527
|
return await executeAsync(this.reactorClient, {
|
|
5827
|
-
|
|
5828
|
-
|
|
6528
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6529
|
+
actions: args.actions,
|
|
6530
|
+
branch: args.branch
|
|
5829
6531
|
});
|
|
5830
6532
|
} catch (error) {
|
|
5831
6533
|
this.logger.error("Error in executeAsync(@args): @Error", args, error);
|
|
@@ -5839,7 +6541,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5839
6541
|
return await mutateDocument(this.reactorClient, {
|
|
5840
6542
|
...args,
|
|
5841
6543
|
documentIdentifier: handle.fetchIdentifier
|
|
5842
|
-
});
|
|
6544
|
+
}, this.viewSubject(ctx));
|
|
5843
6545
|
} catch (error) {
|
|
5844
6546
|
this.logger.error("Error in mutateDocument(@args): @Error", args, error);
|
|
5845
6547
|
throw error;
|
|
@@ -5861,11 +6563,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5861
6563
|
renameDocument: async (_parent, args, ctx) => {
|
|
5862
6564
|
this.logger.debug("renameDocument(@args)", args);
|
|
5863
6565
|
try {
|
|
5864
|
-
const
|
|
6566
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6567
|
+
const handle = await this.assertCanWrite(documentIdOrSlug, ctx);
|
|
5865
6568
|
return await renameDocument(this.reactorClient, {
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
6569
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6570
|
+
name: args.name,
|
|
6571
|
+
branch: args.branch
|
|
6572
|
+
}, void 0, this.viewSubject(ctx));
|
|
5869
6573
|
} catch (error) {
|
|
5870
6574
|
this.logger.error("Error in renameDocument(@args): @Error", args, error);
|
|
5871
6575
|
throw error;
|
|
@@ -5874,11 +6578,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5874
6578
|
setPreferredEditor: async (_parent, args, ctx) => {
|
|
5875
6579
|
this.logger.debug("setPreferredEditor(@args)", args);
|
|
5876
6580
|
try {
|
|
5877
|
-
const
|
|
6581
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6582
|
+
const handle = await this.assertCanWrite(documentIdOrSlug, ctx);
|
|
5878
6583
|
return await setPreferredEditor(this.reactorClient, {
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
6584
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6585
|
+
preferredEditor: args.preferredEditor,
|
|
6586
|
+
branch: args.branch
|
|
6587
|
+
}, void 0, this.viewSubject(ctx));
|
|
5882
6588
|
} catch (error) {
|
|
5883
6589
|
this.logger.error("Error in setPreferredEditor(@args): @Error", args, error);
|
|
5884
6590
|
throw error;
|
|
@@ -5887,24 +6593,51 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5887
6593
|
addRelationship: async (_parent, args, ctx) => {
|
|
5888
6594
|
this.logger.debug("addRelationship(@args)", args);
|
|
5889
6595
|
try {
|
|
5890
|
-
const
|
|
6596
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6597
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6598
|
+
const handle = await this.assertCanWrite(sourceIdOrSlug, ctx);
|
|
5891
6599
|
return await addRelationship(this.reactorClient, {
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
6600
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6601
|
+
targetIdOrSlug,
|
|
6602
|
+
relationshipType: args.relationshipType,
|
|
6603
|
+
metadata: args.metadata,
|
|
6604
|
+
branch: args.branch
|
|
6605
|
+
}, this.viewSubject(ctx));
|
|
5895
6606
|
} catch (error) {
|
|
5896
6607
|
this.logger.error("Error in addRelationship(@args): @Error", args, error);
|
|
5897
6608
|
throw error;
|
|
5898
6609
|
}
|
|
5899
6610
|
},
|
|
6611
|
+
updateRelationship: async (_parent, args, ctx) => {
|
|
6612
|
+
this.logger.debug("updateRelationship(@args)", args);
|
|
6613
|
+
try {
|
|
6614
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6615
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6616
|
+
const handle = await this.assertCanWrite(sourceIdOrSlug, ctx);
|
|
6617
|
+
return await updateRelationship(this.reactorClient, {
|
|
6618
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6619
|
+
targetIdOrSlug,
|
|
6620
|
+
relationshipType: args.relationshipType,
|
|
6621
|
+
metadata: args.metadata,
|
|
6622
|
+
branch: args.branch
|
|
6623
|
+
}, this.viewSubject(ctx));
|
|
6624
|
+
} catch (error) {
|
|
6625
|
+
this.logger.error("Error in updateRelationship(@args): @Error", args, error);
|
|
6626
|
+
throw error;
|
|
6627
|
+
}
|
|
6628
|
+
},
|
|
5900
6629
|
removeRelationship: async (_parent, args, ctx) => {
|
|
5901
6630
|
this.logger.debug("removeRelationship(@args)", args);
|
|
5902
6631
|
try {
|
|
5903
|
-
const
|
|
6632
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6633
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6634
|
+
const handle = await this.assertCanWrite(sourceIdOrSlug, ctx);
|
|
5904
6635
|
return await removeRelationship(this.reactorClient, {
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
6636
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6637
|
+
targetIdOrSlug,
|
|
6638
|
+
relationshipType: args.relationshipType,
|
|
6639
|
+
branch: args.branch
|
|
6640
|
+
}, this.viewSubject(ctx));
|
|
5908
6641
|
} catch (error) {
|
|
5909
6642
|
this.logger.error("Error in removeRelationship(@args): @Error", args, error);
|
|
5910
6643
|
throw error;
|
|
@@ -5913,26 +6646,31 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5913
6646
|
moveRelationship: async (_parent, args, ctx) => {
|
|
5914
6647
|
this.logger.debug("moveRelationship(@args)", args);
|
|
5915
6648
|
try {
|
|
5916
|
-
const
|
|
5917
|
-
const
|
|
6649
|
+
const sourceParentIdOrSlug = requireOneOf(args, "sourceParentIdOrSlug", "sourceParentIdentifier");
|
|
6650
|
+
const targetParentIdOrSlug = requireOneOf(args, "targetParentIdOrSlug", "targetParentIdentifier");
|
|
6651
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6652
|
+
const sourceHandle = await this.assertCanWrite(sourceParentIdOrSlug, ctx);
|
|
6653
|
+
const targetHandle = await this.assertCanWrite(targetParentIdOrSlug, ctx);
|
|
5918
6654
|
return await moveRelationship(this.reactorClient, {
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
6655
|
+
sourceParentIdOrSlug: sourceHandle.fetchIdentifier,
|
|
6656
|
+
targetParentIdOrSlug: targetHandle.fetchIdentifier,
|
|
6657
|
+
targetIdOrSlug,
|
|
6658
|
+
relationshipType: args.relationshipType,
|
|
6659
|
+
branch: args.branch
|
|
6660
|
+
}, this.viewSubject(ctx));
|
|
5923
6661
|
} catch (error) {
|
|
5924
|
-
this.logger.error("Error in moveRelationship(@args): @Error
|
|
6662
|
+
this.logger.error("Error in moveRelationship(@args): @Error", args, error);
|
|
5925
6663
|
throw error;
|
|
5926
6664
|
}
|
|
5927
6665
|
},
|
|
5928
6666
|
deleteDocument: async (_parent, args, ctx) => {
|
|
5929
6667
|
this.logger.debug("deleteDocument(@args)", args);
|
|
5930
6668
|
try {
|
|
5931
|
-
const
|
|
5932
|
-
const driveIdToInvalidate = await this.#resolveDriveId(
|
|
6669
|
+
const idOrSlug = (await this.assertCanWrite(requireOneOf(args, "idOrSlug", "identifier"), ctx)).fetchIdentifier;
|
|
6670
|
+
const driveIdToInvalidate = await this.#resolveDriveId(idOrSlug);
|
|
5933
6671
|
const result = await deleteDocument(this.reactorClient, {
|
|
5934
|
-
|
|
5935
|
-
|
|
6672
|
+
idOrSlug,
|
|
6673
|
+
propagate: args.propagate
|
|
5936
6674
|
}, this.graphqlManager.reactorDriveClient);
|
|
5937
6675
|
if (result && driveIdToInvalidate) this.graphqlManager.driveOwnershipCache.remove(driveIdToInvalidate);
|
|
5938
6676
|
return result;
|
|
@@ -5944,14 +6682,14 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5944
6682
|
deleteDocuments: async (_parent, args, ctx) => {
|
|
5945
6683
|
this.logger.debug("deleteDocuments(@args)", args);
|
|
5946
6684
|
try {
|
|
5947
|
-
const
|
|
5948
|
-
for (const
|
|
5949
|
-
const handle = await this.assertCanWrite(
|
|
5950
|
-
|
|
6685
|
+
const idsOrSlugs = [];
|
|
6686
|
+
for (const idOrSlug of requireOneOf(args, "idsOrSlugs", "identifiers")) {
|
|
6687
|
+
const handle = await this.assertCanWrite(idOrSlug, ctx);
|
|
6688
|
+
idsOrSlugs.push(handle.fetchIdentifier);
|
|
5951
6689
|
}
|
|
5952
6690
|
return await deleteDocuments(this.reactorClient, {
|
|
5953
|
-
|
|
5954
|
-
|
|
6691
|
+
idsOrSlugs,
|
|
6692
|
+
propagate: args.propagate
|
|
5955
6693
|
});
|
|
5956
6694
|
} catch (error) {
|
|
5957
6695
|
this.logger.error("Error in deleteDocuments(@args): @Error", args, error);
|
|
@@ -6030,10 +6768,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
6030
6768
|
documentChanges: {
|
|
6031
6769
|
subscribe: (rootValue, args, ctx, info) => {
|
|
6032
6770
|
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) => {
|
|
6771
|
+
return withFilter(() => this.documentChanges.subscribe(this.viewSubject(ctx)), async (payload, filterArgs, filterCtx) => {
|
|
6037
6772
|
if (!payload) return false;
|
|
6038
6773
|
const search = {
|
|
6039
6774
|
type: filterArgs?.search?.type ?? void 0,
|
|
@@ -6060,6 +6795,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
6060
6795
|
}, async (payload, filterArgs, filterCtx) => {
|
|
6061
6796
|
if (!payload || !filterArgs) return false;
|
|
6062
6797
|
if (!matchesJobFilter(payload, filterArgs)) return false;
|
|
6798
|
+
if (!await this.servesDocument(payload.documentId, filterCtx)) return false;
|
|
6063
6799
|
if (this.authorizationService.isSupremeAdmin(filterCtx?.user?.address)) return true;
|
|
6064
6800
|
if (!payload.documentId) return false;
|
|
6065
6801
|
return this.canReadDocument(payload.documentId, filterCtx);
|
|
@@ -6088,10 +6824,10 @@ const ADMIN_USERS = getAdminUsers();
|
|
|
6088
6824
|
//#endregion
|
|
6089
6825
|
//#region src/graphql/system/version.ts
|
|
6090
6826
|
function getVersion() {
|
|
6091
|
-
return "6.2.3-dev.
|
|
6827
|
+
return "6.2.3-dev.40";
|
|
6092
6828
|
}
|
|
6093
6829
|
function getGitHash() {
|
|
6094
|
-
return "
|
|
6830
|
+
return "9a25fe4dcac53275b3e4e818fa6259f56a2532bb";
|
|
6095
6831
|
}
|
|
6096
6832
|
function getGitUrl() {
|
|
6097
6833
|
return buildTreeUrl(getGitHash());
|
|
@@ -6120,6 +6856,10 @@ var SystemSubgraph = class extends BaseSubgraph {
|
|
|
6120
6856
|
};
|
|
6121
6857
|
//#endregion
|
|
6122
6858
|
//#region src/packages/http-loader.ts
|
|
6859
|
+
const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
6860
|
+
function piecesBaseUrl(registryUrl, packageName, version) {
|
|
6861
|
+
return `${registryUrl.endsWith("/") ? registryUrl : `${registryUrl}/`}-/cdn/${packageName}@${version}/node/pieces/`;
|
|
6862
|
+
}
|
|
6123
6863
|
/**
|
|
6124
6864
|
* Extract subgraph classes from the imported subgraphs/index.mjs module shape.
|
|
6125
6865
|
*
|
|
@@ -6192,7 +6932,8 @@ var HttpPackageLoader = class {
|
|
|
6192
6932
|
async loadUpgradeManifests(packageSpec) {
|
|
6193
6933
|
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
6194
6934
|
if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
|
|
6195
|
-
const
|
|
6935
|
+
const module = await import(`${this.registryUrl}-/cdn/${packageSpec}/node/document-models/index.mjs`);
|
|
6936
|
+
const manifests = extractUpgradeManifests(module);
|
|
6196
6937
|
if (manifests.length > 0) this.logger.verbose(`Loaded ${manifests.length} upgrade manifests from ${packageName}`);
|
|
6197
6938
|
return manifests;
|
|
6198
6939
|
}
|
|
@@ -6218,6 +6959,27 @@ var HttpPackageLoader = class {
|
|
|
6218
6959
|
this.logger.verbose(`No processor factory found in ${packageName}`);
|
|
6219
6960
|
return null;
|
|
6220
6961
|
}
|
|
6962
|
+
async loadPieces(packageSpec) {
|
|
6963
|
+
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
6964
|
+
if (!this.isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
|
|
6965
|
+
const version = await this.packageVersion(packageSpec);
|
|
6966
|
+
if (!version) {
|
|
6967
|
+
this.logger.verbose(`No package version found for: ${packageSpec}`);
|
|
6968
|
+
return [];
|
|
6969
|
+
}
|
|
6970
|
+
const base = piecesBaseUrl(this.registryUrl, packageName, version);
|
|
6971
|
+
this.logger.verbose(`Importing pieces from: ${base}index.mjs`);
|
|
6972
|
+
let module;
|
|
6973
|
+
try {
|
|
6974
|
+
module = await import(`${base}index.mjs`);
|
|
6975
|
+
} catch (error) {
|
|
6976
|
+
this.logger.verbose("No pieces found for @package: @error", packageName, error);
|
|
6977
|
+
return [];
|
|
6978
|
+
}
|
|
6979
|
+
const pieces = piecesFromCdnList(module, base, packageName, this.logger, version);
|
|
6980
|
+
this.logger.verbose(`Loaded ${pieces.length} pieces from ${packageName}`);
|
|
6981
|
+
return pieces;
|
|
6982
|
+
}
|
|
6221
6983
|
/**
|
|
6222
6984
|
* Load document models from multiple packages.
|
|
6223
6985
|
* Continues loading even if some packages fail.
|
|
@@ -6234,13 +6996,23 @@ var HttpPackageLoader = class {
|
|
|
6234
6996
|
logger?.info(logMsg);
|
|
6235
6997
|
this.logger.info(logMsg);
|
|
6236
6998
|
} catch (error) {
|
|
6237
|
-
|
|
6238
|
-
logger
|
|
6239
|
-
this.logger.error(errMsg, error);
|
|
6999
|
+
logger?.error(`Failed to load package ${trimmedName}`, error);
|
|
7000
|
+
this.logger.error("Failed to load package @package: @error", trimmedName, error);
|
|
6240
7001
|
}
|
|
6241
7002
|
}
|
|
6242
7003
|
return allModels;
|
|
6243
7004
|
}
|
|
7005
|
+
async packageVersion(packageSpec) {
|
|
7006
|
+
try {
|
|
7007
|
+
const response = await fetch(`${this.registryUrl}-/cdn/${packageSpec}/package.json`);
|
|
7008
|
+
if (!response.ok) return void 0;
|
|
7009
|
+
const pkg = await response.json();
|
|
7010
|
+
return typeof pkg.version === "string" && EXACT_VERSION.test(pkg.version) ? pkg.version : void 0;
|
|
7011
|
+
} catch (error) {
|
|
7012
|
+
this.logger.verbose("Could not read the version of @package: @error", packageSpec, error);
|
|
7013
|
+
return;
|
|
7014
|
+
}
|
|
7015
|
+
}
|
|
6244
7016
|
isValidPackageName(name) {
|
|
6245
7017
|
return /^(@[a-z0-9][-a-z0-9._]*\/)?[a-z0-9][-a-z0-9._]*$/i.test(name) && !name.includes("..") && name.length <= 214;
|
|
6246
7018
|
}
|
|
@@ -6315,8 +7087,13 @@ var HttpDocumentModelLoader = class {
|
|
|
6315
7087
|
* This class is used to load packages using the import keyword.
|
|
6316
7088
|
*/
|
|
6317
7089
|
var ImportPackageLoader = class {
|
|
7090
|
+
hostDir;
|
|
6318
7091
|
logger = childLogger(["reactor-api", "import-loader"]);
|
|
6319
7092
|
name = "ImportPackageLoader";
|
|
7093
|
+
constructor(hostDir = process.cwd()) {
|
|
7094
|
+
this.hostDir = hostDir;
|
|
7095
|
+
}
|
|
7096
|
+
reportedUnbuilt = /* @__PURE__ */ new Set();
|
|
6320
7097
|
async loadDocumentModels(identifier) {
|
|
6321
7098
|
this.logger.verbose(`Loading document models from package: ${identifier}`);
|
|
6322
7099
|
const pkgModule = await loadDocumentModels(identifier);
|
|
@@ -6359,6 +7136,31 @@ var ImportPackageLoader = class {
|
|
|
6359
7136
|
this.logger.verbose(` ➜ No Processor Factory found: ${identifier}`);
|
|
6360
7137
|
return null;
|
|
6361
7138
|
}
|
|
7139
|
+
async loadPieces(identifier) {
|
|
7140
|
+
this.logger.verbose(`Loading pieces from package: ${identifier}`);
|
|
7141
|
+
const { root, listPath } = pieceListLocation(identifier, this.hostDir);
|
|
7142
|
+
if (!existsSync(listPath)) {
|
|
7143
|
+
this.reportUnbuilt(identifier, root, listPath);
|
|
7144
|
+
return [];
|
|
7145
|
+
}
|
|
7146
|
+
const module = await import(
|
|
7147
|
+
/* @vite-ignore */
|
|
7148
|
+
pathToFileURL(listPath).href
|
|
7149
|
+
);
|
|
7150
|
+
const pieces = piecesFromListModule(module, {
|
|
7151
|
+
root,
|
|
7152
|
+
identifier,
|
|
7153
|
+
logger: this.logger
|
|
7154
|
+
});
|
|
7155
|
+
this.logger.verbose(` ➜ Loaded ${pieces.length} Pieces from: ${identifier}`);
|
|
7156
|
+
return pieces;
|
|
7157
|
+
}
|
|
7158
|
+
reportUnbuilt(identifier, root, listPath) {
|
|
7159
|
+
if (!isAbsolute(identifier) || this.reportedUnbuilt.has(identifier)) return;
|
|
7160
|
+
if (!existsSync(join(root, PIECE_SOURCE_LIST))) return;
|
|
7161
|
+
this.reportedUnbuilt.add(identifier);
|
|
7162
|
+
this.logger.warn("@pkg declares pieces but @list was never built; run `ph build`", identifier, listPath);
|
|
7163
|
+
}
|
|
6362
7164
|
};
|
|
6363
7165
|
//#endregion
|
|
6364
7166
|
//#region src/packages/package-manager.ts
|
|
@@ -6374,10 +7176,12 @@ var ImportPackageLoader = class {
|
|
|
6374
7176
|
* either bare (`'pkg'`) or as a subpath (`'pkg/subgraphs'`), since loaders
|
|
6375
7177
|
* import sub-entries like `${pkg}/subgraphs` / `${pkg}/document-models`.
|
|
6376
7178
|
*/
|
|
6377
|
-
function isExpectedLoaderMiss(error, pkg) {
|
|
7179
|
+
function isExpectedLoaderMiss(error, pkg, subPath) {
|
|
6378
7180
|
if (!(error instanceof Error)) return false;
|
|
6379
7181
|
const code = error.code;
|
|
6380
7182
|
if (code === "ERR_UNSUPPORTED_DIR_IMPORT") return true;
|
|
7183
|
+
if (code === "ERR_PACKAGE_PATH_NOT_EXPORTED" && subPath !== void 0 && error.message.includes(`'./${subPath}'`)) return true;
|
|
7184
|
+
if (subPath !== void 0 && new RegExp(`^Failed to fetch \\S*/${subPath}/index\\.mjs: 404$`).test(error.message)) return true;
|
|
6381
7185
|
if (error.message.startsWith("Invalid package name:")) return true;
|
|
6382
7186
|
if (code === "ERR_MODULE_NOT_FOUND") return error.message.includes(`'${pkg}'`) || error.message.includes(`"${pkg}"`) || error.message.includes(`'${pkg}/`) || error.message.includes(`"${pkg}/`);
|
|
6383
7187
|
return false;
|
|
@@ -6393,12 +7197,14 @@ function getUniqueUpgradeManifests(...upgradeManifests) {
|
|
|
6393
7197
|
return Array.from(uniqueManifests.values());
|
|
6394
7198
|
}
|
|
6395
7199
|
var PackageManager = class {
|
|
7200
|
+
options;
|
|
6396
7201
|
logger = childLogger(["reactor-api", "package-manager"]);
|
|
6397
7202
|
loaders;
|
|
6398
7203
|
docModelsMap = /* @__PURE__ */ new Map();
|
|
6399
7204
|
upgradeManifestsMap = /* @__PURE__ */ new Map();
|
|
6400
7205
|
subgraphsMap = /* @__PURE__ */ new Map();
|
|
6401
7206
|
processorMap = /* @__PURE__ */ new Map();
|
|
7207
|
+
piecesMap = /* @__PURE__ */ new Map();
|
|
6402
7208
|
configWatcher;
|
|
6403
7209
|
debouncedUpdateCallbacks = /* @__PURE__ */ new Map();
|
|
6404
7210
|
eventEmitter = new EventEmitter();
|
|
@@ -6417,10 +7223,12 @@ var PackageManager = class {
|
|
|
6417
7223
|
const upgradeManifestsMap = await this.loadUpgradeManifests(packages);
|
|
6418
7224
|
const subgraphsMap = await this.loadSubgraphs(packages);
|
|
6419
7225
|
const processorsMap = await this.loadProcessors(packages);
|
|
7226
|
+
const piecesMap = await this.loadPieces(packages);
|
|
6420
7227
|
this.upgradeManifestsMap = upgradeManifestsMap;
|
|
6421
7228
|
this.updatePackagesMap(documentModelsMap);
|
|
6422
7229
|
this.updateSubgraphsMap(subgraphsMap);
|
|
6423
7230
|
this.updateProcessorsMap(processorsMap);
|
|
7231
|
+
this.updatePiecesMap(piecesMap);
|
|
6424
7232
|
try {
|
|
6425
7233
|
this.subscribePackages(packages);
|
|
6426
7234
|
} catch (error) {
|
|
@@ -6430,7 +7238,8 @@ var PackageManager = class {
|
|
|
6430
7238
|
documentModels: getUniqueDocumentModels(...Array.from(documentModelsMap.values())),
|
|
6431
7239
|
upgradeManifests: this.getUniqueUpgradeManifests(),
|
|
6432
7240
|
subgraphs: subgraphsMap,
|
|
6433
|
-
processors: processorsMap
|
|
7241
|
+
processors: processorsMap,
|
|
7242
|
+
pieces: piecesMap
|
|
6434
7243
|
};
|
|
6435
7244
|
}
|
|
6436
7245
|
async loadDocumentModels(packages) {
|
|
@@ -6499,7 +7308,7 @@ var PackageManager = class {
|
|
|
6499
7308
|
loader: loader.name,
|
|
6500
7309
|
error
|
|
6501
7310
|
});
|
|
6502
|
-
this.logger.debug(
|
|
7311
|
+
this.logger.debug("[@loader] Failed to load subgraphs from package @package: @error", loader.name, pkg, error);
|
|
6503
7312
|
}
|
|
6504
7313
|
this.maybeWarnAllLoadersFailed("subgraphs", pkg, succeeded, failures);
|
|
6505
7314
|
subgraphsMap.set(pkg, allSubgraphs);
|
|
@@ -6523,16 +7332,41 @@ var PackageManager = class {
|
|
|
6523
7332
|
loader: loader.name,
|
|
6524
7333
|
error
|
|
6525
7334
|
});
|
|
6526
|
-
this.logger.debug(
|
|
7335
|
+
this.logger.debug("[@loader] Failed to load processors from package @package: @error", loader.name, pkg, error);
|
|
6527
7336
|
}
|
|
6528
7337
|
this.maybeWarnAllLoadersFailed("processors", pkg, succeeded, failures);
|
|
6529
7338
|
processorsMap.set(pkg, allProcessors);
|
|
6530
7339
|
}
|
|
6531
7340
|
return processorsMap;
|
|
6532
7341
|
}
|
|
7342
|
+
async loadPieces(packages) {
|
|
7343
|
+
this.logger.debug(`Loading pieces from packages: ${packages.join(", ")}`);
|
|
7344
|
+
const piecesMap = /* @__PURE__ */ new Map();
|
|
7345
|
+
for (const pkg of packages) {
|
|
7346
|
+
const allPieces = [];
|
|
7347
|
+
const failures = [];
|
|
7348
|
+
let succeeded = false;
|
|
7349
|
+
for (const loader of this.loaders) try {
|
|
7350
|
+
allPieces.push(...await loader.loadPieces(pkg));
|
|
7351
|
+
succeeded = true;
|
|
7352
|
+
break;
|
|
7353
|
+
} catch (error) {
|
|
7354
|
+
failures.push({
|
|
7355
|
+
loader: loader.name,
|
|
7356
|
+
error
|
|
7357
|
+
});
|
|
7358
|
+
this.logger.debug("[@loader] Failed to load pieces from package @package: @error", loader.name, pkg, error);
|
|
7359
|
+
}
|
|
7360
|
+
this.maybeWarnAllLoadersFailed("pieces", pkg, succeeded, failures);
|
|
7361
|
+
if (allPieces.length > 0) this.logger.info("Loaded @count piece(s) from package @pkg: @pieces", allPieces.length, pkg, allPieces.map((piece) => piece.name).join(", "));
|
|
7362
|
+
piecesMap.set(pkg, allPieces);
|
|
7363
|
+
}
|
|
7364
|
+
return piecesMap;
|
|
7365
|
+
}
|
|
6533
7366
|
maybeWarnAllLoadersFailed(kind, pkg, succeeded, failures) {
|
|
6534
7367
|
if (succeeded || failures.length === 0) return;
|
|
6535
|
-
const
|
|
7368
|
+
const subPath = kind === "document models" ? "document-models" : kind;
|
|
7369
|
+
const realFailures = failures.filter(({ error }) => !isExpectedLoaderMiss(error, pkg, subPath));
|
|
6536
7370
|
if (realFailures.length === 0) return;
|
|
6537
7371
|
const details = realFailures.map(({ loader, error }) => {
|
|
6538
7372
|
return ` [${loader}] ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -6572,6 +7406,15 @@ var PackageManager = class {
|
|
|
6572
7406
|
else processorsMap.set(pkg, pkgProcessors);
|
|
6573
7407
|
this.updateProcessorsMap(processorsMap);
|
|
6574
7408
|
}
|
|
7409
|
+
async updatePiecesForPackage(pkg) {
|
|
7410
|
+
this.logger.debug(`Updating pieces for package: ${pkg}`);
|
|
7411
|
+
const pieces = await this.loadPieces([pkg]);
|
|
7412
|
+
const piecesMap = new Map(this.piecesMap);
|
|
7413
|
+
const pkgPieces = pieces.get(pkg) ?? [];
|
|
7414
|
+
if (pkgPieces.length === 0) piecesMap.delete(pkg);
|
|
7415
|
+
else piecesMap.set(pkg, pkgPieces);
|
|
7416
|
+
this.updatePiecesMap(piecesMap);
|
|
7417
|
+
}
|
|
6575
7418
|
/** Debounced per key so repeated subscribePackages calls reuse one timer. */
|
|
6576
7419
|
getDebouncedUpdateCallback(key, update) {
|
|
6577
7420
|
if (!this.debouncedUpdateCallbacks.has(key)) {
|
|
@@ -6590,10 +7433,12 @@ var PackageManager = class {
|
|
|
6590
7433
|
const onDocumentModels = this.getDebouncedUpdateCallback(`${pkg}:document-models`, () => this.updateDocumentModelsForPackage(pkg));
|
|
6591
7434
|
const onSubgraphs = this.getDebouncedUpdateCallback(`${pkg}:subgraphs`, () => this.updateSubgraphsForPackage(pkg));
|
|
6592
7435
|
const onProcessors = this.getDebouncedUpdateCallback(`${pkg}:processors`, () => this.updateProcessorsForPackage(pkg));
|
|
7436
|
+
const onPieces = this.getDebouncedUpdateCallback(`${pkg}:pieces`, () => this.updatePiecesForPackage(pkg));
|
|
6593
7437
|
for (const loader of this.loaders) {
|
|
6594
7438
|
if (loader.onDocumentModelsChange) unsubs.push(loader.onDocumentModelsChange(pkg, onDocumentModels));
|
|
6595
7439
|
if (loader.onSubgraphsChange) unsubs.push(loader.onSubgraphsChange(pkg, onSubgraphs));
|
|
6596
7440
|
if (loader.onProcessorsChange) unsubs.push(loader.onProcessorsChange(pkg, onProcessors));
|
|
7441
|
+
if (loader.onPiecesChange) unsubs.push(loader.onPiecesChange(pkg, onPieces));
|
|
6597
7442
|
}
|
|
6598
7443
|
}
|
|
6599
7444
|
return () => {
|
|
@@ -6641,6 +7486,19 @@ var PackageManager = class {
|
|
|
6641
7486
|
this.processorMap = processorsMap;
|
|
6642
7487
|
this.eventEmitter.emit("processorsChange", processorsMap);
|
|
6643
7488
|
}
|
|
7489
|
+
updatePiecesMap(piecesMap) {
|
|
7490
|
+
const oldPackages = Array.from(this.piecesMap.keys());
|
|
7491
|
+
const newPackages = Array.from(piecesMap.keys());
|
|
7492
|
+
oldPackages.filter((pkg) => !newPackages.includes(pkg)).forEach((pkg) => {
|
|
7493
|
+
this.logger.info("Removed pieces from: @pkg", pkg);
|
|
7494
|
+
});
|
|
7495
|
+
this.piecesMap = piecesMap;
|
|
7496
|
+
this.eventEmitter.emit("piecesChange", piecesMap);
|
|
7497
|
+
}
|
|
7498
|
+
/** The pieces every loaded package ships, keyed by the package. */
|
|
7499
|
+
getPieces() {
|
|
7500
|
+
return new Map(this.piecesMap);
|
|
7501
|
+
}
|
|
6644
7502
|
/**
|
|
6645
7503
|
* Remove `pkg` from all package maps and emit the change events, tearing
|
|
6646
7504
|
* down everything the package registered (subgraphs, processors, document
|
|
@@ -6659,9 +7517,11 @@ var PackageManager = class {
|
|
|
6659
7517
|
this.upgradeManifestsMap = dropKey(this.upgradeManifestsMap);
|
|
6660
7518
|
this.subgraphsMap = dropKey(this.subgraphsMap);
|
|
6661
7519
|
this.processorMap = dropKey(this.processorMap);
|
|
7520
|
+
this.piecesMap = dropKey(this.piecesMap);
|
|
6662
7521
|
this.updatePackagesMap(this.docModelsMap);
|
|
6663
7522
|
this.updateSubgraphsMap(this.subgraphsMap);
|
|
6664
7523
|
this.updateProcessorsMap(this.processorMap);
|
|
7524
|
+
this.updatePiecesMap(this.piecesMap);
|
|
6665
7525
|
}
|
|
6666
7526
|
onDocumentModelsChange(handler) {
|
|
6667
7527
|
this.eventEmitter.on("documentModelsChange", handler);
|
|
@@ -6672,6 +7532,9 @@ var PackageManager = class {
|
|
|
6672
7532
|
onProcessorsChange(handler) {
|
|
6673
7533
|
this.eventEmitter.on("processorsChange", handler);
|
|
6674
7534
|
}
|
|
7535
|
+
onPiecesChange(handler) {
|
|
7536
|
+
this.eventEmitter.on("piecesChange", handler);
|
|
7537
|
+
}
|
|
6675
7538
|
};
|
|
6676
7539
|
//#endregion
|
|
6677
7540
|
//#region src/config.ts
|
|
@@ -6689,20 +7552,55 @@ async function createStartupAttachmentBackend({ db, env = process.env, s3Depende
|
|
|
6689
7552
|
}
|
|
6690
7553
|
//#endregion
|
|
6691
7554
|
//#region src/services/attachment-access.service.ts
|
|
7555
|
+
/** The scope an attachment's bytes belong to: the document's own domain state. */
|
|
7556
|
+
const ATTACHMENT_SCOPE = "global";
|
|
7557
|
+
/** The branch an attachment reference is resolved against. */
|
|
7558
|
+
const ATTACHMENT_BRANCH = "main";
|
|
6692
7559
|
const HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
6693
7560
|
/**
|
|
6694
7561
|
* Composes document authorization with the projected document/ref
|
|
6695
7562
|
* relationship. Order is fixed: validate ref, resolve the canonical document
|
|
6696
|
-
* id,
|
|
6697
|
-
*
|
|
6698
|
-
* attachment
|
|
7563
|
+
* id, decide the read, check the reactor's read gate serves the document, then
|
|
7564
|
+
* check the reference index and that the subject may read a scope whose
|
|
7565
|
+
* operations reference the attachment. A denied document never reaches the
|
|
7566
|
+
* reference reader, and the facade never touches attachment metadata, storage
|
|
7567
|
+
* backends, or presigners.
|
|
7568
|
+
*
|
|
7569
|
+
* The named document decides alone: an attachment several documents reference
|
|
7570
|
+
* is readable through each of them on that document's own terms.
|
|
7571
|
+
*
|
|
7572
|
+
* The read is decided by the document's own policy when this composition has a
|
|
7573
|
+
* model to enforce it with, and by the host's permission tables when it does
|
|
7574
|
+
* not. That is not a preference between two equivalent answers: a host running
|
|
7575
|
+
* document policies keeps no rows in the permission tables, so asking them
|
|
7576
|
+
* about such a document returns whatever the host-wide policy says — under
|
|
7577
|
+
* `OPEN`, `true`, for everyone. Serving bytes on that answer hands the file to
|
|
7578
|
+
* anyone who learns its hash, which the document's own state may well have told
|
|
7579
|
+
* them. Both are kept because they are the two halves of one rule: an
|
|
7580
|
+
* attachment is readable by whoever may read the document that references it,
|
|
7581
|
+
* and each composition can only express that in its own terms. The caller
|
|
7582
|
+
* decision mirrors the routes' `requireAuth`.
|
|
6699
7583
|
*/
|
|
6700
7584
|
var AttachmentAccessService = class {
|
|
6701
|
-
|
|
7585
|
+
resolveCanonicalId;
|
|
7586
|
+
authorization;
|
|
7587
|
+
references;
|
|
7588
|
+
projection;
|
|
7589
|
+
readGate;
|
|
7590
|
+
scopeGate;
|
|
7591
|
+
options;
|
|
7592
|
+
constructor(resolveCanonicalId, authorization, references, projection, readGate, scopeGate, options = {}) {
|
|
6702
7593
|
this.resolveCanonicalId = resolveCanonicalId;
|
|
6703
7594
|
this.authorization = authorization;
|
|
6704
7595
|
this.references = references;
|
|
6705
7596
|
this.projection = projection;
|
|
7597
|
+
this.readGate = readGate;
|
|
7598
|
+
this.scopeGate = scopeGate;
|
|
7599
|
+
this.options = options;
|
|
7600
|
+
}
|
|
7601
|
+
admitCaller(request) {
|
|
7602
|
+
if ((request.intent === "read" ? this.options.refuseAnonymousReads ?? false : this.options.refuseAnonymousWrites ?? true) && !request.userAddress) return Promise.resolve({ kind: "unauthenticated" });
|
|
7603
|
+
return Promise.resolve({ kind: "admitted" });
|
|
6706
7604
|
}
|
|
6707
7605
|
async canReadAttachment(request) {
|
|
6708
7606
|
if (this.projection.status !== "available") return { kind: "projection-unavailable" };
|
|
@@ -6714,14 +7612,51 @@ var AttachmentAccessService = class {
|
|
|
6714
7612
|
} catch {
|
|
6715
7613
|
return { kind: "denied" };
|
|
6716
7614
|
}
|
|
6717
|
-
if (!await this.
|
|
6718
|
-
|
|
7615
|
+
if (!await this.canReadDocument(documentId, request)) return { kind: "denied" };
|
|
7616
|
+
const subject = {
|
|
7617
|
+
address: request.userAddress,
|
|
7618
|
+
key: request.appKey
|
|
7619
|
+
};
|
|
7620
|
+
let served;
|
|
7621
|
+
try {
|
|
7622
|
+
served = await this.readGate.isServed(documentId, { subject });
|
|
7623
|
+
} catch {
|
|
7624
|
+
return { kind: "denied" };
|
|
7625
|
+
}
|
|
7626
|
+
if (!served) return { kind: "denied" };
|
|
7627
|
+
const scopes = await this.references.referencingScopes(documentId, ref);
|
|
7628
|
+
if (scopes.length === 0) return { kind: "denied" };
|
|
7629
|
+
let held;
|
|
7630
|
+
try {
|
|
7631
|
+
held = (await this.readGate.get(documentId, {
|
|
7632
|
+
subject,
|
|
7633
|
+
scopes
|
|
7634
|
+
})).state;
|
|
7635
|
+
} catch {
|
|
7636
|
+
return { kind: "denied" };
|
|
7637
|
+
}
|
|
7638
|
+
if (!scopes.some((scope) => scope in held)) return { kind: "denied" };
|
|
6719
7639
|
return {
|
|
6720
7640
|
kind: "allowed",
|
|
6721
7641
|
documentId,
|
|
6722
7642
|
ref
|
|
6723
7643
|
};
|
|
6724
7644
|
}
|
|
7645
|
+
/**
|
|
7646
|
+
* Whether the caller may read the state the reference lives in.
|
|
7647
|
+
*
|
|
7648
|
+
* A failure here is rethrown rather than turned into a denial. A read side
|
|
7649
|
+
* that is down must not read as a policy: a denial nobody can distinguish
|
|
7650
|
+
* from a refusal is one nobody investigates, and the route answers 500 for a
|
|
7651
|
+
* reason.
|
|
7652
|
+
*/
|
|
7653
|
+
async canReadDocument(documentId, request) {
|
|
7654
|
+
if (!this.scopeGate) return this.authorization.canRead(documentId, request.userAddress);
|
|
7655
|
+
return (await this.scopeGate.scopePredicateById(documentId, {
|
|
7656
|
+
address: request.userAddress,
|
|
7657
|
+
key: request.appKey
|
|
7658
|
+
}, ATTACHMENT_BRANCH))(ATTACHMENT_SCOPE);
|
|
7659
|
+
}
|
|
6725
7660
|
};
|
|
6726
7661
|
/**
|
|
6727
7662
|
* Parses and canonicalizes a caller-supplied ref through the shared parser.
|
|
@@ -6742,6 +7677,140 @@ function normalizeAttachmentRef(value) {
|
|
|
6742
7677
|
return createRef(hash, version);
|
|
6743
7678
|
}
|
|
6744
7679
|
//#endregion
|
|
7680
|
+
//#region src/services/authorized-attachment.service.ts
|
|
7681
|
+
/** Matches the attachment routes' `documentId` bound. */
|
|
7682
|
+
const MAX_DOCUMENT_ID_LEN = 512;
|
|
7683
|
+
/** The reference index is not maintained in this composition. */
|
|
7684
|
+
var AttachmentAccessUnavailable = class extends Error {
|
|
7685
|
+
constructor() {
|
|
7686
|
+
super("Attachment access is unavailable");
|
|
7687
|
+
this.name = "AttachmentAccessUnavailable";
|
|
7688
|
+
}
|
|
7689
|
+
};
|
|
7690
|
+
/** The access decision itself failed; the cause is logged, not surfaced. */
|
|
7691
|
+
var AttachmentAccessFailed = class extends Error {
|
|
7692
|
+
constructor(cause) {
|
|
7693
|
+
super("Attachment access decision failed", { cause });
|
|
7694
|
+
this.name = "AttachmentAccessFailed";
|
|
7695
|
+
}
|
|
7696
|
+
};
|
|
7697
|
+
/** An `IAttachmentService` whose reads and writes are decided for one subject. */
|
|
7698
|
+
var AuthorizedAttachmentService = class {
|
|
7699
|
+
inner;
|
|
7700
|
+
access;
|
|
7701
|
+
subject;
|
|
7702
|
+
logger;
|
|
7703
|
+
constructor(inner, access, subject, logger) {
|
|
7704
|
+
this.inner = inner;
|
|
7705
|
+
this.access = access;
|
|
7706
|
+
this.subject = subject;
|
|
7707
|
+
this.logger = logger;
|
|
7708
|
+
}
|
|
7709
|
+
/** Hash-first only: an upload-first handle can send after it expires. */
|
|
7710
|
+
async reserve(options) {
|
|
7711
|
+
const { mimeType, fileName, extension, clientHash, sizeBytes } = options;
|
|
7712
|
+
if (clientHash === void 0) throw new Error("Attachment reservations require a client hash");
|
|
7713
|
+
const copy = {
|
|
7714
|
+
mimeType,
|
|
7715
|
+
fileName,
|
|
7716
|
+
extension,
|
|
7717
|
+
clientHash,
|
|
7718
|
+
sizeBytes
|
|
7719
|
+
};
|
|
7720
|
+
await this.admit("write");
|
|
7721
|
+
validateReserveMetadata(copy);
|
|
7722
|
+
return this.inner.reserve(copy);
|
|
7723
|
+
}
|
|
7724
|
+
async stat(ref, options) {
|
|
7725
|
+
const decision = await this.decideRead(ref, options);
|
|
7726
|
+
return this.inner.stat(decision.ref, {
|
|
7727
|
+
...options,
|
|
7728
|
+
documentId: decision.documentId
|
|
7729
|
+
});
|
|
7730
|
+
}
|
|
7731
|
+
async get(ref, options) {
|
|
7732
|
+
const decision = await this.decideRead(ref, options);
|
|
7733
|
+
return this.inner.get(decision.ref, {
|
|
7734
|
+
...options,
|
|
7735
|
+
documentId: decision.documentId
|
|
7736
|
+
});
|
|
7737
|
+
}
|
|
7738
|
+
async getDownloadTarget(ref, options) {
|
|
7739
|
+
const decision = await this.decideRead(ref, options);
|
|
7740
|
+
return this.inner.getDownloadTarget(decision.ref, {
|
|
7741
|
+
...options,
|
|
7742
|
+
documentId: decision.documentId
|
|
7743
|
+
});
|
|
7744
|
+
}
|
|
7745
|
+
async decideRead(ref, options) {
|
|
7746
|
+
const documentId = readDocumentId(options);
|
|
7747
|
+
if (documentId === null) throw new AttachmentNotFound(ref);
|
|
7748
|
+
await this.admit("read");
|
|
7749
|
+
let decision;
|
|
7750
|
+
try {
|
|
7751
|
+
decision = await this.access.canReadAttachment({
|
|
7752
|
+
documentId,
|
|
7753
|
+
attachmentRef: ref,
|
|
7754
|
+
userAddress: this.subject.address,
|
|
7755
|
+
appKey: this.subject.key
|
|
7756
|
+
});
|
|
7757
|
+
} catch (err) {
|
|
7758
|
+
throw this.accessFailed(err);
|
|
7759
|
+
}
|
|
7760
|
+
if (decision.kind === "allowed") return {
|
|
7761
|
+
documentId: decision.documentId,
|
|
7762
|
+
ref: decision.ref
|
|
7763
|
+
};
|
|
7764
|
+
if (decision.kind === "projection-unavailable") throw new AttachmentAccessUnavailable();
|
|
7765
|
+
throw new AttachmentNotFound(ref);
|
|
7766
|
+
}
|
|
7767
|
+
async admit(intent) {
|
|
7768
|
+
let result;
|
|
7769
|
+
try {
|
|
7770
|
+
result = await this.access.admitCaller({
|
|
7771
|
+
intent,
|
|
7772
|
+
userAddress: this.subject.address,
|
|
7773
|
+
appKey: this.subject.key
|
|
7774
|
+
});
|
|
7775
|
+
} catch (err) {
|
|
7776
|
+
throw this.accessFailed(err);
|
|
7777
|
+
}
|
|
7778
|
+
if (result.kind !== "admitted") throw new AuthenticationRequiredError();
|
|
7779
|
+
}
|
|
7780
|
+
accessFailed(err) {
|
|
7781
|
+
this.logger.error("Attachment access decision failed: @error", err);
|
|
7782
|
+
return new AttachmentAccessFailed(err);
|
|
7783
|
+
}
|
|
7784
|
+
};
|
|
7785
|
+
/** The options' `documentId` when it is one the routes would accept, else null. */
|
|
7786
|
+
function readDocumentId(options) {
|
|
7787
|
+
if (typeof options !== "object" || options === null) return null;
|
|
7788
|
+
if (isAbortSignal(options)) return null;
|
|
7789
|
+
const value = options.documentId;
|
|
7790
|
+
if (typeof value !== "string") return null;
|
|
7791
|
+
if (value.trim().length === 0 || value.length > MAX_DOCUMENT_ID_LEN) return null;
|
|
7792
|
+
return value;
|
|
7793
|
+
}
|
|
7794
|
+
/** Duck-checked too, so a signal from another realm is still a signal. */
|
|
7795
|
+
function isAbortSignal(value) {
|
|
7796
|
+
if (value instanceof AbortSignal) return true;
|
|
7797
|
+
const candidate = value;
|
|
7798
|
+
return typeof candidate.aborted === "boolean" && typeof candidate.addEventListener === "function";
|
|
7799
|
+
}
|
|
7800
|
+
var AttachmentClientProvider = class {
|
|
7801
|
+
service;
|
|
7802
|
+
access;
|
|
7803
|
+
logger;
|
|
7804
|
+
constructor(service, access, logger) {
|
|
7805
|
+
this.service = service;
|
|
7806
|
+
this.access = access;
|
|
7807
|
+
this.logger = logger;
|
|
7808
|
+
}
|
|
7809
|
+
forSubject(subject) {
|
|
7810
|
+
return createAttachmentClient(new AuthorizedAttachmentService(this.service, this.access, subject, this.logger));
|
|
7811
|
+
}
|
|
7812
|
+
};
|
|
7813
|
+
//#endregion
|
|
6745
7814
|
//#region src/graphql/gateway/require-auth-middleware.ts
|
|
6746
7815
|
/**
|
|
6747
7816
|
* Returns a fetch middleware that rejects anonymous callers with a 401.
|
|
@@ -6767,14 +7836,42 @@ function normalizeAttachmentRef(value) {
|
|
|
6767
7836
|
* context has no caller, and is rejected. The server never composes this
|
|
6768
7837
|
* middleware without the auth middleware in the chain (see
|
|
6769
7838
|
* `assertRequireAuthenticatedCallerAllowed` in `server.ts`).
|
|
7839
|
+
*
|
|
7840
|
+
* `exemptPaths` names the mounted paths that stay reachable anonymously — a
|
|
7841
|
+
* deployment whose product has a flow that runs before sign-in, such as
|
|
7842
|
+
* previewing an invitation from its code, needs somewhere to serve it from.
|
|
7843
|
+
* Each entry is a hole in the floor, so the match is deliberately the
|
|
7844
|
+
* narrowest one that works: the request's pathname, compared in full. A
|
|
7845
|
+
* prefix rule would exempt `/graphql/public-admin` along with
|
|
7846
|
+
* `/graphql/public`, and an exempt surface nobody can enumerate by reading
|
|
7847
|
+
* the configuration is not one anybody audits.
|
|
6770
7848
|
*/
|
|
6771
|
-
function createRequireAuthFetchMiddleware() {
|
|
7849
|
+
function createRequireAuthFetchMiddleware(exemptPaths = []) {
|
|
7850
|
+
const exempt = new Set(exemptPaths.map(normalizeExemptPath));
|
|
6772
7851
|
return (next) => async (request) => {
|
|
6773
7852
|
if (request.method === "OPTIONS") return next(request);
|
|
7853
|
+
if (exempt.size > 0 && exempt.has(normalizeExemptPath(new URL(request.url).pathname))) return next(request);
|
|
6774
7854
|
if (getAuthContext(request)?.user === void 0) return new globalThis.Response(JSON.stringify({ error: "Authentication required" }), { status: 401 });
|
|
6775
7855
|
return next(request);
|
|
6776
7856
|
};
|
|
6777
7857
|
}
|
|
7858
|
+
/**
|
|
7859
|
+
* The form both sides of the comparison are reduced to: surrounding whitespace
|
|
7860
|
+
* gone, and one trailing slash gone, so `/graphql/public/` and
|
|
7861
|
+
* `/graphql/public` are the same path. A client that appends a slash is not
|
|
7862
|
+
* asking for a different route, and a configuration that carries one is not
|
|
7863
|
+
* asking for a different rule.
|
|
7864
|
+
*
|
|
7865
|
+
* Deliberately NOT normalized: case, percent-encoding, and a missing leading
|
|
7866
|
+
* slash. The first two would let two spellings of one path diverge from how
|
|
7867
|
+
* the router itself matches; the third is a configuration error, and
|
|
7868
|
+
* `assertRequireAuthenticatedCallerAllowed` refuses to boot on it rather than
|
|
7869
|
+
* leaving an exemption that silently never applies.
|
|
7870
|
+
*/
|
|
7871
|
+
function normalizeExemptPath(path) {
|
|
7872
|
+
const trimmed = path.trim();
|
|
7873
|
+
return trimmed.length > 1 && trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
7874
|
+
}
|
|
6778
7875
|
//#endregion
|
|
6779
7876
|
//#region src/migrations/001_create_document_permissions.ts
|
|
6780
7877
|
var _001_create_document_permissions_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -7177,6 +8274,7 @@ async function createRenownCredentialVerifier(config = {}) {
|
|
|
7177
8274
|
* - Users can be granted permission to execute specific operations
|
|
7178
8275
|
*/
|
|
7179
8276
|
var DocumentPermissionService = class {
|
|
8277
|
+
db;
|
|
7180
8278
|
config;
|
|
7181
8279
|
constructor(db, config = { defaultProtection: false }) {
|
|
7182
8280
|
this.db = db;
|
|
@@ -7252,8 +8350,38 @@ var DocumentPermissionService = class {
|
|
|
7252
8350
|
* Delete all permissions for a document (used when deleting a document)
|
|
7253
8351
|
*/
|
|
7254
8352
|
async deleteAllDocumentPermissions(documentId) {
|
|
7255
|
-
await this.db.deleteFrom("DocumentPermission").where("documentId", "=", documentId).execute();
|
|
7256
|
-
await this.db.deleteFrom("OperationUserPermission").where("documentId", "=", documentId).execute();
|
|
8353
|
+
const permissions = await this.db.deleteFrom("DocumentPermission").where("documentId", "=", documentId).returning("id").execute();
|
|
8354
|
+
const operations = await this.db.deleteFrom("OperationUserPermission").where("documentId", "=", documentId).returning("id").execute();
|
|
8355
|
+
return {
|
|
8356
|
+
DocumentPermission: permissions.length,
|
|
8357
|
+
OperationUserPermission: operations.length
|
|
8358
|
+
};
|
|
8359
|
+
}
|
|
8360
|
+
/** Delete a document's protection row, which names its owner. */
|
|
8361
|
+
async deleteDocumentProtection(documentId) {
|
|
8362
|
+
return (await this.db.deleteFrom("DocumentProtection").where("documentId", "=", documentId).returning("documentId").execute()).length;
|
|
8363
|
+
}
|
|
8364
|
+
/** Every row that names the address, for a subject access request. */
|
|
8365
|
+
async rowsForAddress(address) {
|
|
8366
|
+
const lower = address.toLowerCase();
|
|
8367
|
+
return {
|
|
8368
|
+
permissions: await this.db.selectFrom("DocumentPermission").select([
|
|
8369
|
+
"documentId",
|
|
8370
|
+
"userAddress",
|
|
8371
|
+
"permission",
|
|
8372
|
+
"grantedBy",
|
|
8373
|
+
"createdAt",
|
|
8374
|
+
"updatedAt"
|
|
8375
|
+
]).where((eb) => eb.or([eb("userAddress", "=", lower), eb("grantedBy", "=", lower)])).orderBy("documentId").execute(),
|
|
8376
|
+
operationPermissions: await this.db.selectFrom("OperationUserPermission").select([
|
|
8377
|
+
"documentId",
|
|
8378
|
+
"operationType",
|
|
8379
|
+
"userAddress",
|
|
8380
|
+
"grantedBy",
|
|
8381
|
+
"createdAt"
|
|
8382
|
+
]).where((eb) => eb.or([eb("userAddress", "=", lower), eb("grantedBy", "=", lower)])).orderBy("documentId").execute(),
|
|
8383
|
+
ownedDocumentIds: (await this.db.selectFrom("DocumentProtection").select("documentId").where("ownerAddress", "=", lower).orderBy("documentId").execute()).map((row) => row.documentId)
|
|
8384
|
+
};
|
|
7257
8385
|
}
|
|
7258
8386
|
/**
|
|
7259
8387
|
* Grant a user permission to execute an operation on a document
|
|
@@ -7447,12 +8575,16 @@ function createGetParentIdsFn(reactorClient) {
|
|
|
7447
8575
|
* Authorization gate for the /mcp endpoint (AUTH_REVIEW S-C1). MCP tools have
|
|
7448
8576
|
* unrestricted reactor access, so access is limited to supreme admins when
|
|
7449
8577
|
* auth is enabled. OPEN policy is public; any other policy without an
|
|
7450
|
-
* AuthService fails closed.
|
|
8578
|
+
* AuthService fails closed. The tools read as the caller: anonymous under OPEN,
|
|
8579
|
+
* else the admin's address and the app key its token carries.
|
|
7451
8580
|
*/
|
|
7452
8581
|
function createMcpRequestAuthorizer(authService, authorizationService) {
|
|
7453
8582
|
return async (req) => {
|
|
7454
8583
|
if (!authService) {
|
|
7455
|
-
if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return {
|
|
8584
|
+
if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return {
|
|
8585
|
+
authorized: true,
|
|
8586
|
+
subject: {}
|
|
8587
|
+
};
|
|
7456
8588
|
return {
|
|
7457
8589
|
authorized: false,
|
|
7458
8590
|
status: 401,
|
|
@@ -7475,10 +8607,39 @@ function createMcpRequestAuthorizer(authService, authorizationService) {
|
|
|
7475
8607
|
status: 403,
|
|
7476
8608
|
message: "Forbidden: MCP access requires an administrator"
|
|
7477
8609
|
};
|
|
7478
|
-
return {
|
|
8610
|
+
return {
|
|
8611
|
+
authorized: true,
|
|
8612
|
+
subject: {
|
|
8613
|
+
address: context.user.address,
|
|
8614
|
+
key: context.user.appKey
|
|
8615
|
+
}
|
|
8616
|
+
};
|
|
7479
8617
|
};
|
|
7480
8618
|
}
|
|
7481
8619
|
//#endregion
|
|
8620
|
+
//#region src/services/sync-serving-gate.ts
|
|
8621
|
+
/**
|
|
8622
|
+
* The gate sync serving evaluates a document's own policy through, deciding
|
|
8623
|
+
* what the reactor client's read gate decides at the same flag level.
|
|
8624
|
+
*
|
|
8625
|
+
* Under `authEnforcement` it is built here rather than taken off the client
|
|
8626
|
+
* because it also carries the host's closes-by-default setting, which withholds
|
|
8627
|
+
* the domain scopes of a document nobody has policied yet. That answer belongs
|
|
8628
|
+
* to serving alone -- replay must keep reading an uninitialized document in
|
|
8629
|
+
* full -- so the two gates are separate objects over the same model.
|
|
8630
|
+
*
|
|
8631
|
+
* Below it the registered model ignores the auth scope, so the client reads
|
|
8632
|
+
* through the policy alone, and so does serving: a policy written below the
|
|
8633
|
+
* flag still withholds, where serving ungated would sync to a peer what `get`
|
|
8634
|
+
* refuses it.
|
|
8635
|
+
*/
|
|
8636
|
+
function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
|
|
8637
|
+
if (!reactorModule) return;
|
|
8638
|
+
const model = readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry);
|
|
8639
|
+
if (!model) return new SyncScopeGate(new BareReadGate(), reactorModule.documentView, logger);
|
|
8640
|
+
return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
|
|
8641
|
+
}
|
|
8642
|
+
//#endregion
|
|
7482
8643
|
//#region src/services/renown-config.ts
|
|
7483
8644
|
/** Reading credentials from this reactor's own read model needs a host-supplied
|
|
7484
8645
|
* verifier: core has no idea which subgraph serves that read model. */
|
|
@@ -7660,10 +8821,36 @@ function assertSkipCredentialVerificationAllowed(resolvesCallerIdentity, skipCre
|
|
|
7660
8821
|
* require-authenticated-caller middleware would reject every caller —
|
|
7661
8822
|
* authenticated ones included. Refuse to boot rather than run broken.
|
|
7662
8823
|
*/
|
|
7663
|
-
function assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolvesCallerIdentity) {
|
|
8824
|
+
function assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolvesCallerIdentity, exemptPaths = []) {
|
|
8825
|
+
/**
|
|
8826
|
+
* An exemption is only ever a hole in this floor, so configuring one while
|
|
8827
|
+
* the floor is off is not a harmless no-op: it reads, to anyone auditing the
|
|
8828
|
+
* configuration, as a surface that was deliberately opened — and therefore
|
|
8829
|
+
* as a floor that exists. Refuse rather than let the two drift.
|
|
8830
|
+
*/
|
|
8831
|
+
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.");
|
|
8832
|
+
/**
|
|
8833
|
+
* A path that cannot match anything is worse than no path: the operator
|
|
8834
|
+
* believes the flow is reachable, and finds out when the first user cannot
|
|
8835
|
+
* claim an invitation. `URL.pathname` is always absolute, so an entry that
|
|
8836
|
+
* does not start with `/` never matches, whatever the router does.
|
|
8837
|
+
*/
|
|
8838
|
+
const relative = exemptPaths.filter((path) => !path.trim().startsWith("/"));
|
|
8839
|
+
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
8840
|
if (!requireAuthenticatedCaller || resolvesCallerIdentity) return;
|
|
7665
8841
|
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
8842
|
}
|
|
8843
|
+
/**
|
|
8844
|
+
* Deciding an attachment read by the document's policy needs a policy model to
|
|
8845
|
+
* decide with, and that is what auth enforcement supplies. Without one there is
|
|
8846
|
+
* nothing to consult, so the setting would silently leave the permission tables
|
|
8847
|
+
* in charge — configuration that describes a protection the server is not
|
|
8848
|
+
* applying, which is worse than no configuration at all. Refuse instead.
|
|
8849
|
+
*/
|
|
8850
|
+
function assertAttachmentPolicyReadsAllowed(attachmentReadsFollowDocumentPolicy, hasDecisionModel) {
|
|
8851
|
+
if (!attachmentReadsFollowDocumentPolicy || hasDecisionModel) return;
|
|
8852
|
+
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).");
|
|
8853
|
+
}
|
|
7667
8854
|
function createReadinessGate() {
|
|
7668
8855
|
let ready = false;
|
|
7669
8856
|
return {
|
|
@@ -7718,27 +8905,6 @@ function makeDbClosers(knexInstance, pglite) {
|
|
|
7718
8905
|
return closers;
|
|
7719
8906
|
}
|
|
7720
8907
|
/**
|
|
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
8908
|
* Resolves the gateway adapter type from the `GATEWAY_ADAPTER` env var.
|
|
7743
8909
|
* Defaults to "apollo" (the federation gateway, production behavior).
|
|
7744
8910
|
* "stitching" selects the in-process graphql-tools merge gateway (#1565
|
|
@@ -7754,8 +8920,28 @@ function resolveGatewayAdapterType(logger) {
|
|
|
7754
8920
|
/**
|
|
7755
8921
|
* Sets up the subgraph manager and registers subgraphs
|
|
7756
8922
|
*/
|
|
7757
|
-
async function setupGraphQLManager(httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes) {
|
|
7758
|
-
const graphqlManager = new GraphQLManager(
|
|
8923
|
+
async function setupGraphQLManager({ httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes, attachments }) {
|
|
8924
|
+
const graphqlManager = new GraphQLManager({
|
|
8925
|
+
path: config.basePath,
|
|
8926
|
+
httpServer,
|
|
8927
|
+
wsServer,
|
|
8928
|
+
reactorClient: client,
|
|
8929
|
+
relationalDb,
|
|
8930
|
+
analyticsStore,
|
|
8931
|
+
syncManager,
|
|
8932
|
+
logger,
|
|
8933
|
+
httpAdapter,
|
|
8934
|
+
gatewayAdapter: await createGatewayAdapter(resolveGatewayAdapterType(logger), logger),
|
|
8935
|
+
authService,
|
|
8936
|
+
documentPermissionService,
|
|
8937
|
+
featureFlags: { enableDocumentModelSubgraphs },
|
|
8938
|
+
port,
|
|
8939
|
+
authorizationService,
|
|
8940
|
+
reactorDriveClient,
|
|
8941
|
+
syncServingGate,
|
|
8942
|
+
httpRoutes,
|
|
8943
|
+
attachments
|
|
8944
|
+
});
|
|
7759
8945
|
await graphqlManager.init(subgraphs.core, authFetchMiddleware, requireAuthFetchMiddleware);
|
|
7760
8946
|
for (const [packageName, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql", false, packageName);
|
|
7761
8947
|
await graphqlManager.updateRouter();
|
|
@@ -7853,6 +9039,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7853
9039
|
let authEnabled = false;
|
|
7854
9040
|
let configuredResolveIdentity;
|
|
7855
9041
|
let configuredRequireAuth;
|
|
9042
|
+
let configuredExemptPaths;
|
|
9043
|
+
let configuredAttachmentPolicyReads;
|
|
7856
9044
|
let configuredRenown;
|
|
7857
9045
|
if (options.configFile) {
|
|
7858
9046
|
const config = getConfig(options.configFile);
|
|
@@ -7864,8 +9052,10 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7864
9052
|
authEnabled = options.auth.enabled;
|
|
7865
9053
|
configuredResolveIdentity = options.auth.resolveIdentity;
|
|
7866
9054
|
configuredRequireAuth = options.auth.requireAuthenticatedCaller;
|
|
9055
|
+
configuredExemptPaths = options.auth.requireAuthenticatedCallerExemptPaths;
|
|
9056
|
+
configuredAttachmentPolicyReads = options.auth.attachmentReadsFollowDocumentPolicy;
|
|
7867
9057
|
}
|
|
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;
|
|
9058
|
+
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
9059
|
if (AUTH_ENABLED !== void 0) authEnabled = AUTH_ENABLED === "true";
|
|
7870
9060
|
/**
|
|
7871
9061
|
* Whether the auth middleware reads the bearer and populates `ctx.user`,
|
|
@@ -7896,6 +9086,30 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7896
9086
|
*/
|
|
7897
9087
|
let requireAuthenticatedCaller = configuredRequireAuth ?? false;
|
|
7898
9088
|
if (REQUIRE_AUTHENTICATED_CALLER !== void 0) requireAuthenticatedCaller = REQUIRE_AUTHENTICATED_CALLER === "true";
|
|
9089
|
+
/**
|
|
9090
|
+
* The paths the floor above does not apply to. Comma-separated, like
|
|
9091
|
+
* `ADMINS`, because a deployment configures this the same way it configures
|
|
9092
|
+
* everything else: one variable, one line, however many values it has.
|
|
9093
|
+
*
|
|
9094
|
+
* Empty entries are dropped rather than refused, so a trailing comma or a
|
|
9095
|
+
* value split across lines in a manifest is not a boot failure; a value that
|
|
9096
|
+
* cannot ever match is refused, in
|
|
9097
|
+
* {@link assertRequireAuthenticatedCallerAllowed}.
|
|
9098
|
+
*/
|
|
9099
|
+
let requireAuthenticatedCallerExemptPaths = configuredExemptPaths ?? [];
|
|
9100
|
+
if (REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS !== void 0) requireAuthenticatedCallerExemptPaths = REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS.split(",").map((path) => path.trim()).filter((path) => path.length > 0);
|
|
9101
|
+
/**
|
|
9102
|
+
* Whether an attachment read is decided by the referencing document's own
|
|
9103
|
+
* policy rather than by the host's permission tables.
|
|
9104
|
+
*
|
|
9105
|
+
* Off by default, and a deployment's choice rather than something derived
|
|
9106
|
+
* from the flags around it. Which model governs those bytes is configuration
|
|
9107
|
+
* in the same sense the storage backend behind them is: a host that has said
|
|
9108
|
+
* nothing keeps exactly the behaviour it has, and one that wants the change
|
|
9109
|
+
* asks for it and can take it back without disturbing anything else.
|
|
9110
|
+
*/
|
|
9111
|
+
let attachmentReadsFollowDocumentPolicy = configuredAttachmentPolicyReads ?? false;
|
|
9112
|
+
if (ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY !== void 0) attachmentReadsFollowDocumentPolicy = ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY === "true";
|
|
7899
9113
|
if (ADMINS !== void 0) admins = ADMINS.split(",").map((a) => a.toLowerCase());
|
|
7900
9114
|
let defaultProtection = false;
|
|
7901
9115
|
if (DEFAULT_PROTECTION !== void 0) defaultProtection = DEFAULT_PROTECTION.toLowerCase() === "true";
|
|
@@ -7910,7 +9124,7 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7910
9124
|
const documentPermissionsRequested = options.documentPermissionService !== void 0 || DOCUMENT_PERMISSIONS_ENABLED === "true";
|
|
7911
9125
|
assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissionsRequested);
|
|
7912
9126
|
assertSkipCredentialVerificationAllowed(resolveCallerIdentity, skipCredentialVerification, process.env);
|
|
7913
|
-
assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolveCallerIdentity);
|
|
9127
|
+
assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolveCallerIdentity, requireAuthenticatedCallerExemptPaths);
|
|
7914
9128
|
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
9129
|
httpAdapter.getRoute("/health", () => new Response("OK", { status: 200 }));
|
|
7916
9130
|
const readiness = createReadinessGate();
|
|
@@ -7946,8 +9160,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7946
9160
|
});
|
|
7947
9161
|
authFetchMiddleware = createAuthFetchMiddleware(authService);
|
|
7948
9162
|
if (requireAuthenticatedCaller) {
|
|
7949
|
-
requireAuthFetchMiddleware = createRequireAuthFetchMiddleware();
|
|
7950
|
-
logger.info("Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph");
|
|
9163
|
+
requireAuthFetchMiddleware = createRequireAuthFetchMiddleware(requireAuthenticatedCallerExemptPaths);
|
|
9164
|
+
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
9165
|
} 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
9166
|
}
|
|
7953
9167
|
const dbClosers = [];
|
|
@@ -7972,7 +9186,7 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7972
9186
|
await mkdir(attachmentStoragePath, { recursive: true });
|
|
7973
9187
|
const { db: attachmentDb, knex: attachmentKnex, pglite: attachmentPglite } = getDbClient(options.dbPath, options.pgliteFactory);
|
|
7974
9188
|
dbClosers.push(...makeDbClosers(attachmentKnex, attachmentPglite));
|
|
7975
|
-
const ATTACHMENT_SWEEP_INTERVAL_MS =
|
|
9189
|
+
const ATTACHMENT_SWEEP_INTERVAL_MS = 36e5;
|
|
7976
9190
|
const attachmentBackend = await createStartupAttachmentBackend({ db: attachmentDb });
|
|
7977
9191
|
const attachmentBuilder = new AttachmentBuilder(attachmentDb, attachmentStoragePath).withReservationSweepMs(ATTACHMENT_SWEEP_INTERVAL_MS);
|
|
7978
9192
|
if (attachmentBackend) attachmentBuilder.withBackend(attachmentBackend);
|
|
@@ -8008,6 +9222,7 @@ async function _setupCommonInfrastructure(options) {
|
|
|
8008
9222
|
httpRoutes,
|
|
8009
9223
|
authFetchMiddleware,
|
|
8010
9224
|
requireAuthFetchMiddleware,
|
|
9225
|
+
authEnabled,
|
|
8011
9226
|
authService,
|
|
8012
9227
|
relationalDb,
|
|
8013
9228
|
analyticsStore,
|
|
@@ -8017,13 +9232,14 @@ async function _setupCommonInfrastructure(options) {
|
|
|
8017
9232
|
attachmentReferenceIndex,
|
|
8018
9233
|
packages,
|
|
8019
9234
|
dbClosers,
|
|
8020
|
-
readiness
|
|
9235
|
+
readiness,
|
|
9236
|
+
attachmentReadsFollowDocumentPolicy
|
|
8021
9237
|
};
|
|
8022
9238
|
}
|
|
8023
9239
|
/**
|
|
8024
9240
|
* Private helper function containing common setup logic for API initialization
|
|
8025
9241
|
*/
|
|
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) {
|
|
9242
|
+
async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authEnabled, authService, port, packages, relationalDb, analyticsStore, documentPermissionService, processors, subgraphs, options, processorApp, readModels, attachments, attachmentReferenceIndex, attachmentReferenceProjection, authorizationConfig, documentModelRegistry, dbClosers = [], reactorDriveClient, syncServingGate, httpRoutes, attachmentReadsFollowDocumentPolicy = false) {
|
|
8027
9243
|
const hostModuleBase = {
|
|
8028
9244
|
...createReactorHostModuleBase({
|
|
8029
9245
|
client: reactorClient,
|
|
@@ -8048,7 +9264,7 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
8048
9264
|
try {
|
|
8049
9265
|
return fn(moduleFor(packageName));
|
|
8050
9266
|
} catch (e) {
|
|
8051
|
-
logger.error(
|
|
9267
|
+
logger.error("Error initializing processor factory for package @package: @error", packageName, e);
|
|
8052
9268
|
return null;
|
|
8053
9269
|
}
|
|
8054
9270
|
}))).filter((factory) => factory.status === "fulfilled" && factory.value !== null && typeof factory.value === "function");
|
|
@@ -8065,17 +9281,44 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
8065
9281
|
const { httpServer, wsServer } = await startServer(httpAdapter, port, options.https, logger);
|
|
8066
9282
|
const authorizationService = createAuthorizationService(authorizationConfig, documentPermissionService, createGetParentIdsFn(reactorClient));
|
|
8067
9283
|
logger.info(`Authorization service initialized (policy: ${authorizationConfig.policy})`);
|
|
8068
|
-
|
|
9284
|
+
assertAttachmentPolicyReadsAllowed(attachmentReadsFollowDocumentPolicy, syncServingGate !== void 0);
|
|
9285
|
+
const floor = requireAuthFetchMiddleware !== void 0;
|
|
9286
|
+
const attachmentAccess = new AttachmentAccessService(createCanonicalDocumentIdResolver(reactorClient), authorizationService, attachmentReferenceIndex.store, attachmentReferenceProjection, reactorClient, attachmentReadsFollowDocumentPolicy ? syncServingGate : void 0, {
|
|
9287
|
+
refuseAnonymousWrites: authEnabled || floor,
|
|
9288
|
+
refuseAnonymousReads: floor
|
|
9289
|
+
});
|
|
9290
|
+
const attachmentClientProvider = new AttachmentClientProvider(attachments.service, attachmentAccess, logger);
|
|
8069
9291
|
const coreSubgraphs = DefaultCoreSubgraphs.slice();
|
|
8070
9292
|
coreSubgraphs.push(ReactorSubgraph);
|
|
8071
9293
|
if (documentPermissionService) {
|
|
8072
9294
|
coreSubgraphs.push(AuthSubgraph);
|
|
8073
9295
|
logger.info("Auth subgraph registered (document permissions enabled)");
|
|
8074
9296
|
}
|
|
8075
|
-
const graphqlManager = await setupGraphQLManager(
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
9297
|
+
const graphqlManager = await setupGraphQLManager({
|
|
9298
|
+
httpAdapter,
|
|
9299
|
+
authFetchMiddleware,
|
|
9300
|
+
requireAuthFetchMiddleware,
|
|
9301
|
+
httpServer,
|
|
9302
|
+
wsServer,
|
|
9303
|
+
client: reactorClient,
|
|
9304
|
+
relationalDb,
|
|
9305
|
+
analyticsStore,
|
|
9306
|
+
syncManager,
|
|
9307
|
+
subgraphs: {
|
|
9308
|
+
extended: subgraphs,
|
|
9309
|
+
core: coreSubgraphs
|
|
9310
|
+
},
|
|
9311
|
+
logger: logger.child(["graphql-manager"]),
|
|
9312
|
+
authorizationService,
|
|
9313
|
+
authService,
|
|
9314
|
+
documentPermissionService,
|
|
9315
|
+
enableDocumentModelSubgraphs: options.enableDocumentModelSubgraphs,
|
|
9316
|
+
port,
|
|
9317
|
+
reactorDriveClient,
|
|
9318
|
+
syncServingGate,
|
|
9319
|
+
httpRoutes,
|
|
9320
|
+
attachments: attachmentClientProvider
|
|
9321
|
+
});
|
|
8079
9322
|
setupEventListeners(packages, graphqlManager, reactorProcessorManager, moduleFor, documentModelRegistry);
|
|
8080
9323
|
if (mcpServerEnabled) {
|
|
8081
9324
|
await setupMcpServer({
|
|
@@ -8101,7 +9344,11 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
8101
9344
|
attachments,
|
|
8102
9345
|
attachmentReferenceIndex,
|
|
8103
9346
|
attachmentAccess,
|
|
9347
|
+
attachmentClientProvider,
|
|
8104
9348
|
authService,
|
|
9349
|
+
requireAuthenticatedCaller: floor,
|
|
9350
|
+
authorizationService,
|
|
9351
|
+
relationalDb,
|
|
8105
9352
|
dispose
|
|
8106
9353
|
};
|
|
8107
9354
|
}
|
|
@@ -8148,7 +9395,7 @@ function buildApiDispose(args) {
|
|
|
8148
9395
|
};
|
|
8149
9396
|
}
|
|
8150
9397
|
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);
|
|
9398
|
+
const { port, httpAdapter, httpRoutes, authFetchMiddleware, requireAuthFetchMiddleware, authEnabled, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness, attachmentReadsFollowDocumentPolicy } = await _setupCommonInfrastructure(options);
|
|
8152
9399
|
const { documentModels, upgradeManifests, processors, subgraphs } = await packages.init();
|
|
8153
9400
|
const { module: reactorClientModule, reactorDriveClient, attachmentReferenceProjection = {
|
|
8154
9401
|
status: "unavailable",
|
|
@@ -8165,13 +9412,14 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
8165
9412
|
const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
|
|
8166
9413
|
if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from InProcessReactorClientModule");
|
|
8167
9414
|
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),
|
|
9415
|
+
...await _setupAPI(reactorClient, syncManager, reactorProcessorManager, httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, authEnabled, 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
9416
|
client: reactorClient,
|
|
8170
9417
|
syncManager,
|
|
8171
9418
|
documentModelRegistry,
|
|
8172
9419
|
readiness,
|
|
8173
9420
|
attachmentReferenceProjection,
|
|
8174
|
-
packageManager: packages
|
|
9421
|
+
packageManager: packages,
|
|
9422
|
+
documentPermissionService
|
|
8175
9423
|
};
|
|
8176
9424
|
}
|
|
8177
9425
|
/**
|
|
@@ -8195,6 +9443,55 @@ function withScheme(origin) {
|
|
|
8195
9443
|
return /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
8196
9444
|
}
|
|
8197
9445
|
//#endregion
|
|
9446
|
+
//#region src/services/privacy-permissions.ts
|
|
9447
|
+
/**
|
|
9448
|
+
* reactor-privacy's permission lookup and eraser, over this host's
|
|
9449
|
+
* DocumentPermissionService. Typed structurally on the privacy side.
|
|
9450
|
+
*/
|
|
9451
|
+
var PrivacyPermissionAdapter = class {
|
|
9452
|
+
service;
|
|
9453
|
+
constructor(service) {
|
|
9454
|
+
this.service = service;
|
|
9455
|
+
}
|
|
9456
|
+
async rowsForAddress(address) {
|
|
9457
|
+
const lower = address.toLowerCase();
|
|
9458
|
+
const found = await this.service.rowsForAddress(address);
|
|
9459
|
+
const rows = [];
|
|
9460
|
+
for (const row of found.permissions) for (const column of ["userAddress", "grantedBy"]) {
|
|
9461
|
+
if (row[column] !== lower) continue;
|
|
9462
|
+
rows.push({
|
|
9463
|
+
table: "DocumentPermission",
|
|
9464
|
+
column,
|
|
9465
|
+
documentId: row.documentId,
|
|
9466
|
+
detail: { permission: row.permission }
|
|
9467
|
+
});
|
|
9468
|
+
}
|
|
9469
|
+
for (const row of found.operationPermissions) for (const column of ["userAddress", "grantedBy"]) {
|
|
9470
|
+
if (row[column] !== lower) continue;
|
|
9471
|
+
rows.push({
|
|
9472
|
+
table: "OperationUserPermission",
|
|
9473
|
+
column,
|
|
9474
|
+
documentId: row.documentId,
|
|
9475
|
+
detail: { operationType: row.operationType }
|
|
9476
|
+
});
|
|
9477
|
+
}
|
|
9478
|
+
for (const documentId of found.ownedDocumentIds) rows.push({
|
|
9479
|
+
table: "DocumentProtection",
|
|
9480
|
+
column: "ownerAddress",
|
|
9481
|
+
documentId
|
|
9482
|
+
});
|
|
9483
|
+
return rows;
|
|
9484
|
+
}
|
|
9485
|
+
async erasePermissions(documentId) {
|
|
9486
|
+
const permissions = await this.service.deleteAllDocumentPermissions(documentId);
|
|
9487
|
+
const protection = await this.service.deleteDocumentProtection(documentId);
|
|
9488
|
+
return {
|
|
9489
|
+
...permissions,
|
|
9490
|
+
DocumentProtection: protection
|
|
9491
|
+
};
|
|
9492
|
+
}
|
|
9493
|
+
};
|
|
9494
|
+
//#endregion
|
|
8198
9495
|
//#region src/services/package-storage.ts
|
|
8199
9496
|
var InMemoryPackageStorage = class {
|
|
8200
9497
|
packages = /* @__PURE__ */ new Map();
|
|
@@ -8298,7 +9595,7 @@ var PackageManagementService = class {
|
|
|
8298
9595
|
}
|
|
8299
9596
|
};
|
|
8300
9597
|
//#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 };
|
|
9598
|
+
export { ADMIN_USERS, ActionCandidateInputSchema, ActionContextInputSchema, ActionInputSchema, AddRelationshipDocument, AnalyticsSubgraph, AttachmentAccessFailed, AttachmentAccessService, AttachmentAccessUnavailable, AttachmentClientProvider, AuthDecision, AuthDecisionSchema, AuthEvaluationUnsupportedError, AuthService, AuthSubgraph, AuthenticationRequiredError, AuthorizationPolicy, AuthorizedAttachmentService, 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, PrivacyPermissionAdapter, PropagationMode, PropagationModeSchema, PushSyncEnvelopesDocument, ReactorSignerAppInputSchema, ReactorSignerInputSchema, ReactorSignerUserInputSchema, ReactorSubgraph, RefusalNotRecordedError, 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
9599
|
|
|
8303
9600
|
//# sourceMappingURL=index.mjs.map
|
|
8304
|
-
//# debugId=
|
|
9601
|
+
//# debugId=a50faed2-4e40-54ba-873f-1808cedc1e5c
|