@powerhousedao/reactor-api 6.2.3-dev.4 → 6.2.3-dev.41
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 +1315 -602
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2309 -517
- 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 +19 -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,27 @@
|
|
|
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]="65e47e18-15f4-5a67-b94b-f6e795c1eb9b")}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";
|
|
5
|
+
import { findPackageJSON, isBuiltin } from "node:module";
|
|
4
6
|
import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
|
|
5
7
|
import { AnalyticsModel, AnalyticsResolvers, typedefs } from "@powerhousedao/analytics-engine-graphql";
|
|
6
8
|
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";
|
|
9
|
+
import { GraphQLError, Kind, Lexer, Source, TokenKind, parse, print, visit } from "graphql";
|
|
10
|
+
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";
|
|
11
|
+
import { deserializeSignature, readPeerManifest, serializeSignature, setName, withSignaturePolicy } from "@powerhousedao/shared/document-model";
|
|
9
12
|
import { ConsoleLogger, childLogger, documentModelDocumentModelModule } from "document-model";
|
|
10
|
-
import path, { join } from "node:path";
|
|
13
|
+
import path, { isAbsolute, join } from "node:path";
|
|
11
14
|
import { match } from "path-to-regexp";
|
|
12
15
|
import posix from "node:path/posix";
|
|
13
16
|
import { Readable } from "node:stream";
|
|
14
|
-
import fs, { readFileSync, watchFile } from "node:fs";
|
|
17
|
+
import fs, { existsSync, readFileSync, watchFile } from "node:fs";
|
|
15
18
|
import { pipeline } from "node:stream/promises";
|
|
16
|
-
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
19
|
+
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
20
|
+
import { pathToFileURL } from "node:url";
|
|
17
21
|
import { buildSubgraphSchema } from "@apollo/subgraph";
|
|
18
22
|
import { typeDefs } from "@powerhousedao/document-engineering/graphql";
|
|
19
23
|
import { camelCase, kebabCase, pascalCase } from "change-case";
|
|
20
24
|
import { GraphQLJSON, GraphQLJSONObject } from "graphql-type-json";
|
|
21
|
-
import { deserializeSignature, serializeSignature, setName } from "@powerhousedao/shared/document-model";
|
|
22
25
|
import * as z$1 from "zod";
|
|
23
26
|
import { z } from "zod";
|
|
24
27
|
import { createHandler } from "graphql-sse/lib/use/fetch";
|
|
@@ -26,16 +29,17 @@ import lzString from "lz-string";
|
|
|
26
29
|
import { PubSub, withFilter } from "graphql-subscriptions";
|
|
27
30
|
import dotenv from "dotenv";
|
|
28
31
|
import { buildTreeUrl } from "@powerhousedao/shared";
|
|
32
|
+
import { init, parse as parse$1 } from "es-module-lexer";
|
|
33
|
+
import { chmod, lstat, mkdir, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
29
34
|
import { getConfig } from "@powerhousedao/config/node";
|
|
30
35
|
import { reactorDriveDocumentModelModule } from "@powerhousedao/reactor-drive";
|
|
31
36
|
import { ReactorGroupV1 } from "@powerhousedao/reactor-group";
|
|
32
37
|
import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
|
|
33
38
|
import EventEmitter from "node:events";
|
|
34
39
|
import { PostgresAnalyticsStore } from "@powerhousedao/analytics-engine-pg";
|
|
35
|
-
import { AttachmentBuilder, AttachmentReferenceIndexBuilder, createRef, createS3AttachmentBackend, parseAttachmentStorageConfig, parseRef } from "@powerhousedao/reactor-attachments";
|
|
40
|
+
import { AttachmentBuilder, AttachmentNotFound, AttachmentReferenceIndexBuilder, createRef, createS3AttachmentBackend, parseAttachmentStorageConfig, parseRef, validateReserveMetadata } from "@powerhousedao/reactor-attachments";
|
|
36
41
|
import { createAttachmentClient } from "@powerhousedao/reactor-attachments/client";
|
|
37
42
|
import { setupMcpServer } from "@powerhousedao/reactor-mcp";
|
|
38
|
-
import { mkdir } from "node:fs/promises";
|
|
39
43
|
import { tmpdir } from "node:os";
|
|
40
44
|
import { WebSocketServer } from "ws";
|
|
41
45
|
import { createRelationalDb } from "@powerhousedao/shared/processors";
|
|
@@ -46,19 +50,8 @@ import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
|
|
|
46
50
|
import knex from "knex";
|
|
47
51
|
import ClientPgLite from "knex-pglite";
|
|
48
52
|
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
53
|
//#region src/graphql/analytics-subgraph.ts
|
|
54
|
+
const analyticsResolvers = AnalyticsResolvers;
|
|
62
55
|
var AnalyticsSubgraph = class extends BaseSubgraph {
|
|
63
56
|
analyticsStore;
|
|
64
57
|
analyticsModel;
|
|
@@ -66,17 +59,60 @@ var AnalyticsSubgraph = class extends BaseSubgraph {
|
|
|
66
59
|
typeDefs = gql`
|
|
67
60
|
${typedefs}
|
|
68
61
|
`;
|
|
69
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Every analytics field hangs off `Query.analytics`, so gating it gates them
|
|
64
|
+
* all. Series are aggregates a processor built from many documents and carry
|
|
65
|
+
* no document to decide on, so only a caller with policy-wide read (anyone
|
|
66
|
+
* under OPEN, an admin otherwise) may read them.
|
|
67
|
+
*/
|
|
68
|
+
resolvers = {
|
|
69
|
+
...analyticsResolvers,
|
|
70
|
+
Query: {
|
|
71
|
+
...analyticsResolvers.Query,
|
|
72
|
+
analytics: (parent, args, ctx) => {
|
|
73
|
+
this.#assertCanReadAnalytics(ctx);
|
|
74
|
+
return analyticsResolvers.Query.analytics(parent, args, ctx);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
70
78
|
constructor(args) {
|
|
71
79
|
super(args);
|
|
72
80
|
this.analyticsStore = args.analyticsStore;
|
|
73
81
|
this.analyticsModel = new AnalyticsModel(new AnalyticsQueryEngine(this.analyticsStore));
|
|
74
82
|
args.graphqlManager.setAdditionalContextFields({ dataSources: { db: { Analytics: this.analyticsModel } } });
|
|
75
83
|
}
|
|
84
|
+
#assertCanReadAnalytics(ctx) {
|
|
85
|
+
if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return;
|
|
86
|
+
if (ctx.user?.address) throw new ForbiddenError("to read analytics");
|
|
87
|
+
throw new AuthenticationRequiredError("to read analytics");
|
|
88
|
+
}
|
|
76
89
|
};
|
|
77
90
|
//#endregion
|
|
78
91
|
//#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
|
|
92
|
+
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";
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/graphql/argument-aliases.ts
|
|
95
|
+
/** A renamed argument given under both names, or a required one under neither. */
|
|
96
|
+
var ArgumentAliasError = class extends GraphQLError {
|
|
97
|
+
constructor(message) {
|
|
98
|
+
super(message, { extensions: { code: "BAD_USER_INPUT" } });
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/** The value of a required argument that may still arrive under its old name. */
|
|
102
|
+
function requireOneOf(args, name, deprecatedName) {
|
|
103
|
+
const current = args[name] ?? void 0;
|
|
104
|
+
const deprecated = args[deprecatedName] ?? void 0;
|
|
105
|
+
if (current !== void 0 && deprecated !== void 0) throw new ArgumentAliasError(`Pass ${name} or ${deprecatedName}, not both.`);
|
|
106
|
+
if (current === void 0 && deprecated === void 0) throw new ArgumentAliasError(`${name} is required.`);
|
|
107
|
+
return current ?? deprecated;
|
|
108
|
+
}
|
|
109
|
+
/** The value of an optional argument that may still arrive under its old name. */
|
|
110
|
+
function optionalOneOf(args, name, deprecatedName) {
|
|
111
|
+
const current = args[name] ?? void 0;
|
|
112
|
+
const deprecated = args[deprecatedName] ?? void 0;
|
|
113
|
+
if (current !== void 0 && deprecated !== void 0) throw new ArgumentAliasError(`Pass ${name} or ${deprecatedName}, not both.`);
|
|
114
|
+
return current ?? deprecated;
|
|
115
|
+
}
|
|
80
116
|
//#endregion
|
|
81
117
|
//#region src/graphql/auth/resolvers.ts
|
|
82
118
|
async function documentAccess(service, authorizationService, args, userAddress) {
|
|
@@ -172,7 +208,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
172
208
|
this.logger.debug("documentAccess(@args)", args);
|
|
173
209
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
174
210
|
try {
|
|
175
|
-
return await documentAccess(this.documentPermissionService, this.authorizationService, await this
|
|
211
|
+
return await documentAccess(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
176
212
|
} catch (error) {
|
|
177
213
|
this.logger.error("Error in documentAccess: @error", error);
|
|
178
214
|
throw error;
|
|
@@ -193,7 +229,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
193
229
|
this.logger.debug("operationPermissions(@args)", args);
|
|
194
230
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
195
231
|
try {
|
|
196
|
-
return await operationPermissions(this.documentPermissionService, this.authorizationService, await this
|
|
232
|
+
return await operationPermissions(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
197
233
|
} catch (error) {
|
|
198
234
|
this.logger.error("Error in operationPermissions: @error", error);
|
|
199
235
|
throw error;
|
|
@@ -202,7 +238,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
202
238
|
canExecuteOperation: async (_parent, args, ctx) => {
|
|
203
239
|
this.logger.debug("canExecuteOperation(@args)", args);
|
|
204
240
|
try {
|
|
205
|
-
return await canExecuteOperation(this.authorizationService, await this
|
|
241
|
+
return await canExecuteOperation(this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
206
242
|
} catch (error) {
|
|
207
243
|
this.logger.error("Error in canExecuteOperation: @error", error);
|
|
208
244
|
throw error;
|
|
@@ -212,7 +248,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
212
248
|
this.logger.debug("documentProtection(@args)", args);
|
|
213
249
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
214
250
|
try {
|
|
215
|
-
return await documentProtection(this.documentPermissionService, this.authorizationService, await this
|
|
251
|
+
return await documentProtection(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
216
252
|
} catch (error) {
|
|
217
253
|
this.logger.error("Error in documentProtection: @error", error);
|
|
218
254
|
throw error;
|
|
@@ -224,7 +260,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
224
260
|
this.logger.debug("setDocumentProtection(@args)", args);
|
|
225
261
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
226
262
|
try {
|
|
227
|
-
return await setDocumentProtection(this.documentPermissionService, this.authorizationService, await this
|
|
263
|
+
return await setDocumentProtection(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
228
264
|
} catch (error) {
|
|
229
265
|
this.logger.error("Error in setDocumentProtection: @error", error);
|
|
230
266
|
throw error;
|
|
@@ -234,7 +270,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
234
270
|
this.logger.debug("transferDocumentOwnership(@args)", args);
|
|
235
271
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
236
272
|
try {
|
|
237
|
-
return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, await this
|
|
273
|
+
return await transferDocumentOwnership(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
238
274
|
} catch (error) {
|
|
239
275
|
this.logger.error("Error in transferDocumentOwnership: @error", error);
|
|
240
276
|
throw error;
|
|
@@ -244,7 +280,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
244
280
|
this.logger.debug("grantDocumentPermission(@args)", args);
|
|
245
281
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
246
282
|
try {
|
|
247
|
-
const resolved = await this
|
|
283
|
+
const resolved = await this.#withCanonicalDocumentArgs(args, ctx);
|
|
248
284
|
return await grantDocumentPermission(this.documentPermissionService, this.authorizationService, {
|
|
249
285
|
...resolved,
|
|
250
286
|
permission: resolved.permission
|
|
@@ -258,7 +294,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
258
294
|
this.logger.debug("revokeDocumentPermission(@args)", args);
|
|
259
295
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
260
296
|
try {
|
|
261
|
-
return await revokeDocumentPermission(this.documentPermissionService, this.authorizationService, await this
|
|
297
|
+
return await revokeDocumentPermission(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
262
298
|
} catch (error) {
|
|
263
299
|
this.logger.error("Error in revokeDocumentPermission: @error", error);
|
|
264
300
|
throw error;
|
|
@@ -268,7 +304,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
268
304
|
this.logger.debug("grantOperationPermission(@args)", args);
|
|
269
305
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
270
306
|
try {
|
|
271
|
-
return await grantOperationPermission(this.documentPermissionService, this.authorizationService, await this
|
|
307
|
+
return await grantOperationPermission(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
272
308
|
} catch (error) {
|
|
273
309
|
this.logger.error("Error in grantOperationPermission: @error", error);
|
|
274
310
|
throw error;
|
|
@@ -278,7 +314,7 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
278
314
|
this.logger.debug("revokeOperationPermission(@args)", args);
|
|
279
315
|
if (!this.documentPermissionService) throw new GraphQLError("DocumentPermissionService not available");
|
|
280
316
|
try {
|
|
281
|
-
return await revokeOperationPermission(this.documentPermissionService, this.authorizationService, await this
|
|
317
|
+
return await revokeOperationPermission(this.documentPermissionService, this.authorizationService, await this.#withCanonicalDocumentArgs(args, ctx), ctx.user?.address);
|
|
282
318
|
} catch (error) {
|
|
283
319
|
this.logger.error("Error in revokeOperationPermission: @error", error);
|
|
284
320
|
throw error;
|
|
@@ -286,6 +322,16 @@ var AuthSubgraph = class extends BaseSubgraph {
|
|
|
286
322
|
}
|
|
287
323
|
}
|
|
288
324
|
};
|
|
325
|
+
#withCanonicalDocumentArgs(args, ctx) {
|
|
326
|
+
const { documentIdOrSlug, documentId, ...rest } = args;
|
|
327
|
+
return this.withCanonicalDocumentId({
|
|
328
|
+
...rest,
|
|
329
|
+
documentId: requireOneOf({
|
|
330
|
+
documentIdOrSlug,
|
|
331
|
+
documentId
|
|
332
|
+
}, "documentIdOrSlug", "documentId")
|
|
333
|
+
}, ctx);
|
|
334
|
+
}
|
|
289
335
|
onSetup() {
|
|
290
336
|
this.logger.debug("Setting up AuthSubgraph");
|
|
291
337
|
return Promise.resolve();
|
|
@@ -311,15 +357,15 @@ function createAuthFetchMiddleware(authService) {
|
|
|
311
357
|
async function createGatewayAdapter(type, logger) {
|
|
312
358
|
switch (type) {
|
|
313
359
|
case "apollo": {
|
|
314
|
-
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-
|
|
360
|
+
const { ApolloGatewayAdapter } = await import("./adapter-gateway-apollo-D2DNlPY0.mjs");
|
|
315
361
|
return new ApolloGatewayAdapter(logger);
|
|
316
362
|
}
|
|
317
363
|
case "mercurius": {
|
|
318
|
-
const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-
|
|
364
|
+
const { MercuriusGatewayAdapter } = await import("./adapter-gateway-mercurius-rbKlwF9m.mjs");
|
|
319
365
|
return new MercuriusGatewayAdapter(logger);
|
|
320
366
|
}
|
|
321
367
|
case "stitching": {
|
|
322
|
-
const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-
|
|
368
|
+
const { StitchingGatewayAdapter } = await import("./adapter-gateway-stitching-Dzv10u8D.mjs");
|
|
323
369
|
return new StitchingGatewayAdapter(logger);
|
|
324
370
|
}
|
|
325
371
|
}
|
|
@@ -327,11 +373,11 @@ async function createGatewayAdapter(type, logger) {
|
|
|
327
373
|
async function createHttpAdapter(type) {
|
|
328
374
|
switch (type) {
|
|
329
375
|
case "express": {
|
|
330
|
-
const { createExpressHttpAdapter } = await import("./adapter-http-express-
|
|
376
|
+
const { createExpressHttpAdapter } = await import("./adapter-http-express-CWqziB1p.mjs");
|
|
331
377
|
return createExpressHttpAdapter();
|
|
332
378
|
}
|
|
333
379
|
case "fastify": {
|
|
334
|
-
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-
|
|
380
|
+
const { createFastifyHttpAdapter } = await import("./adapter-http-fastify-CYZQ6FFv.mjs");
|
|
335
381
|
return createFastifyHttpAdapter();
|
|
336
382
|
}
|
|
337
383
|
}
|
|
@@ -359,7 +405,7 @@ const WS_AUTH_CLOSE_REASONS = [WS_CLOSE_REASON_AUTHENTICATION_REQUIRED, WS_CLOSE
|
|
|
359
405
|
*/
|
|
360
406
|
const SEGMENT = /^[a-z0-9][-a-z0-9._]*$/i;
|
|
361
407
|
/** Segments that would read as path traversal rather than as a name. */
|
|
362
|
-
const TRAVERSAL = new Set([".", ".."]);
|
|
408
|
+
const TRAVERSAL = /* @__PURE__ */ new Set([".", ".."]);
|
|
363
409
|
var InvalidNamespaceError = class extends Error {};
|
|
364
410
|
/**
|
|
365
411
|
* Turns a package name into the path segments it occupies.
|
|
@@ -845,27 +891,29 @@ var HttpScope = class {
|
|
|
845
891
|
headers: fetchHeaders(req),
|
|
846
892
|
signal: controller.signal
|
|
847
893
|
};
|
|
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 = {
|
|
894
|
+
if (method !== "GET" && method !== "HEAD" && body !== "none") {
|
|
895
|
+
if (body === "stream") init = {
|
|
866
896
|
...init,
|
|
867
|
-
body:
|
|
897
|
+
body: Readable.toWeb(req),
|
|
898
|
+
duplex: "half"
|
|
868
899
|
};
|
|
900
|
+
else {
|
|
901
|
+
try {
|
|
902
|
+
raw = await readBody(req, spec.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES$1);
|
|
903
|
+
} catch (error) {
|
|
904
|
+
if (error instanceof BodyTooLargeError) {
|
|
905
|
+
res.setHeader("connection", "close");
|
|
906
|
+
res.once("finish", () => req.destroy());
|
|
907
|
+
writeError(res, 413, "Payload too large");
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
throw error;
|
|
911
|
+
}
|
|
912
|
+
if (raw.length) init = {
|
|
913
|
+
...init,
|
|
914
|
+
body: new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength)
|
|
915
|
+
};
|
|
916
|
+
}
|
|
869
917
|
}
|
|
870
918
|
await writeResponse(res, await spec.handler(new Request(url, init), {
|
|
871
919
|
...ctx,
|
|
@@ -1042,7 +1090,7 @@ function verifyWebhook(options) {
|
|
|
1042
1090
|
}
|
|
1043
1091
|
/** Headers that would put a credential in a log or run journal. The configured signature
|
|
1044
1092
|
* header joins them: it is an HMAC of the body under a live secret. */
|
|
1045
|
-
const REDACTED_HEADERS = new Set([
|
|
1093
|
+
const REDACTED_HEADERS = /* @__PURE__ */ new Set([
|
|
1046
1094
|
"authorization",
|
|
1047
1095
|
"proxy-authorization",
|
|
1048
1096
|
"cookie",
|
|
@@ -1315,6 +1363,7 @@ function newWebhookToken() {
|
|
|
1315
1363
|
const NAMESPACE = "reactor_webhooks";
|
|
1316
1364
|
/** Relational implementation, over the reactor's own database. */
|
|
1317
1365
|
var RelationalWebhookStore = class {
|
|
1366
|
+
relationalDb;
|
|
1318
1367
|
#db;
|
|
1319
1368
|
constructor(relationalDb) {
|
|
1320
1369
|
this.relationalDb = relationalDb;
|
|
@@ -1329,7 +1378,11 @@ var RelationalWebhookStore = class {
|
|
|
1329
1378
|
"owner_key"
|
|
1330
1379
|
]).unique().execute();
|
|
1331
1380
|
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();
|
|
1381
|
+
try {
|
|
1382
|
+
await db.schema.alterTable("webhook_deliveries").addColumn("expires_at", "text").execute();
|
|
1383
|
+
} catch {}
|
|
1332
1384
|
await db.schema.createIndex("webhook_deliveries_key").ifNotExists().on("webhook_deliveries").columns(["token", "dedupe_key"]).unique().execute();
|
|
1385
|
+
await db.schema.createIndex("webhook_deliveries_expires_at").ifNotExists().on("webhook_deliveries").columns(["expires_at"]).execute();
|
|
1333
1386
|
await db.schema.createIndex("webhook_deliveries_seen_at").ifNotExists().on("webhook_deliveries").columns(["seen_at"]).execute();
|
|
1334
1387
|
this.#db = db;
|
|
1335
1388
|
}
|
|
@@ -1370,13 +1423,16 @@ var RelationalWebhookStore = class {
|
|
|
1370
1423
|
}
|
|
1371
1424
|
async seen(token, key, ttlSeconds) {
|
|
1372
1425
|
const now = Date.now();
|
|
1426
|
+
const nowIso = new Date(now).toISOString();
|
|
1373
1427
|
const cutoff = (/* @__PURE__ */ new Date(now - ttlSeconds * 1e3)).toISOString();
|
|
1374
|
-
await this.#handle.deleteFrom("webhook_deliveries").where("seen_at", "<", cutoff).execute();
|
|
1428
|
+
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();
|
|
1429
|
+
await this.#handle.deleteFrom("webhook_deliveries").where("token", "=", token).where("dedupe_key", "=", key).where("seen_at", "<", cutoff).execute();
|
|
1375
1430
|
const claimId = randomBytes(16).toString("hex");
|
|
1376
1431
|
await this.#handle.insertInto("webhook_deliveries").values({
|
|
1377
1432
|
token,
|
|
1378
1433
|
dedupe_key: key,
|
|
1379
|
-
seen_at:
|
|
1434
|
+
seen_at: nowIso,
|
|
1435
|
+
expires_at: new Date(now + ttlSeconds * 1e3).toISOString(),
|
|
1380
1436
|
claim_id: claimId
|
|
1381
1437
|
}).onConflict((oc) => oc.columns(["token", "dedupe_key"]).doNothing()).execute();
|
|
1382
1438
|
return (await this.#handle.selectFrom("webhook_deliveries").select("claim_id").where("token", "=", token).where("dedupe_key", "=", key).executeTakeFirst())?.claim_id !== claimId;
|
|
@@ -1422,10 +1478,14 @@ var MemoryWebhookStore = class {
|
|
|
1422
1478
|
}
|
|
1423
1479
|
seen(token, key, ttlSeconds) {
|
|
1424
1480
|
const now = Date.now();
|
|
1425
|
-
for (const [k,
|
|
1481
|
+
for (const [k, entry] of this.#deliveries) if (entry.expiresAt < now) this.#deliveries.delete(k);
|
|
1426
1482
|
const composite = `${token}|${key}`;
|
|
1427
|
-
|
|
1428
|
-
|
|
1483
|
+
const existing = this.#deliveries.get(composite);
|
|
1484
|
+
if (existing && existing.seenAt >= now - ttlSeconds * 1e3) return Promise.resolve(true);
|
|
1485
|
+
this.#deliveries.set(composite, {
|
|
1486
|
+
seenAt: now,
|
|
1487
|
+
expiresAt: now + ttlSeconds * 1e3
|
|
1488
|
+
});
|
|
1429
1489
|
return Promise.resolve(false);
|
|
1430
1490
|
}
|
|
1431
1491
|
};
|
|
@@ -1460,7 +1520,7 @@ const stripScalarDefinitions = (doc) => {
|
|
|
1460
1520
|
* whole gateway. We dedupe these by name; field/operation/extension nodes are
|
|
1461
1521
|
* left untouched.
|
|
1462
1522
|
*/
|
|
1463
|
-
const TYPE_DEFINITION_KINDS = new Set([
|
|
1523
|
+
const TYPE_DEFINITION_KINDS = /* @__PURE__ */ new Set([
|
|
1464
1524
|
Kind.OBJECT_TYPE_DEFINITION,
|
|
1465
1525
|
Kind.ENUM_TYPE_DEFINITION,
|
|
1466
1526
|
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
@@ -1496,6 +1556,98 @@ const dedupeTypeDefinitions = (doc) => {
|
|
|
1496
1556
|
definitions
|
|
1497
1557
|
};
|
|
1498
1558
|
};
|
|
1559
|
+
const EMPTY_DOCUMENT = {
|
|
1560
|
+
kind: Kind.DOCUMENT,
|
|
1561
|
+
definitions: []
|
|
1562
|
+
};
|
|
1563
|
+
const OUTPUT_TYPE_KINDS = [
|
|
1564
|
+
Kind.OBJECT_TYPE_DEFINITION,
|
|
1565
|
+
Kind.OBJECT_TYPE_EXTENSION,
|
|
1566
|
+
Kind.ENUM_TYPE_DEFINITION,
|
|
1567
|
+
Kind.ENUM_TYPE_EXTENSION,
|
|
1568
|
+
Kind.UNION_TYPE_DEFINITION,
|
|
1569
|
+
Kind.UNION_TYPE_EXTENSION,
|
|
1570
|
+
Kind.INTERFACE_TYPE_DEFINITION,
|
|
1571
|
+
Kind.INTERFACE_TYPE_EXTENSION
|
|
1572
|
+
];
|
|
1573
|
+
const STATE_PREFIX_KINDS = new Set(OUTPUT_TYPE_KINDS);
|
|
1574
|
+
const ALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
|
|
1575
|
+
...OUTPUT_TYPE_KINDS,
|
|
1576
|
+
Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
1577
|
+
Kind.INPUT_OBJECT_TYPE_EXTENSION
|
|
1578
|
+
]);
|
|
1579
|
+
const TYPE_NAME_KINDS = /* @__PURE__ */ new Set([
|
|
1580
|
+
...ALL_PREFIX_KINDS,
|
|
1581
|
+
Kind.SCALAR_TYPE_DEFINITION,
|
|
1582
|
+
Kind.SCALAR_TYPE_EXTENSION,
|
|
1583
|
+
Kind.NAMED_TYPE
|
|
1584
|
+
]);
|
|
1585
|
+
const LOCAL_STATE_STUBS = /* @__PURE__ */ new Set([
|
|
1586
|
+
"AccountSnapshotLocalState",
|
|
1587
|
+
"BudgetStatementLocalState",
|
|
1588
|
+
"ScopeFrameworkLocalState"
|
|
1589
|
+
]);
|
|
1590
|
+
/** Parse SDL; blank or comment-only input yields an empty document. */
|
|
1591
|
+
function parseSdl(sdl) {
|
|
1592
|
+
const source = new Source(sdl);
|
|
1593
|
+
if (new Lexer(source).advance().kind === TokenKind.EOF) return EMPTY_DOCUMENT;
|
|
1594
|
+
try {
|
|
1595
|
+
return parse(source, { noLocation: true });
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
const withoutSemicolons = sdl.replaceAll(";", "");
|
|
1598
|
+
if (withoutSemicolons === sdl) throw error;
|
|
1599
|
+
return parse(withoutSemicolons, { noLocation: true });
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
function definedTypeNames(doc, kinds) {
|
|
1603
|
+
return doc.definitions.filter((def) => kinds.has(def.kind)).map((def) => def.name.value);
|
|
1604
|
+
}
|
|
1605
|
+
/** Prefix type definition names and type references found in `names`. */
|
|
1606
|
+
function prefixTypeNames(doc, prefix, names) {
|
|
1607
|
+
return visit(doc, { enter(node) {
|
|
1608
|
+
if (!TYPE_NAME_KINDS.has(node.kind)) return void 0;
|
|
1609
|
+
const { name } = node;
|
|
1610
|
+
if (!names.has(name.value)) return void 0;
|
|
1611
|
+
return {
|
|
1612
|
+
...node,
|
|
1613
|
+
name: {
|
|
1614
|
+
...name,
|
|
1615
|
+
value: `${prefix}_${name.value}`
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1618
|
+
} });
|
|
1619
|
+
}
|
|
1620
|
+
/** State schema definitions without inputs, `DateTime` or the given object types. */
|
|
1621
|
+
function stateTypeDefinitions(sdl, droppedObjectTypes = /* @__PURE__ */ new Set()) {
|
|
1622
|
+
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)));
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Prefixed operation schemas per module. The first definition of each module
|
|
1626
|
+
* carries a "Module: <Name>" description unless it has its own.
|
|
1627
|
+
*/
|
|
1628
|
+
function buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema) {
|
|
1629
|
+
const names = new Set(allTypeNames);
|
|
1630
|
+
return specification?.modules.map((module) => {
|
|
1631
|
+
const definitions = module.operations.filter((op) => hasValidSchema(op.schema)).flatMap((op) => {
|
|
1632
|
+
const doc = parseSdl(op.schema ?? "");
|
|
1633
|
+
return prefixTypeNames(doc, documentName, /* @__PURE__ */ new Set([...names, ...definedTypeNames(doc, ALL_PREFIX_KINDS)])).definitions;
|
|
1634
|
+
});
|
|
1635
|
+
if (definitions.length === 0) return "";
|
|
1636
|
+
const [first, ...rest] = definitions;
|
|
1637
|
+
const header = "description" in first && !first.description ? {
|
|
1638
|
+
...first,
|
|
1639
|
+
description: {
|
|
1640
|
+
kind: Kind.STRING,
|
|
1641
|
+
value: `Module: ${pascalCase(module.name)}`,
|
|
1642
|
+
block: true
|
|
1643
|
+
}
|
|
1644
|
+
} : first;
|
|
1645
|
+
return print({
|
|
1646
|
+
kind: Kind.DOCUMENT,
|
|
1647
|
+
definitions: [header, ...rest]
|
|
1648
|
+
});
|
|
1649
|
+
}).filter(Boolean).join("\n") ?? "";
|
|
1650
|
+
}
|
|
1499
1651
|
const buildSubgraphSchemaModule = (documentModels, resolvers, typeDefs) => {
|
|
1500
1652
|
const newResolvers = {
|
|
1501
1653
|
...resolvers,
|
|
@@ -1534,20 +1686,13 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1534
1686
|
const latestSpec = documentModel.global.specifications.at(-1);
|
|
1535
1687
|
const globalSchema = latestSpec?.state.global.schema ?? "";
|
|
1536
1688
|
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;
|
|
1689
|
+
const definitions = [...stateTypeDefinitions(globalSchema), ...stateTypeDefinitions(localSchema, LOCAL_STATE_STUBS)];
|
|
1690
|
+
const stateDoc = {
|
|
1691
|
+
kind: Kind.DOCUMENT,
|
|
1692
|
+
definitions
|
|
1693
|
+
};
|
|
1694
|
+
const prefixed = prefixTypeNames(stateDoc, dmSchemaName, new Set(definedTypeNames(stateDoc, STATE_PREFIX_KINDS)));
|
|
1695
|
+
if (definitions.length > 0) dmSchema += `${print(prefixed)}\n`;
|
|
1551
1696
|
dmSchema += `
|
|
1552
1697
|
type ${dmSchemaName} implements IDocument {
|
|
1553
1698
|
id: String!
|
|
@@ -1562,7 +1707,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1562
1707
|
stateJSON: JSONObject
|
|
1563
1708
|
}\n`;
|
|
1564
1709
|
});
|
|
1565
|
-
|
|
1710
|
+
const schema = gql`
|
|
1566
1711
|
scalar JSONObject
|
|
1567
1712
|
scalar AttachmentRef
|
|
1568
1713
|
# Codegen scalars not in the document-engineering set.
|
|
@@ -1613,7 +1758,7 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1613
1758
|
operations(first: Int, skip: Int): [Operation!]!
|
|
1614
1759
|
stateJSON: JSONObject
|
|
1615
1760
|
}
|
|
1616
|
-
${dmSchema
|
|
1761
|
+
${dmSchema}
|
|
1617
1762
|
|
|
1618
1763
|
type GqlDocument implements IDocument {
|
|
1619
1764
|
id: String!
|
|
@@ -1638,34 +1783,32 @@ const getDocumentModelTypeDefs = (documentModels, typeDefs$1) => {
|
|
|
1638
1783
|
}
|
|
1639
1784
|
|
|
1640
1785
|
${stripScalarDefinitions(typeDefs$1)}
|
|
1641
|
-
|
|
1786
|
+
`;
|
|
1787
|
+
return dedupeTypeDefinitions(schema);
|
|
1642
1788
|
};
|
|
1643
1789
|
/**
|
|
1644
|
-
* Extract
|
|
1645
|
-
*
|
|
1646
|
-
* @returns {string[]} Array of type names
|
|
1790
|
+
* Extract the names of types defined in a GraphQL schema.
|
|
1791
|
+
* Unparseable schemas yield no names.
|
|
1647
1792
|
*/
|
|
1648
1793
|
function extractTypeNames(schema) {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1794
|
+
try {
|
|
1795
|
+
return definedTypeNames(parseSdl(schema), ALL_PREFIX_KINDS);
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
logger.debug(`Skipping unparseable schema: ${String(error)}`);
|
|
1798
|
+
return [];
|
|
1799
|
+
}
|
|
1652
1800
|
}
|
|
1653
1801
|
/**
|
|
1654
|
-
*
|
|
1655
|
-
* @param
|
|
1656
|
-
* @param {Set<string>} excludeTypeNames - Type names to exclude from extraction
|
|
1657
|
-
* @returns {string} All input type definitions as a string
|
|
1802
|
+
* Print the input type definitions of a GraphQL schema.
|
|
1803
|
+
* @param excludeTypeNames - Input type names to leave out
|
|
1658
1804
|
*/
|
|
1659
1805
|
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");
|
|
1806
|
+
const definitions = parseSdl(schema).definitions.filter((def) => def.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION && !excludeTypeNames.has(def.name.value));
|
|
1807
|
+
if (definitions.length === 0) return "";
|
|
1808
|
+
return print({
|
|
1809
|
+
kind: Kind.DOCUMENT,
|
|
1810
|
+
definitions
|
|
1811
|
+
});
|
|
1669
1812
|
}
|
|
1670
1813
|
/**
|
|
1671
1814
|
* Extract the root state type name from a scope's GraphQL schema.
|
|
@@ -1734,9 +1877,7 @@ function generateStateInputTypes(stateSchema, excludeTypeNames) {
|
|
|
1734
1877
|
case Kind.OBJECT_TYPE_DEFINITION:
|
|
1735
1878
|
objectNames.add(def.name.value);
|
|
1736
1879
|
break;
|
|
1737
|
-
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
|
|
1738
|
-
existingInputNames.add(def.name.value);
|
|
1739
|
-
break;
|
|
1880
|
+
case Kind.INPUT_OBJECT_TYPE_DEFINITION: existingInputNames.add(def.name.value);
|
|
1740
1881
|
}
|
|
1741
1882
|
const inputTypes = [];
|
|
1742
1883
|
for (const def of ast.definitions) {
|
|
@@ -1763,18 +1904,10 @@ function generateStateInputTypes(stateSchema, excludeTypeNames) {
|
|
|
1763
1904
|
*/
|
|
1764
1905
|
function applyGraphQLTypePrefixes(schema, prefix, externalTypeNames = []) {
|
|
1765
1906
|
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;
|
|
1907
|
+
const doc = parseSdl(schema);
|
|
1908
|
+
const names = /* @__PURE__ */ new Set([...definedTypeNames(doc, ALL_PREFIX_KINDS), ...externalTypeNames]);
|
|
1909
|
+
if (names.size === 0) return schema;
|
|
1910
|
+
return print(prefixTypeNames(doc, prefix, names));
|
|
1778
1911
|
}
|
|
1779
1912
|
/**
|
|
1780
1913
|
* Generate a GraphQL schema for a document model.
|
|
@@ -1793,11 +1926,11 @@ function generateDocumentModelSchema(documentModel, options = {}) {
|
|
|
1793
1926
|
const localStateTypeNames = extractTypeNames(localStateSchema ?? "");
|
|
1794
1927
|
const stateTypeNames = [...globalStateTypeNames, ...localStateTypeNames];
|
|
1795
1928
|
const allOperationTypeNames = specification?.modules.flatMap((module) => module.operations.flatMap((op) => extractTypeNames(op.schema ?? ""))) ?? [];
|
|
1796
|
-
const allTypeNames = [
|
|
1797
|
-
const
|
|
1929
|
+
const allTypeNames = [.../* @__PURE__ */ new Set([...stateTypeNames, ...allOperationTypeNames])];
|
|
1930
|
+
const operationInputTypeNames = new Set(allOperationTypeNames);
|
|
1931
|
+
const prefixedStateInputTypes = applyGraphQLTypePrefixes(extractInputTypeDefinitions(globalStateSchema ?? "", operationInputTypeNames), documentName, allTypeNames);
|
|
1798
1932
|
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);
|
|
1933
|
+
if (useNewApi) return generateNewApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema);
|
|
1801
1934
|
return generateLegacyApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema);
|
|
1802
1935
|
}
|
|
1803
1936
|
/**
|
|
@@ -1807,10 +1940,7 @@ function generateLegacyApiSchema(documentName, specification, prefixedStateInput
|
|
|
1807
1940
|
const createDocumentMutation = `${documentName}_createDocument(name:String!, driveId:String): String`;
|
|
1808
1941
|
const operationMutations = specification?.modules.flatMap((module) => module.operations.filter((op) => op.name && hasValidSchema(op.schema)).map((op) => `${documentName}_${camelCase(op.name)}(
|
|
1809
1942
|
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") ?? "";
|
|
1943
|
+
const moduleSchemas = buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema);
|
|
1814
1944
|
return gql`
|
|
1815
1945
|
"""
|
|
1816
1946
|
Queries: ${documentName} Document
|
|
@@ -1847,7 +1977,7 @@ function generateLegacyApiSchema(documentName, specification, prefixedStateInput
|
|
|
1847
1977
|
* in getDocumentModelTypeDefs() which is used during schema composition.
|
|
1848
1978
|
* Including them here would cause duplicate type definitions.
|
|
1849
1979
|
*/
|
|
1850
|
-
function generateNewApiSchema(documentName, specification,
|
|
1980
|
+
function generateNewApiSchema(documentName, specification, prefixedStateInputTypes, allTypeNames, hasValidSchema) {
|
|
1851
1981
|
const stateType = `${documentName}_FullState!`;
|
|
1852
1982
|
const sharedBaseTypes = `
|
|
1853
1983
|
"""Hash configuration for document state"""
|
|
@@ -1890,7 +2020,7 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1890
2020
|
|
|
1891
2021
|
input ${documentName}_SearchFilterInput {
|
|
1892
2022
|
parentId: String
|
|
1893
|
-
identifiers: [String!]
|
|
2023
|
+
identifiers: [String!] @deprecated(reason: "Ignored. Filter by parentId.")
|
|
1894
2024
|
}
|
|
1895
2025
|
`;
|
|
1896
2026
|
const revisionType = RevisionType;
|
|
@@ -1924,7 +2054,6 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1924
2054
|
"""
|
|
1925
2055
|
type ${documentName}_DocumentResultPage {
|
|
1926
2056
|
items: [${documentName}MutationResult!]!
|
|
1927
|
-
totalCount: Int!
|
|
1928
2057
|
hasNextPage: Boolean!
|
|
1929
2058
|
hasPreviousPage: Boolean!
|
|
1930
2059
|
cursor: String
|
|
@@ -1933,7 +2062,7 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1933
2062
|
const queries = `
|
|
1934
2063
|
type ${documentName}Queries {
|
|
1935
2064
|
"""Get a specific ${documentName} document by identifier"""
|
|
1936
|
-
document(identifier: String
|
|
2065
|
+
document(idOrSlug: String, identifier: String @deprecated(reason: "Use idOrSlug."), view: ${documentName}_ViewFilterInput): ${documentName}_DocumentWithChildren
|
|
1937
2066
|
|
|
1938
2067
|
"""Get all ${documentName} documents (paged)"""
|
|
1939
2068
|
documents(paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
|
|
@@ -1942,10 +2071,10 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1942
2071
|
findDocuments(search: ${documentName}_SearchFilterInput, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
|
|
1943
2072
|
|
|
1944
2073
|
"""Get outgoing relationships of a ${documentName} document"""
|
|
1945
|
-
documentOutgoingRelationships(sourceIdentifier: String
|
|
2074
|
+
documentOutgoingRelationships(sourceIdOrSlug: String, sourceIdentifier: String @deprecated(reason: "Use sourceIdOrSlug."), relationshipType: String!, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
|
|
1946
2075
|
|
|
1947
2076
|
"""Get incoming relationships to a ${documentName} document"""
|
|
1948
|
-
documentIncomingRelationships(targetIdentifier: String
|
|
2077
|
+
documentIncomingRelationships(targetIdOrSlug: String, targetIdentifier: String @deprecated(reason: "Use targetIdOrSlug."), relationshipType: String!, view: ${documentName}_ViewFilterInput, paging: ${documentName}_PagingInput): ${documentName}_DocumentResultPage!
|
|
1949
2078
|
}
|
|
1950
2079
|
`;
|
|
1951
2080
|
let initialStateInputSchema = "";
|
|
@@ -1976,13 +2105,10 @@ function generateNewApiSchema(documentName, specification, _stateSchemaTypes, pr
|
|
|
1976
2105
|
initialStateInputSchema = inputTypeDefs ? `${inputTypeDefs}\n\n${wrapper}` : wrapper;
|
|
1977
2106
|
}
|
|
1978
2107
|
}
|
|
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") ?? "";
|
|
2108
|
+
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!`;
|
|
2109
|
+
const createEmptyDocumentMutation = `createEmptyDocument(parentIdOrSlug: String, parentIdentifier: String @deprecated(reason: "Use parentIdOrSlug.")): ${documentName}MutationResult!`;
|
|
2110
|
+
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 ") ?? "";
|
|
2111
|
+
const moduleSchemas = buildModuleSchemas(specification, documentName, allTypeNames, hasValidSchema);
|
|
1986
2112
|
return gql`
|
|
1987
2113
|
scalar DateTime
|
|
1988
2114
|
scalar JSONObject
|
|
@@ -2135,7 +2261,8 @@ function OperationsFilterInputSchema() {
|
|
|
2135
2261
|
return z$1.object({
|
|
2136
2262
|
actionTypes: z$1.array(z$1.string()).nullish(),
|
|
2137
2263
|
branch: z$1.string().nullish(),
|
|
2138
|
-
documentId: z$1.string(),
|
|
2264
|
+
documentId: z$1.string().nullish(),
|
|
2265
|
+
documentIdOrSlug: z$1.string().nullish(),
|
|
2139
2266
|
scopes: z$1.array(z$1.string()).nullish(),
|
|
2140
2267
|
sinceRevision: z$1.number().nullish(),
|
|
2141
2268
|
timestampFrom: z$1.string().nullish(),
|
|
@@ -2200,11 +2327,19 @@ function SyncEnvelopeInputSchema() {
|
|
|
2200
2327
|
type: SyncEnvelopeTypeSchema
|
|
2201
2328
|
});
|
|
2202
2329
|
}
|
|
2330
|
+
function SyncRefusalInputSchema() {
|
|
2331
|
+
return z$1.object({
|
|
2332
|
+
branch: z$1.string(),
|
|
2333
|
+
documentId: z$1.string(),
|
|
2334
|
+
kind: z$1.string().nullish()
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2203
2337
|
function TouchChannelInputSchema() {
|
|
2204
2338
|
return z$1.object({
|
|
2205
2339
|
collectionId: z$1.string(),
|
|
2206
2340
|
filter: z$1.lazy(() => RemoteFilterInputSchema()),
|
|
2207
2341
|
id: z$1.string(),
|
|
2342
|
+
manifest: z$1.custom((v) => v != null).nullish(),
|
|
2208
2343
|
name: z$1.string(),
|
|
2209
2344
|
sinceTimestampUtcMs: z$1.string()
|
|
2210
2345
|
});
|
|
@@ -2230,6 +2365,16 @@ const PhDocumentFieldsFragmentDoc = gql`
|
|
|
2230
2365
|
lastModifiedAtUtcIso
|
|
2231
2366
|
}
|
|
2232
2367
|
`;
|
|
2368
|
+
const DocumentRelationshipFieldsFragmentDoc = gql`
|
|
2369
|
+
fragment DocumentRelationshipFields on DocumentRelationship {
|
|
2370
|
+
sourceId
|
|
2371
|
+
targetId
|
|
2372
|
+
relationshipType
|
|
2373
|
+
metadata
|
|
2374
|
+
createdAt
|
|
2375
|
+
updatedAt
|
|
2376
|
+
}
|
|
2377
|
+
`;
|
|
2233
2378
|
const GetDocumentModelsDocument = gql`
|
|
2234
2379
|
query GetDocumentModels($namespace: String, $paging: PagingInput) {
|
|
2235
2380
|
documentModels(namespace: $namespace, paging: $paging) {
|
|
@@ -2240,7 +2385,6 @@ const GetDocumentModelsDocument = gql`
|
|
|
2240
2385
|
version
|
|
2241
2386
|
specification
|
|
2242
2387
|
}
|
|
2243
|
-
totalCount
|
|
2244
2388
|
hasNextPage
|
|
2245
2389
|
hasPreviousPage
|
|
2246
2390
|
cursor
|
|
@@ -2299,7 +2443,6 @@ const GetDocumentWithOperationsDocument = gql`
|
|
|
2299
2443
|
}
|
|
2300
2444
|
}
|
|
2301
2445
|
}
|
|
2302
|
-
totalCount
|
|
2303
2446
|
hasNextPage
|
|
2304
2447
|
hasPreviousPage
|
|
2305
2448
|
cursor
|
|
@@ -2326,7 +2469,6 @@ const GetDocumentOutgoingRelationshipsDocument = gql`
|
|
|
2326
2469
|
items {
|
|
2327
2470
|
...PHDocumentFields
|
|
2328
2471
|
}
|
|
2329
|
-
totalCount
|
|
2330
2472
|
hasNextPage
|
|
2331
2473
|
hasPreviousPage
|
|
2332
2474
|
cursor
|
|
@@ -2350,7 +2492,6 @@ const GetDocumentIncomingRelationshipsDocument = gql`
|
|
|
2350
2492
|
items {
|
|
2351
2493
|
...PHDocumentFields
|
|
2352
2494
|
}
|
|
2353
|
-
totalCount
|
|
2354
2495
|
hasNextPage
|
|
2355
2496
|
hasPreviousPage
|
|
2356
2497
|
cursor
|
|
@@ -2358,6 +2499,52 @@ const GetDocumentIncomingRelationshipsDocument = gql`
|
|
|
2358
2499
|
}
|
|
2359
2500
|
${PhDocumentFieldsFragmentDoc}
|
|
2360
2501
|
`;
|
|
2502
|
+
const GetDocumentOutgoingRelationshipEdgesDocument = gql`
|
|
2503
|
+
query GetDocumentOutgoingRelationshipEdges(
|
|
2504
|
+
$sourceIdentifier: String!
|
|
2505
|
+
$relationshipType: String
|
|
2506
|
+
$view: ViewFilterInput
|
|
2507
|
+
$paging: PagingInput
|
|
2508
|
+
) {
|
|
2509
|
+
documentOutgoingRelationshipEdges(
|
|
2510
|
+
sourceIdentifier: $sourceIdentifier
|
|
2511
|
+
relationshipType: $relationshipType
|
|
2512
|
+
view: $view
|
|
2513
|
+
paging: $paging
|
|
2514
|
+
) {
|
|
2515
|
+
items {
|
|
2516
|
+
...DocumentRelationshipFields
|
|
2517
|
+
}
|
|
2518
|
+
hasNextPage
|
|
2519
|
+
hasPreviousPage
|
|
2520
|
+
cursor
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
${DocumentRelationshipFieldsFragmentDoc}
|
|
2524
|
+
`;
|
|
2525
|
+
const GetDocumentIncomingRelationshipEdgesDocument = gql`
|
|
2526
|
+
query GetDocumentIncomingRelationshipEdges(
|
|
2527
|
+
$targetIdentifier: String!
|
|
2528
|
+
$relationshipType: String
|
|
2529
|
+
$view: ViewFilterInput
|
|
2530
|
+
$paging: PagingInput
|
|
2531
|
+
) {
|
|
2532
|
+
documentIncomingRelationshipEdges(
|
|
2533
|
+
targetIdentifier: $targetIdentifier
|
|
2534
|
+
relationshipType: $relationshipType
|
|
2535
|
+
view: $view
|
|
2536
|
+
paging: $paging
|
|
2537
|
+
) {
|
|
2538
|
+
items {
|
|
2539
|
+
...DocumentRelationshipFields
|
|
2540
|
+
}
|
|
2541
|
+
hasNextPage
|
|
2542
|
+
hasPreviousPage
|
|
2543
|
+
cursor
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
${DocumentRelationshipFieldsFragmentDoc}
|
|
2547
|
+
`;
|
|
2361
2548
|
const FindDocumentsDocument = gql`
|
|
2362
2549
|
query FindDocuments(
|
|
2363
2550
|
$search: SearchFilterInput
|
|
@@ -2368,7 +2555,6 @@ const FindDocumentsDocument = gql`
|
|
|
2368
2555
|
items {
|
|
2369
2556
|
...PHDocumentFields
|
|
2370
2557
|
}
|
|
2371
|
-
totalCount
|
|
2372
2558
|
hasNextPage
|
|
2373
2559
|
hasPreviousPage
|
|
2374
2560
|
cursor
|
|
@@ -2412,7 +2598,6 @@ const GetDocumentOperationsDocument = gql`
|
|
|
2412
2598
|
}
|
|
2413
2599
|
}
|
|
2414
2600
|
}
|
|
2415
|
-
totalCount
|
|
2416
2601
|
hasNextPage
|
|
2417
2602
|
hasPreviousPage
|
|
2418
2603
|
cursor
|
|
@@ -2548,12 +2733,34 @@ const AddRelationshipDocument = gql`
|
|
|
2548
2733
|
$sourceIdentifier: String!
|
|
2549
2734
|
$targetIdentifier: String!
|
|
2550
2735
|
$relationshipType: String!
|
|
2736
|
+
$metadata: JSONObject
|
|
2551
2737
|
$branch: String
|
|
2552
2738
|
) {
|
|
2553
2739
|
addRelationship(
|
|
2554
2740
|
sourceIdentifier: $sourceIdentifier
|
|
2555
2741
|
targetIdentifier: $targetIdentifier
|
|
2556
2742
|
relationshipType: $relationshipType
|
|
2743
|
+
metadata: $metadata
|
|
2744
|
+
branch: $branch
|
|
2745
|
+
) {
|
|
2746
|
+
...PHDocumentFields
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
${PhDocumentFieldsFragmentDoc}
|
|
2750
|
+
`;
|
|
2751
|
+
const UpdateRelationshipDocument = gql`
|
|
2752
|
+
mutation UpdateRelationship(
|
|
2753
|
+
$sourceIdentifier: String!
|
|
2754
|
+
$targetIdentifier: String!
|
|
2755
|
+
$relationshipType: String!
|
|
2756
|
+
$metadata: JSONObject
|
|
2757
|
+
$branch: String
|
|
2758
|
+
) {
|
|
2759
|
+
updateRelationship(
|
|
2760
|
+
sourceIdentifier: $sourceIdentifier
|
|
2761
|
+
targetIdentifier: $targetIdentifier
|
|
2762
|
+
relationshipType: $relationshipType
|
|
2763
|
+
metadata: $metadata
|
|
2557
2764
|
branch: $branch
|
|
2558
2765
|
) {
|
|
2559
2766
|
...PHDocumentFields
|
|
@@ -2650,11 +2857,15 @@ const PollSyncEnvelopesDocument = gql`
|
|
|
2650
2857
|
$channelId: String!
|
|
2651
2858
|
$outboxAck: Int!
|
|
2652
2859
|
$outboxLatest: Int!
|
|
2860
|
+
$manifestRevision: String
|
|
2861
|
+
$refusals: [SyncRefusalInput!]
|
|
2653
2862
|
) {
|
|
2654
2863
|
pollSyncEnvelopes(
|
|
2655
2864
|
channelId: $channelId
|
|
2656
2865
|
outboxAck: $outboxAck
|
|
2657
2866
|
outboxLatest: $outboxLatest
|
|
2867
|
+
manifestRevision: $manifestRevision
|
|
2868
|
+
refusals: $refusals
|
|
2658
2869
|
) {
|
|
2659
2870
|
envelopes {
|
|
2660
2871
|
type
|
|
@@ -2714,6 +2925,8 @@ const PollSyncEnvelopesDocument = gql`
|
|
|
2714
2925
|
errorType
|
|
2715
2926
|
}
|
|
2716
2927
|
hasMore
|
|
2928
|
+
manifestRevision
|
|
2929
|
+
peerManifestRevision
|
|
2717
2930
|
}
|
|
2718
2931
|
}
|
|
2719
2932
|
`;
|
|
@@ -2722,12 +2935,19 @@ const TouchChannelDocument = gql`
|
|
|
2722
2935
|
touchChannel(input: $input) {
|
|
2723
2936
|
success
|
|
2724
2937
|
ackOrdinal
|
|
2938
|
+
manifest
|
|
2725
2939
|
}
|
|
2726
2940
|
}
|
|
2727
2941
|
`;
|
|
2728
2942
|
const PushSyncEnvelopesDocument = gql`
|
|
2729
|
-
mutation PushSyncEnvelopes(
|
|
2730
|
-
|
|
2943
|
+
mutation PushSyncEnvelopes(
|
|
2944
|
+
$envelopes: [SyncEnvelopeInput!]!
|
|
2945
|
+
$peerManifestRevision: String
|
|
2946
|
+
) {
|
|
2947
|
+
pushSyncEnvelopes(
|
|
2948
|
+
envelopes: $envelopes
|
|
2949
|
+
peerManifestRevision: $peerManifestRevision
|
|
2950
|
+
)
|
|
2731
2951
|
}
|
|
2732
2952
|
`;
|
|
2733
2953
|
function getSdk(requester) {
|
|
@@ -2747,6 +2967,12 @@ function getSdk(requester) {
|
|
|
2747
2967
|
GetDocumentIncomingRelationships(variables, options) {
|
|
2748
2968
|
return requester(GetDocumentIncomingRelationshipsDocument, variables, options);
|
|
2749
2969
|
},
|
|
2970
|
+
GetDocumentOutgoingRelationshipEdges(variables, options) {
|
|
2971
|
+
return requester(GetDocumentOutgoingRelationshipEdgesDocument, variables, options);
|
|
2972
|
+
},
|
|
2973
|
+
GetDocumentIncomingRelationshipEdges(variables, options) {
|
|
2974
|
+
return requester(GetDocumentIncomingRelationshipEdgesDocument, variables, options);
|
|
2975
|
+
},
|
|
2750
2976
|
FindDocuments(variables, options) {
|
|
2751
2977
|
return requester(FindDocumentsDocument, variables, options);
|
|
2752
2978
|
},
|
|
@@ -2780,6 +3006,9 @@ function getSdk(requester) {
|
|
|
2780
3006
|
AddRelationship(variables, options) {
|
|
2781
3007
|
return requester(AddRelationshipDocument, variables, options);
|
|
2782
3008
|
},
|
|
3009
|
+
UpdateRelationship(variables, options) {
|
|
3010
|
+
return requester(UpdateRelationshipDocument, variables, options);
|
|
3011
|
+
},
|
|
2783
3012
|
RemoveRelationship(variables, options) {
|
|
2784
3013
|
return requester(RemoveRelationshipDocument, variables, options);
|
|
2785
3014
|
},
|
|
@@ -2820,8 +3049,7 @@ function toDocumentModelResultPage(result) {
|
|
|
2820
3049
|
cursor: result.nextCursor ?? null,
|
|
2821
3050
|
hasNextPage: !!result.nextCursor,
|
|
2822
3051
|
hasPreviousPage: !!result.options.cursor,
|
|
2823
|
-
items: models.map(toGqlDocumentModelState)
|
|
2824
|
-
totalCount: result.results.length
|
|
3052
|
+
items: models.map(toGqlDocumentModelState)
|
|
2825
3053
|
};
|
|
2826
3054
|
}
|
|
2827
3055
|
/**
|
|
@@ -2853,8 +3081,32 @@ function toPhDocumentResultPage(result) {
|
|
|
2853
3081
|
cursor: result.nextCursor ?? null,
|
|
2854
3082
|
hasNextPage: !!result.nextCursor,
|
|
2855
3083
|
hasPreviousPage: !!result.options.cursor,
|
|
2856
|
-
items: result.results.map(toGqlPhDocument)
|
|
2857
|
-
|
|
3084
|
+
items: result.results.map(toGqlPhDocument)
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
/**
|
|
3088
|
+
* Converts a relationship edge from the indexer to GraphQL format.
|
|
3089
|
+
*/
|
|
3090
|
+
function toGqlDocumentRelationship(relationship) {
|
|
3091
|
+
return {
|
|
3092
|
+
sourceId: relationship.sourceId,
|
|
3093
|
+
targetId: relationship.targetId,
|
|
3094
|
+
relationshipType: relationship.relationshipType,
|
|
3095
|
+
metadata: relationship.metadata ?? null,
|
|
3096
|
+
createdAt: relationship.createdAt,
|
|
3097
|
+
updatedAt: relationship.updatedAt
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Converts a PagedResults of relationship edges to GraphQL
|
|
3102
|
+
* DocumentRelationshipResultPage format
|
|
3103
|
+
*/
|
|
3104
|
+
function toDocumentRelationshipResultPage(result) {
|
|
3105
|
+
return {
|
|
3106
|
+
cursor: result.nextCursor ?? null,
|
|
3107
|
+
hasNextPage: !!result.nextCursor,
|
|
3108
|
+
hasPreviousPage: !!result.options.cursor,
|
|
3109
|
+
items: result.results.map(toGqlDocumentRelationship)
|
|
2858
3110
|
};
|
|
2859
3111
|
}
|
|
2860
3112
|
/**
|
|
@@ -2904,8 +3156,8 @@ function fromInputMaybe(value) {
|
|
|
2904
3156
|
function toReactorPropagationMode(gqlMode) {
|
|
2905
3157
|
if (gqlMode == null) return;
|
|
2906
3158
|
switch (gqlMode) {
|
|
2907
|
-
case
|
|
2908
|
-
case
|
|
3159
|
+
case "CASCADE": return PropagationMode$1.Cascade;
|
|
3160
|
+
case "ORPHAN": return PropagationMode$1.None;
|
|
2909
3161
|
}
|
|
2910
3162
|
}
|
|
2911
3163
|
/**
|
|
@@ -2915,10 +3167,10 @@ function toReactorPropagationMode(gqlMode) {
|
|
|
2915
3167
|
*/
|
|
2916
3168
|
function toGqlActionEvaluation(evaluation) {
|
|
2917
3169
|
return evaluation.decision === "allow" ? {
|
|
2918
|
-
decision:
|
|
3170
|
+
decision: "ALLOW",
|
|
2919
3171
|
reason: null
|
|
2920
3172
|
} : {
|
|
2921
|
-
decision:
|
|
3173
|
+
decision: "DENY",
|
|
2922
3174
|
reason: evaluation.reason
|
|
2923
3175
|
};
|
|
2924
3176
|
}
|
|
@@ -3123,8 +3375,7 @@ function toOperationResultPage(result) {
|
|
|
3123
3375
|
cursor: result.nextCursor ?? null,
|
|
3124
3376
|
hasNextPage: !!result.nextCursor,
|
|
3125
3377
|
hasPreviousPage: !!result.options.cursor && result.options.cursor !== "0",
|
|
3126
|
-
items: result.results.map(serializeOperationForGraphQL)
|
|
3127
|
-
totalCount: result.results.length
|
|
3378
|
+
items: result.results.map(serializeOperationForGraphQL)
|
|
3128
3379
|
};
|
|
3129
3380
|
}
|
|
3130
3381
|
function toGqlDocumentChangeEvent(event) {
|
|
@@ -3196,6 +3447,21 @@ function pickDriveClient(reactorClient, driveContainerType, reactorDriveClient)
|
|
|
3196
3447
|
}
|
|
3197
3448
|
return reactorClient.drives;
|
|
3198
3449
|
}
|
|
3450
|
+
/**
|
|
3451
|
+
* A mutation's document re-read as the caller, since the client reads it back
|
|
3452
|
+
* as its signer. A caller who may write but not read gets it stripped.
|
|
3453
|
+
*/
|
|
3454
|
+
async function readAsCaller(reactorClient, document, subject, branch) {
|
|
3455
|
+
if (!subject) return document;
|
|
3456
|
+
try {
|
|
3457
|
+
return await reactorClient.get(document.header.id, {
|
|
3458
|
+
subject,
|
|
3459
|
+
branch
|
|
3460
|
+
});
|
|
3461
|
+
} catch (error) {
|
|
3462
|
+
throw new GraphQLError(`Failed to read the document back: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3199
3465
|
async function documentModels(reactorClient, args) {
|
|
3200
3466
|
const namespace = fromInputMaybe(args.namespace);
|
|
3201
3467
|
let paging;
|
|
@@ -3228,13 +3494,13 @@ async function document(reactorClient, args, subject) {
|
|
|
3228
3494
|
};
|
|
3229
3495
|
let result;
|
|
3230
3496
|
try {
|
|
3231
|
-
result = await reactorClient.get(args.
|
|
3497
|
+
result = await reactorClient.get(args.idOrSlug, view);
|
|
3232
3498
|
} catch (error) {
|
|
3233
3499
|
throw new GraphQLError(`Failed to fetch document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3234
3500
|
}
|
|
3235
3501
|
let children;
|
|
3236
3502
|
try {
|
|
3237
|
-
children = await reactorClient.getOutgoingRelationships(args.
|
|
3503
|
+
children = await reactorClient.getOutgoingRelationships(args.idOrSlug, "child", view);
|
|
3238
3504
|
} catch (error) {
|
|
3239
3505
|
throw new GraphQLError(`Failed to fetch children: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3240
3506
|
}
|
|
@@ -3265,7 +3531,7 @@ async function documentOutgoingRelationships(reactorClient, args, subject) {
|
|
|
3265
3531
|
}
|
|
3266
3532
|
let result;
|
|
3267
3533
|
try {
|
|
3268
|
-
result = await reactorClient.getOutgoingRelationships(args.
|
|
3534
|
+
result = await reactorClient.getOutgoingRelationships(args.sourceIdOrSlug, args.relationshipType, view, paging);
|
|
3269
3535
|
} catch (error) {
|
|
3270
3536
|
throw new GraphQLError(`Failed to fetch outgoing relationships: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3271
3537
|
}
|
|
@@ -3293,7 +3559,7 @@ async function documentIncomingRelationships(reactorClient, args, subject) {
|
|
|
3293
3559
|
}
|
|
3294
3560
|
let result;
|
|
3295
3561
|
try {
|
|
3296
|
-
result = await reactorClient.getIncomingRelationships(args.
|
|
3562
|
+
result = await reactorClient.getIncomingRelationships(args.targetIdOrSlug, args.relationshipType, view, paging);
|
|
3297
3563
|
} catch (error) {
|
|
3298
3564
|
throw new GraphQLError(`Failed to fetch incoming relationships: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3299
3565
|
}
|
|
@@ -3303,6 +3569,54 @@ async function documentIncomingRelationships(reactorClient, args, subject) {
|
|
|
3303
3569
|
throw new GraphQLError(`Failed to convert incoming relationships to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3304
3570
|
}
|
|
3305
3571
|
}
|
|
3572
|
+
async function documentOutgoingRelationshipEdges(reactorClient, args, subject) {
|
|
3573
|
+
const view = toRelationshipViewFilter(args.view, subject);
|
|
3574
|
+
const paging = toRelationshipPagingOptions(args.paging);
|
|
3575
|
+
let result;
|
|
3576
|
+
try {
|
|
3577
|
+
result = await reactorClient.getOutgoingRelationshipEdges(args.sourceIdOrSlug, fromInputMaybe(args.relationshipType), view, paging);
|
|
3578
|
+
} catch (error) {
|
|
3579
|
+
throw new GraphQLError(`Failed to fetch outgoing relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3580
|
+
}
|
|
3581
|
+
try {
|
|
3582
|
+
return toDocumentRelationshipResultPage(result);
|
|
3583
|
+
} catch (error) {
|
|
3584
|
+
throw new GraphQLError(`Failed to convert outgoing relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
async function documentIncomingRelationshipEdges(reactorClient, args, subject) {
|
|
3588
|
+
const view = toRelationshipViewFilter(args.view, subject);
|
|
3589
|
+
const paging = toRelationshipPagingOptions(args.paging);
|
|
3590
|
+
let result;
|
|
3591
|
+
try {
|
|
3592
|
+
result = await reactorClient.getIncomingRelationshipEdges(args.targetIdOrSlug, fromInputMaybe(args.relationshipType), view, paging);
|
|
3593
|
+
} catch (error) {
|
|
3594
|
+
throw new GraphQLError(`Failed to fetch incoming relationship edges: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3595
|
+
}
|
|
3596
|
+
try {
|
|
3597
|
+
return toDocumentRelationshipResultPage(result);
|
|
3598
|
+
} catch (error) {
|
|
3599
|
+
throw new GraphQLError(`Failed to convert incoming relationship edges to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
function toRelationshipViewFilter(input, subject) {
|
|
3603
|
+
if (!input) return subject ? { subject } : void 0;
|
|
3604
|
+
return {
|
|
3605
|
+
subject,
|
|
3606
|
+
branch: fromInputMaybe(input.branch),
|
|
3607
|
+
scopes: toMutableArray(fromInputMaybe(input.scopes))
|
|
3608
|
+
};
|
|
3609
|
+
}
|
|
3610
|
+
function toRelationshipPagingOptions(input) {
|
|
3611
|
+
if (!input) return;
|
|
3612
|
+
const cursor = fromInputMaybe(input.cursor);
|
|
3613
|
+
const limit = fromInputMaybe(input.limit);
|
|
3614
|
+
if (!cursor && !limit) return;
|
|
3615
|
+
return {
|
|
3616
|
+
cursor: cursor || "",
|
|
3617
|
+
limit: limit || 10
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3306
3620
|
async function findDocuments(reactorClient, args, subject) {
|
|
3307
3621
|
let view = subject ? { subject } : void 0;
|
|
3308
3622
|
if (args.view) view = {
|
|
@@ -3335,13 +3649,42 @@ async function findDocuments(reactorClient, args, subject) {
|
|
|
3335
3649
|
throw new GraphQLError(`Failed to convert documents to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3336
3650
|
}
|
|
3337
3651
|
}
|
|
3338
|
-
|
|
3652
|
+
/**
|
|
3653
|
+
* The answer for a job the caller may not see, shaped as the reactor answers
|
|
3654
|
+
* an unknown one so that the two cannot be told apart.
|
|
3655
|
+
*/
|
|
3656
|
+
function unknownJob(jobId) {
|
|
3657
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3658
|
+
return {
|
|
3659
|
+
id: jobId,
|
|
3660
|
+
documentId: "",
|
|
3661
|
+
status: JobStatus.FAILED,
|
|
3662
|
+
createdAtUtcIso: now,
|
|
3663
|
+
completedAtUtcIso: now,
|
|
3664
|
+
error: {
|
|
3665
|
+
name: "Error",
|
|
3666
|
+
message: "Job not found",
|
|
3667
|
+
stack: ""
|
|
3668
|
+
},
|
|
3669
|
+
consistencyToken: {
|
|
3670
|
+
version: 1,
|
|
3671
|
+
createdAtUtcIso: now,
|
|
3672
|
+
coordinates: []
|
|
3673
|
+
},
|
|
3674
|
+
meta: {
|
|
3675
|
+
batchId: jobId,
|
|
3676
|
+
batchJobIds: [jobId]
|
|
3677
|
+
}
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3680
|
+
async function jobStatus(reactorClient, args, serves) {
|
|
3339
3681
|
let result;
|
|
3340
3682
|
try {
|
|
3341
3683
|
result = await reactorClient.getJobStatus(args.jobId);
|
|
3342
3684
|
} catch (error) {
|
|
3343
3685
|
throw new GraphQLError(`Failed to fetch job status: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3344
3686
|
}
|
|
3687
|
+
if (!await serves(result.documentId)) result = unknownJob(args.jobId);
|
|
3345
3688
|
try {
|
|
3346
3689
|
return toGqlJobInfo(result);
|
|
3347
3690
|
} catch (error) {
|
|
@@ -3379,7 +3722,7 @@ async function documentOperations(reactorClient, args, subject) {
|
|
|
3379
3722
|
}
|
|
3380
3723
|
let result;
|
|
3381
3724
|
try {
|
|
3382
|
-
result = await reactorClient.getOperations(args.filter.
|
|
3725
|
+
result = await reactorClient.getOperations(args.filter.documentIdOrSlug, view, operationFilter, paging);
|
|
3383
3726
|
} catch (error) {
|
|
3384
3727
|
throw new GraphQLError(`Failed to fetch document operations: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3385
3728
|
}
|
|
@@ -3411,7 +3754,7 @@ async function evaluateActions(reactorClient, args, subject) {
|
|
|
3411
3754
|
}));
|
|
3412
3755
|
let result;
|
|
3413
3756
|
try {
|
|
3414
|
-
result = await reactorClient.evaluateActions(args.
|
|
3757
|
+
result = await reactorClient.evaluateActions(args.documentIdOrSlug, branch, candidates, subject);
|
|
3415
3758
|
} catch (error) {
|
|
3416
3759
|
if (AuthEnforcementDisabledError.isError(error)) throw new AuthEvaluationUnsupportedError();
|
|
3417
3760
|
throw new GraphQLError(`Failed to evaluate actions: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -3424,51 +3767,54 @@ async function evaluateActions(reactorClient, args, subject) {
|
|
|
3424
3767
|
anyDenied: result.anyDenied
|
|
3425
3768
|
};
|
|
3426
3769
|
}
|
|
3427
|
-
async function createDocument(reactorClient, args, reactorDriveClient) {
|
|
3770
|
+
async function createDocument(reactorClient, args, reactorDriveClient, subject) {
|
|
3428
3771
|
if (!args.document || typeof args.document !== "object") throw new GraphQLError("Invalid document: must be an object");
|
|
3429
3772
|
const document = args.document;
|
|
3430
3773
|
if (!document.header || typeof document.header !== "object") throw new GraphQLError("Invalid document: missing or invalid header");
|
|
3431
|
-
const
|
|
3774
|
+
const parentIdOrSlug = fromInputMaybe(args.parentIdOrSlug);
|
|
3432
3775
|
let result;
|
|
3433
3776
|
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,
|
|
3777
|
+
if (parentIdOrSlug) {
|
|
3778
|
+
const parent = await reactorClient.get(parentIdOrSlug, subject && { subject });
|
|
3779
|
+
if (isDriveContainerType(parent.header.documentType)) result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parent.header.id, document);
|
|
3780
|
+
else result = await reactorClient.create(document, parentIdOrSlug);
|
|
3438
3781
|
} else result = await reactorClient.create(document);
|
|
3439
3782
|
} catch (error) {
|
|
3440
3783
|
throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3441
3784
|
}
|
|
3785
|
+
result = await readAsCaller(reactorClient, result, subject);
|
|
3442
3786
|
try {
|
|
3443
3787
|
return toGqlPhDocument(result);
|
|
3444
3788
|
} catch (error) {
|
|
3445
3789
|
throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3446
3790
|
}
|
|
3447
3791
|
}
|
|
3448
|
-
async function createEmptyDocument(reactorClient, args, reactorDriveClient) {
|
|
3449
|
-
const
|
|
3792
|
+
async function createEmptyDocument(reactorClient, args, reactorDriveClient, subject) {
|
|
3793
|
+
const parentIdOrSlug = fromInputMaybe(args.parentIdOrSlug);
|
|
3450
3794
|
const name = fromInputMaybe(args.name);
|
|
3451
3795
|
let result;
|
|
3452
3796
|
try {
|
|
3453
|
-
if (
|
|
3454
|
-
const parent = await reactorClient.get(
|
|
3797
|
+
if (parentIdOrSlug) {
|
|
3798
|
+
const parent = await reactorClient.get(parentIdOrSlug, subject && { subject });
|
|
3455
3799
|
if (isDriveContainerType(parent.header.documentType)) {
|
|
3456
|
-
const
|
|
3800
|
+
const module = await reactorClient.getDocumentModelModule(args.documentType);
|
|
3801
|
+
const document = withSignaturePolicy(module.utils.createDocument(), await reactorClient.getCreateSignaturePolicy());
|
|
3457
3802
|
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 });
|
|
3803
|
+
result = await pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient).addFile(parent.header.id, document);
|
|
3804
|
+
} else result = await reactorClient.createEmpty(args.documentType, { parentIdentifier: parentIdOrSlug });
|
|
3460
3805
|
} else result = await reactorClient.createEmpty(args.documentType, {});
|
|
3461
3806
|
} catch (error) {
|
|
3462
3807
|
throw new GraphQLError(`Failed to create empty document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3463
3808
|
}
|
|
3809
|
+
result = await readAsCaller(reactorClient, result, subject);
|
|
3464
3810
|
try {
|
|
3465
3811
|
return toGqlPhDocument(result);
|
|
3466
3812
|
} catch (error) {
|
|
3467
3813
|
throw new GraphQLError(`Failed to convert created document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3468
3814
|
}
|
|
3469
3815
|
}
|
|
3470
|
-
async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient) {
|
|
3471
|
-
const
|
|
3816
|
+
async function createDocumentWithInitialState(reactorClient, args, reactorDriveClient, subject) {
|
|
3817
|
+
const parentIdOrSlug = fromInputMaybe(args.parentIdOrSlug);
|
|
3472
3818
|
const name = fromInputMaybe(args.name);
|
|
3473
3819
|
const slug = fromInputMaybe(args.slug);
|
|
3474
3820
|
const preferredEditor = fromInputMaybe(args.preferredEditor);
|
|
@@ -3478,7 +3824,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
|
|
|
3478
3824
|
} catch (error) {
|
|
3479
3825
|
throw new GraphQLError(`Document model not found for type ${args.documentType}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3480
3826
|
}
|
|
3481
|
-
const document = module.utils.createDocument();
|
|
3827
|
+
const document = withSignaturePolicy(module.utils.createDocument(), await reactorClient.getCreateSignaturePolicy());
|
|
3482
3828
|
const allowedScopes = new Set(Object.keys(module.documentModel.global.specifications.at(-1)?.state ?? {}));
|
|
3483
3829
|
const state = document.state;
|
|
3484
3830
|
for (const [scope, scopeState] of Object.entries(args.initialState)) if (allowedScopes.has(scope) && scope in state) state[scope] = {
|
|
@@ -3492,22 +3838,22 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
|
|
|
3492
3838
|
preferredEditor
|
|
3493
3839
|
};
|
|
3494
3840
|
let result;
|
|
3495
|
-
if (
|
|
3841
|
+
if (parentIdOrSlug) {
|
|
3496
3842
|
let parent;
|
|
3497
3843
|
try {
|
|
3498
|
-
parent = await reactorClient.get(
|
|
3844
|
+
parent = await reactorClient.get(parentIdOrSlug, subject && { subject });
|
|
3499
3845
|
} catch (error) {
|
|
3500
3846
|
throw new GraphQLError(`Parent document not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3501
3847
|
}
|
|
3502
3848
|
if (isDriveContainerType(parent.header.documentType)) {
|
|
3503
3849
|
const driveClient = pickDriveClient(reactorClient, parent.header.documentType, reactorDriveClient);
|
|
3504
3850
|
try {
|
|
3505
|
-
result = await driveClient.addFile(
|
|
3851
|
+
result = await driveClient.addFile(parent.header.id, document);
|
|
3506
3852
|
} catch (error) {
|
|
3507
3853
|
throw new GraphQLError(`Failed to create document in drive: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3508
3854
|
}
|
|
3509
3855
|
} else try {
|
|
3510
|
-
result = await reactorClient.create(document,
|
|
3856
|
+
result = await reactorClient.create(document, parentIdOrSlug);
|
|
3511
3857
|
} catch (error) {
|
|
3512
3858
|
throw new GraphQLError(`Failed to create document with parent: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3513
3859
|
}
|
|
@@ -3516,6 +3862,7 @@ async function createDocumentWithInitialState(reactorClient, args, reactorDriveC
|
|
|
3516
3862
|
} catch (error) {
|
|
3517
3863
|
throw new GraphQLError(`Failed to create document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3518
3864
|
}
|
|
3865
|
+
result = await readAsCaller(reactorClient, result, subject);
|
|
3519
3866
|
try {
|
|
3520
3867
|
return toGqlPhDocument(result);
|
|
3521
3868
|
} catch (error) {
|
|
@@ -3532,12 +3879,12 @@ const DEFAULT_BRANCH = "main";
|
|
|
3532
3879
|
* the schema cannot express: a signature travels as one string, and verification
|
|
3533
3880
|
* reads it as the tuple it was.
|
|
3534
3881
|
*/
|
|
3535
|
-
async function execute(reactorClient, args) {
|
|
3882
|
+
async function execute(reactorClient, args, subject) {
|
|
3536
3883
|
const actions = toSubmittableActions(args.actions);
|
|
3537
3884
|
const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
|
|
3538
3885
|
let result;
|
|
3539
3886
|
try {
|
|
3540
|
-
result = await reactorClient.execute(args.
|
|
3887
|
+
result = await reactorClient.execute(args.documentIdOrSlug, branch, actions, void 0, subject);
|
|
3541
3888
|
} catch (error) {
|
|
3542
3889
|
throw new GraphQLError(`Failed to execute actions: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3543
3890
|
}
|
|
@@ -3559,13 +3906,13 @@ async function executeAsync(reactorClient, args) {
|
|
|
3559
3906
|
const branch = fromInputMaybe(args.branch) ?? DEFAULT_BRANCH;
|
|
3560
3907
|
let job;
|
|
3561
3908
|
try {
|
|
3562
|
-
job = await reactorClient.executeAsync(args.
|
|
3909
|
+
job = await reactorClient.executeAsync(args.documentIdOrSlug, branch, actions);
|
|
3563
3910
|
} catch (error) {
|
|
3564
3911
|
throw new GraphQLError(`Failed to submit actions: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3565
3912
|
}
|
|
3566
3913
|
return toGqlJobInfo(job);
|
|
3567
3914
|
}
|
|
3568
|
-
async function mutateDocument(reactorClient, args) {
|
|
3915
|
+
async function mutateDocument(reactorClient, args, subject) {
|
|
3569
3916
|
let validatedActions;
|
|
3570
3917
|
try {
|
|
3571
3918
|
validatedActions = validateActions(args.actions);
|
|
@@ -3576,7 +3923,7 @@ async function mutateDocument(reactorClient, args) {
|
|
|
3576
3923
|
const branch = args.view?.branch ?? "main";
|
|
3577
3924
|
let result;
|
|
3578
3925
|
try {
|
|
3579
|
-
result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions);
|
|
3926
|
+
result = await reactorClient.execute(args.documentIdentifier, branch, validatedActions, void 0, subject);
|
|
3580
3927
|
} catch (error) {
|
|
3581
3928
|
throw new GraphQLError(`Failed to mutate document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3582
3929
|
}
|
|
@@ -3603,71 +3950,96 @@ async function mutateDocumentAsync(reactorClient, args) {
|
|
|
3603
3950
|
}
|
|
3604
3951
|
return result.id;
|
|
3605
3952
|
}
|
|
3606
|
-
async function renameDocument(reactorClient, args, signal) {
|
|
3953
|
+
async function renameDocument(reactorClient, args, signal, subject) {
|
|
3607
3954
|
const branch = fromInputMaybe(args.branch);
|
|
3608
3955
|
let result;
|
|
3609
3956
|
try {
|
|
3610
|
-
result = await reactorClient.rename(args.
|
|
3957
|
+
result = await reactorClient.rename(args.documentIdOrSlug, args.name, branch, signal);
|
|
3611
3958
|
} catch (error) {
|
|
3612
3959
|
throw new GraphQLError(`Failed to rename document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3613
3960
|
}
|
|
3961
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3614
3962
|
try {
|
|
3615
3963
|
return toGqlPhDocument(result);
|
|
3616
3964
|
} catch (error) {
|
|
3617
3965
|
throw new GraphQLError(`Failed to convert renamed document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3618
3966
|
}
|
|
3619
3967
|
}
|
|
3620
|
-
async function setPreferredEditor(reactorClient, args, signal) {
|
|
3968
|
+
async function setPreferredEditor(reactorClient, args, signal, subject) {
|
|
3621
3969
|
const branch = fromInputMaybe(args.branch);
|
|
3622
3970
|
const preferredEditor = fromInputMaybe(args.preferredEditor) ?? null;
|
|
3623
3971
|
let result;
|
|
3624
3972
|
try {
|
|
3625
|
-
result = await reactorClient.setPreferredEditor(args.
|
|
3973
|
+
result = await reactorClient.setPreferredEditor(args.documentIdOrSlug, preferredEditor, branch, signal);
|
|
3626
3974
|
} catch (error) {
|
|
3627
3975
|
throw new GraphQLError(`Failed to set preferred editor: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3628
3976
|
}
|
|
3977
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3629
3978
|
try {
|
|
3630
3979
|
return toGqlPhDocument(result);
|
|
3631
3980
|
} catch (error) {
|
|
3632
3981
|
throw new GraphQLError(`Failed to convert updated document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3633
3982
|
}
|
|
3634
3983
|
}
|
|
3635
|
-
async function addRelationship(reactorClient, args) {
|
|
3984
|
+
async function addRelationship(reactorClient, args, subject) {
|
|
3636
3985
|
const branch = fromInputMaybe(args.branch);
|
|
3986
|
+
const metadata = fromInputMaybe(args.metadata);
|
|
3637
3987
|
let result;
|
|
3638
3988
|
try {
|
|
3639
|
-
result = await reactorClient.addRelationship(args.
|
|
3989
|
+
result = await reactorClient.addRelationship(args.sourceIdOrSlug, args.targetIdOrSlug, args.relationshipType, metadata, branch);
|
|
3640
3990
|
} catch (error) {
|
|
3641
3991
|
throw new GraphQLError(`Failed to add relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3642
3992
|
}
|
|
3993
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3994
|
+
try {
|
|
3995
|
+
return toGqlPhDocument(result);
|
|
3996
|
+
} catch (error) {
|
|
3997
|
+
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
async function updateRelationship(reactorClient, args, subject) {
|
|
4001
|
+
const branch = fromInputMaybe(args.branch);
|
|
4002
|
+
const metadata = fromInputMaybe(args.metadata) ?? null;
|
|
4003
|
+
let result;
|
|
4004
|
+
try {
|
|
4005
|
+
result = await reactorClient.updateRelationship(args.sourceIdOrSlug, args.targetIdOrSlug, args.relationshipType, metadata, branch);
|
|
4006
|
+
} catch (error) {
|
|
4007
|
+
throw new GraphQLError(`Failed to update relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4008
|
+
}
|
|
4009
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3643
4010
|
try {
|
|
3644
4011
|
return toGqlPhDocument(result);
|
|
3645
4012
|
} catch (error) {
|
|
3646
4013
|
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3647
4014
|
}
|
|
3648
4015
|
}
|
|
3649
|
-
async function removeRelationship(reactorClient, args) {
|
|
4016
|
+
async function removeRelationship(reactorClient, args, subject) {
|
|
3650
4017
|
const branch = fromInputMaybe(args.branch);
|
|
3651
4018
|
let result;
|
|
3652
4019
|
try {
|
|
3653
|
-
result = await reactorClient.removeRelationship(args.
|
|
4020
|
+
result = await reactorClient.removeRelationship(args.sourceIdOrSlug, args.targetIdOrSlug, args.relationshipType, branch);
|
|
3654
4021
|
} catch (error) {
|
|
3655
4022
|
throw new GraphQLError(`Failed to remove relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3656
4023
|
}
|
|
4024
|
+
result = await readAsCaller(reactorClient, result, subject, branch);
|
|
3657
4025
|
try {
|
|
3658
4026
|
return toGqlPhDocument(result);
|
|
3659
4027
|
} catch (error) {
|
|
3660
4028
|
throw new GraphQLError(`Failed to convert document to GraphQL: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3661
4029
|
}
|
|
3662
4030
|
}
|
|
3663
|
-
async function moveRelationship(reactorClient, args) {
|
|
4031
|
+
async function moveRelationship(reactorClient, args, subject) {
|
|
3664
4032
|
const branch = fromInputMaybe(args.branch);
|
|
3665
4033
|
let result;
|
|
3666
4034
|
try {
|
|
3667
|
-
result = await reactorClient.moveRelationship(args.
|
|
4035
|
+
result = await reactorClient.moveRelationship(args.sourceParentIdOrSlug, args.targetParentIdOrSlug, args.targetIdOrSlug, args.relationshipType, branch);
|
|
3668
4036
|
} catch (error) {
|
|
3669
4037
|
throw new GraphQLError(`Failed to move relationship: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3670
4038
|
}
|
|
4039
|
+
result = {
|
|
4040
|
+
source: await readAsCaller(reactorClient, result.source, subject, branch),
|
|
4041
|
+
target: await readAsCaller(reactorClient, result.target, subject, branch)
|
|
4042
|
+
};
|
|
3671
4043
|
try {
|
|
3672
4044
|
return {
|
|
3673
4045
|
source: toGqlPhDocument(result.source),
|
|
@@ -3680,9 +4052,12 @@ async function moveRelationship(reactorClient, args) {
|
|
|
3680
4052
|
async function deleteDocument(reactorClient, args, reactorDriveClient) {
|
|
3681
4053
|
const propagate = toReactorPropagationMode(args.propagate);
|
|
3682
4054
|
try {
|
|
3683
|
-
const driveParent = (await reactorClient.getIncomingRelationships(args.
|
|
3684
|
-
if (driveParent)
|
|
3685
|
-
|
|
4055
|
+
const driveParent = (await reactorClient.getIncomingRelationships(args.idOrSlug, "child")).results.find((p) => isDriveContainerType(p.header.documentType));
|
|
4056
|
+
if (driveParent) {
|
|
4057
|
+
const driveClient = pickDriveClient(reactorClient, driveParent.header.documentType, reactorDriveClient);
|
|
4058
|
+
const documentId = await reactorClient.resolveIdOrSlug(args.idOrSlug);
|
|
4059
|
+
await driveClient.removeNode(driveParent.header.id, documentId);
|
|
4060
|
+
} else await reactorClient.deleteDocument(args.idOrSlug, propagate);
|
|
3686
4061
|
return true;
|
|
3687
4062
|
} catch (error) {
|
|
3688
4063
|
throw new GraphQLError(`Failed to delete document: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -3690,9 +4065,9 @@ async function deleteDocument(reactorClient, args, reactorDriveClient) {
|
|
|
3690
4065
|
}
|
|
3691
4066
|
async function deleteDocuments(reactorClient, args) {
|
|
3692
4067
|
const propagate = toReactorPropagationMode(args.propagate);
|
|
3693
|
-
const
|
|
4068
|
+
const idsOrSlugs = [...args.idsOrSlugs];
|
|
3694
4069
|
try {
|
|
3695
|
-
await reactorClient.deleteDocuments(
|
|
4070
|
+
await reactorClient.deleteDocuments(idsOrSlugs, propagate);
|
|
3696
4071
|
return true;
|
|
3697
4072
|
} catch (error) {
|
|
3698
4073
|
throw new GraphQLError(`Failed to delete documents: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -3706,12 +4081,19 @@ async function deleteDocuments(reactorClient, args) {
|
|
|
3706
4081
|
* an unanswered question: the first authenticated subject to use it adopts it.
|
|
3707
4082
|
*/
|
|
3708
4083
|
async function touchChannel(syncManager, args, boundAddress) {
|
|
4084
|
+
const peer = readPeerManifest(args.input.manifest);
|
|
4085
|
+
let existing;
|
|
3709
4086
|
try {
|
|
4087
|
+
existing = syncManager.getById(args.input.id);
|
|
4088
|
+
} catch {}
|
|
4089
|
+
if (existing) {
|
|
4090
|
+
await syncManager.setPeerManifest(args.input.id, peer);
|
|
3710
4091
|
return {
|
|
3711
4092
|
success: true,
|
|
3712
|
-
ackOrdinal:
|
|
4093
|
+
ackOrdinal: existing.channel.inbox.ackOrdinal,
|
|
4094
|
+
manifest: syncManager.localManifest()
|
|
3713
4095
|
};
|
|
3714
|
-
}
|
|
4096
|
+
}
|
|
3715
4097
|
const filter = {
|
|
3716
4098
|
documentId: [...args.input.filter.documentId],
|
|
3717
4099
|
scope: [...args.input.filter.scope],
|
|
@@ -3725,13 +4107,45 @@ async function touchChannel(syncManager, args, boundAddress) {
|
|
|
3725
4107
|
await syncManager.add(args.input.name, DriveCollectionId.fromKey(args.input.collectionId), {
|
|
3726
4108
|
type: "polling",
|
|
3727
4109
|
parameters: {}
|
|
3728
|
-
}, filter, options, args.input.id);
|
|
4110
|
+
}, filter, options, args.input.id, peer);
|
|
3729
4111
|
} catch (error) {
|
|
3730
4112
|
throw new GraphQLError(`Failed to create channel: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3731
4113
|
}
|
|
3732
4114
|
return {
|
|
3733
4115
|
success: true,
|
|
3734
|
-
ackOrdinal: 0
|
|
4116
|
+
ackOrdinal: 0,
|
|
4117
|
+
manifest: syncManager.localManifest()
|
|
4118
|
+
};
|
|
4119
|
+
}
|
|
4120
|
+
async function syncHolds(syncManager, args) {
|
|
4121
|
+
return (await syncManager.listHolds({
|
|
4122
|
+
remoteName: args.remoteName ?? void 0,
|
|
4123
|
+
documentId: args.documentId ?? void 0
|
|
4124
|
+
})).map((hold) => ({
|
|
4125
|
+
...hold,
|
|
4126
|
+
reason: {
|
|
4127
|
+
...hold.reason,
|
|
4128
|
+
peerSupports: [...hold.reason.peerSupports]
|
|
4129
|
+
},
|
|
4130
|
+
heldAtUtcMs: String(hold.heldAtUtcMs)
|
|
4131
|
+
}));
|
|
4132
|
+
}
|
|
4133
|
+
function peerAgreement(syncManager, args) {
|
|
4134
|
+
const agreement = syncManager.agreement();
|
|
4135
|
+
const members = [...agreement.members([args.collectionId])].map(([remoteName, supports]) => ({
|
|
4136
|
+
remoteName,
|
|
4137
|
+
announced: "revision" in supports,
|
|
4138
|
+
protocols: supports.protocols,
|
|
4139
|
+
features: supports.features
|
|
4140
|
+
}));
|
|
4141
|
+
return {
|
|
4142
|
+
collectionId: args.collectionId,
|
|
4143
|
+
local: agreement.local(),
|
|
4144
|
+
members,
|
|
4145
|
+
limitedBy: Object.keys(agreement.basis().wanted).map((protocol) => ({
|
|
4146
|
+
protocol,
|
|
4147
|
+
remoteNames: agreement.limitedBy(args.collectionId, protocol)
|
|
4148
|
+
}))
|
|
3735
4149
|
};
|
|
3736
4150
|
}
|
|
3737
4151
|
/**
|
|
@@ -3786,7 +4200,8 @@ async function collectHeldSyncOperations(syncOps, gate, subject, signal) {
|
|
|
3786
4200
|
* client ordinal the switchboard has successfully applied, so the client
|
|
3787
4201
|
* can trim its own outbox)
|
|
3788
4202
|
*/
|
|
3789
|
-
function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set()) {
|
|
4203
|
+
function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new Set(), heldOpIds = /* @__PURE__ */ new Set(), gatedOpIds) {
|
|
4204
|
+
const isHeld = (syncOp) => heldOpIds.has(syncOp.id) || gatedOpIds !== void 0 && !gatedOpIds.has(syncOp.id);
|
|
3790
4205
|
let remote;
|
|
3791
4206
|
try {
|
|
3792
4207
|
remote = syncManager.getById(args.channelId);
|
|
@@ -3794,7 +4209,11 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3794
4209
|
throw new GraphQLError(`Channel not found: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3795
4210
|
}
|
|
3796
4211
|
remote.channel.notePoll();
|
|
3797
|
-
const
|
|
4212
|
+
const revisions = {
|
|
4213
|
+
manifestRevision: syncManager.localManifest().revision,
|
|
4214
|
+
peerManifestRevision: remote.meta.peer?.manifest?.revision ?? null
|
|
4215
|
+
};
|
|
4216
|
+
const deadLetters = remote.channel.deadLetter.items.filter((syncOp) => !forbiddenIds.has(syncOp.documentId) && !isHeld(syncOp)).map((syncOp) => ({
|
|
3798
4217
|
documentId: syncOp.documentId,
|
|
3799
4218
|
error: syncOp.error?.message ?? "Unknown error",
|
|
3800
4219
|
errorType: syncOperationErrorType(syncOp.error),
|
|
@@ -3829,7 +4248,8 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3829
4248
|
envelopes: [],
|
|
3830
4249
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
3831
4250
|
deadLetters,
|
|
3832
|
-
hasMore: false
|
|
4251
|
+
hasMore: false,
|
|
4252
|
+
...revisions
|
|
3833
4253
|
};
|
|
3834
4254
|
const sorted = [...operations].sort((a, b) => {
|
|
3835
4255
|
return (a.operations[0]?.context.ordinal ?? 0) - (b.operations[0]?.context.ordinal ?? 0);
|
|
@@ -3843,9 +4263,10 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3843
4263
|
hasMore = true;
|
|
3844
4264
|
break;
|
|
3845
4265
|
}
|
|
3846
|
-
if (
|
|
4266
|
+
if (isHeld(syncOp)) continue;
|
|
3847
4267
|
syncOp.deliveredCount ??= 0;
|
|
3848
4268
|
syncOp.emittedCount ??= 0;
|
|
4269
|
+
if (!forbiddenIds.has(syncOp.documentId)) while (syncOp.deliveredCount > 0 && syncOp.operations[syncOp.deliveredCount - 1].context.ordinal > args.outboxLatest) syncOp.deliveredCount -= 1;
|
|
3849
4270
|
while (syncOp.deliveredCount < syncOp.emittedCount && syncOp.operations[syncOp.deliveredCount].context.ordinal <= args.outboxLatest) syncOp.deliveredCount += 1;
|
|
3850
4271
|
const remaining = syncOp.operations.slice(syncOp.deliveredCount);
|
|
3851
4272
|
if (remaining.length === 0) continue;
|
|
@@ -3897,9 +4318,71 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3897
4318
|
envelopes,
|
|
3898
4319
|
ackOrdinal: remote.channel.inbox.ackOrdinal,
|
|
3899
4320
|
deadLetters,
|
|
3900
|
-
hasMore
|
|
4321
|
+
hasMore,
|
|
4322
|
+
...revisions
|
|
3901
4323
|
};
|
|
3902
4324
|
}
|
|
4325
|
+
const MARKER_REFUSAL = "marker";
|
|
4326
|
+
/**
|
|
4327
|
+
* The client's UNSUPPORTED_PROTOCOL refusals of polled rows. Each becomes a hold
|
|
4328
|
+
* for that client, as a pushed refusal does, rather than counting as delivered.
|
|
4329
|
+
* A refused marker is not a hold: {@link recordPollMarkerRefusals} takes it.
|
|
4330
|
+
* A kind this server does not know is ignored.
|
|
4331
|
+
*/
|
|
4332
|
+
function holdPollRefusals(syncManager, channelId, all) {
|
|
4333
|
+
const refusals = all?.filter((refusal) => refusal.kind === void 0 || refusal.kind === null);
|
|
4334
|
+
if (!refusals?.length) return;
|
|
4335
|
+
let remote;
|
|
4336
|
+
try {
|
|
4337
|
+
remote = syncManager.getById(channelId);
|
|
4338
|
+
} catch {
|
|
4339
|
+
return;
|
|
4340
|
+
}
|
|
4341
|
+
const refused = refusals.map((refusal) => {
|
|
4342
|
+
const syncOp = new SyncOperation(crypto.randomUUID(), "", [], remote.meta.name, refusal.documentId, [], refusal.branch, []);
|
|
4343
|
+
syncOp.failed(new ChannelError(ChannelErrorSource.Outbox, /* @__PURE__ */ new Error(`Refused by ${remote.meta.name}`), "UNSUPPORTED_PROTOCOL"));
|
|
4344
|
+
return syncOp;
|
|
4345
|
+
});
|
|
4346
|
+
remote.channel.deadLetter.add(...refused);
|
|
4347
|
+
}
|
|
4348
|
+
/**
|
|
4349
|
+
* The client's refusals of purge markers it polled. Persisted before the poll is
|
|
4350
|
+
* served; a failure fails the poll, and the client reports them again. The sync
|
|
4351
|
+
* manager keeps only refusals of markers this remote was owed.
|
|
4352
|
+
*/
|
|
4353
|
+
async function recordPollMarkerRefusals(syncManager, channelId, all) {
|
|
4354
|
+
const refusals = all?.filter((refusal) => refusal.kind === MARKER_REFUSAL);
|
|
4355
|
+
if (!refusals?.length) return;
|
|
4356
|
+
if (!supportsPurgeRefusals(syncManager)) return;
|
|
4357
|
+
let remote;
|
|
4358
|
+
try {
|
|
4359
|
+
remote = syncManager.getById(channelId);
|
|
4360
|
+
} catch {
|
|
4361
|
+
return;
|
|
4362
|
+
}
|
|
4363
|
+
try {
|
|
4364
|
+
await syncManager.recordPolledMarkerRefusals(remote.meta.name, refusals.map(({ documentId, branch }) => ({
|
|
4365
|
+
documentId,
|
|
4366
|
+
branch
|
|
4367
|
+
})));
|
|
4368
|
+
} catch (error) {
|
|
4369
|
+
throw new RefusalNotRecordedError(error);
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
/**
|
|
4373
|
+
* A poll naming no revision is from a client without peer agreement: it is
|
|
4374
|
+
* recorded silent, and what it can no longer run is held, before it is served.
|
|
4375
|
+
*/
|
|
4376
|
+
async function silenceUnversionedPoll(syncManager, channelId, manifestRevision) {
|
|
4377
|
+
if (typeof manifestRevision === "string") return;
|
|
4378
|
+
let remote;
|
|
4379
|
+
try {
|
|
4380
|
+
remote = syncManager.getById(channelId);
|
|
4381
|
+
} catch {
|
|
4382
|
+
return;
|
|
4383
|
+
}
|
|
4384
|
+
if (remote.meta.peer?.manifest) await syncManager.setPeerManifest(channelId, null);
|
|
4385
|
+
}
|
|
3903
4386
|
/**
|
|
3904
4387
|
* Receives sync envelopes pushed by a client and adds them to the
|
|
3905
4388
|
* appropriate channel inboxes.
|
|
@@ -3908,7 +4391,7 @@ function pollSyncEnvelopes(syncManager, args, forbiddenIds = /* @__PURE__ */ new
|
|
|
3908
4391
|
* It must be preserved because the inbox mailbox tracks applied ordinals
|
|
3909
4392
|
* and returns the highest one as `ackOrdinal` in pollSyncEnvelopes.
|
|
3910
4393
|
*/
|
|
3911
|
-
function pushSyncEnvelopes(syncManager, args) {
|
|
4394
|
+
async function pushSyncEnvelopes(syncManager, args) {
|
|
3912
4395
|
const remoteSyncOps = /* @__PURE__ */ new Map();
|
|
3913
4396
|
for (const envelope of args.envelopes) {
|
|
3914
4397
|
let remote;
|
|
@@ -3929,7 +4412,12 @@ function pushSyncEnvelopes(syncManager, args) {
|
|
|
3929
4412
|
for (const syncOp of consolidated) syncOp.jobDependencies = syncOp.jobDependencies.filter((dep) => validKeys.has(dep));
|
|
3930
4413
|
remote.channel.inbox.add(...consolidated);
|
|
3931
4414
|
}
|
|
3932
|
-
|
|
4415
|
+
try {
|
|
4416
|
+
await syncManager.receiptsStored?.([...remoteSyncOps.keys()].map((remote) => remote.meta.name));
|
|
4417
|
+
} catch (error) {
|
|
4418
|
+
throw new GraphQLError(`Failed to store received markers: ${error instanceof Error ? error.message : String(error)}`);
|
|
4419
|
+
}
|
|
4420
|
+
return true;
|
|
3933
4421
|
}
|
|
3934
4422
|
//#endregion
|
|
3935
4423
|
//#region src/graphql/document-model-subgraph.ts
|
|
@@ -3956,6 +4444,16 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
3956
4444
|
const documentName = getDocumentModelSchemaName(this.documentModel.documentModel.global);
|
|
3957
4445
|
return this.resolvers[`${documentName}Mutations`];
|
|
3958
4446
|
}
|
|
4447
|
+
/** Drops the items the host's own ACL refuses the caller. */
|
|
4448
|
+
async #readableItems(page, ctx) {
|
|
4449
|
+
if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
|
|
4450
|
+
const items = [];
|
|
4451
|
+
for (const item of page.items) if (await this.canReadDocument(item.id, ctx)) items.push(item);
|
|
4452
|
+
return {
|
|
4453
|
+
...page,
|
|
4454
|
+
items
|
|
4455
|
+
};
|
|
4456
|
+
}
|
|
3959
4457
|
/**
|
|
3960
4458
|
* Generate __resolveType functions for union types found in the document model schema.
|
|
3961
4459
|
* Parses the state schema to find union definitions and their member types,
|
|
@@ -4011,13 +4509,14 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4011
4509
|
Query: { [documentName]: () => ({}) },
|
|
4012
4510
|
[`${documentName}Queries`]: {
|
|
4013
4511
|
document: async (_, args, ctx) => {
|
|
4014
|
-
const
|
|
4015
|
-
|
|
4512
|
+
const idOrSlug = requireOneOf(args, "idOrSlug", "identifier");
|
|
4513
|
+
const { view } = args;
|
|
4514
|
+
if (!idOrSlug) throw new GraphQLError("Document identifier is required");
|
|
4016
4515
|
const result = await document(this.reactorClient, {
|
|
4017
|
-
|
|
4516
|
+
idOrSlug,
|
|
4018
4517
|
view
|
|
4019
|
-
});
|
|
4020
|
-
if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${
|
|
4518
|
+
}, this.viewSubject(ctx));
|
|
4519
|
+
if (result.document.documentType !== documentType) throw new GraphQLError(`Document with id ${idOrSlug} is not of type ${documentType}`);
|
|
4021
4520
|
await this.assertCanReadCanonical(result.document.id, ctx);
|
|
4022
4521
|
return result;
|
|
4023
4522
|
},
|
|
@@ -4026,17 +4525,8 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4026
4525
|
const result = await findDocuments(this.reactorClient, {
|
|
4027
4526
|
search: { type: documentType },
|
|
4028
4527
|
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;
|
|
4528
|
+
}, this.viewSubject(ctx));
|
|
4529
|
+
return this.#readableItems(result, ctx);
|
|
4040
4530
|
},
|
|
4041
4531
|
findDocuments: async (_, args, ctx) => {
|
|
4042
4532
|
const { search, view, paging } = args;
|
|
@@ -4047,98 +4537,94 @@ var DocumentModelSubgraph = class extends BaseSubgraph {
|
|
|
4047
4537
|
},
|
|
4048
4538
|
view,
|
|
4049
4539
|
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;
|
|
4540
|
+
}, this.viewSubject(ctx));
|
|
4541
|
+
return this.#readableItems(result, ctx);
|
|
4061
4542
|
},
|
|
4062
4543
|
documentOutgoingRelationships: async (_, args, ctx) => {
|
|
4544
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
4063
4545
|
const { relationshipType, view, paging } = args;
|
|
4064
|
-
const handle = await this.assertCanRead(
|
|
4546
|
+
const handle = await this.assertCanRead(sourceIdOrSlug, ctx);
|
|
4065
4547
|
const result = await documentOutgoingRelationships(this.reactorClient, {
|
|
4066
|
-
|
|
4548
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
4067
4549
|
relationshipType,
|
|
4068
4550
|
view,
|
|
4069
4551
|
paging
|
|
4070
|
-
});
|
|
4071
|
-
const
|
|
4552
|
+
}, this.viewSubject(ctx));
|
|
4553
|
+
const readable = await this.#readableItems(result, ctx);
|
|
4554
|
+
const filteredItems = readable.items.filter((item) => item.documentType === documentType);
|
|
4072
4555
|
return {
|
|
4073
|
-
...
|
|
4074
|
-
items: filteredItems
|
|
4075
|
-
totalCount: filteredItems.length
|
|
4556
|
+
...readable,
|
|
4557
|
+
items: filteredItems
|
|
4076
4558
|
};
|
|
4077
4559
|
},
|
|
4078
4560
|
documentIncomingRelationships: async (_, args, ctx) => {
|
|
4561
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
4079
4562
|
const { relationshipType, view, paging } = args;
|
|
4080
|
-
const handle = await this.assertCanRead(
|
|
4081
|
-
|
|
4082
|
-
|
|
4563
|
+
const handle = await this.assertCanRead(targetIdOrSlug, ctx);
|
|
4564
|
+
const result = await documentIncomingRelationships(this.reactorClient, {
|
|
4565
|
+
targetIdOrSlug: handle.fetchIdentifier,
|
|
4083
4566
|
relationshipType,
|
|
4084
4567
|
view,
|
|
4085
4568
|
paging
|
|
4086
|
-
});
|
|
4569
|
+
}, this.viewSubject(ctx));
|
|
4570
|
+
return this.#readableItems(result, ctx);
|
|
4087
4571
|
}
|
|
4088
4572
|
},
|
|
4089
4573
|
Mutation: { [documentName]: () => ({}) },
|
|
4090
4574
|
[`${documentName}Mutations`]: {
|
|
4091
4575
|
createDocument: async (_, args, ctx) => {
|
|
4092
4576
|
const { name, slug, preferredEditor, initialState } = args;
|
|
4093
|
-
let
|
|
4094
|
-
if (
|
|
4577
|
+
let parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
|
|
4578
|
+
if (parentIdOrSlug) parentIdOrSlug = (await this.assertCanWrite(parentIdOrSlug, ctx)).fetchIdentifier;
|
|
4095
4579
|
else this.assertCanCreate(ctx);
|
|
4096
4580
|
let createdDoc;
|
|
4097
4581
|
if (initialState || preferredEditor) createdDoc = await createDocumentWithInitialState(this.reactorClient, {
|
|
4098
4582
|
documentType,
|
|
4099
|
-
|
|
4583
|
+
parentIdOrSlug,
|
|
4100
4584
|
name,
|
|
4101
4585
|
slug,
|
|
4102
4586
|
preferredEditor,
|
|
4103
4587
|
initialState: initialState ?? {}
|
|
4104
|
-
}, this.graphqlManager.reactorDriveClient);
|
|
4588
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
4105
4589
|
else createdDoc = await createEmptyDocument(this.reactorClient, {
|
|
4106
4590
|
documentType,
|
|
4107
|
-
|
|
4591
|
+
parentIdOrSlug,
|
|
4108
4592
|
name
|
|
4109
|
-
}, this.graphqlManager.reactorDriveClient);
|
|
4593
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
4110
4594
|
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)]));
|
|
4595
|
+
if (!initialState && !preferredEditor && name && createdDoc.name !== name) return toGqlPhDocument(await this.reactorClient.execute(createdDoc.id, "main", [setName(name)], void 0, this.viewSubject(ctx)));
|
|
4112
4596
|
return createdDoc;
|
|
4113
4597
|
},
|
|
4114
4598
|
createEmptyDocument: async (_, args, ctx) => {
|
|
4115
|
-
let
|
|
4116
|
-
if (
|
|
4599
|
+
let parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
|
|
4600
|
+
if (parentIdOrSlug) parentIdOrSlug = (await this.assertCanWrite(parentIdOrSlug, ctx)).fetchIdentifier;
|
|
4117
4601
|
else this.assertCanCreate(ctx);
|
|
4118
4602
|
const result = await createEmptyDocument(this.reactorClient, {
|
|
4119
4603
|
documentType,
|
|
4120
|
-
|
|
4121
|
-
}, this.graphqlManager.reactorDriveClient);
|
|
4604
|
+
parentIdOrSlug
|
|
4605
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
4122
4606
|
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
4123
4607
|
return result;
|
|
4124
4608
|
},
|
|
4125
4609
|
...operations.reduce((mutations, op) => {
|
|
4126
4610
|
mutations[camelCase(op.name)] = async (_, args, ctx) => {
|
|
4127
|
-
const
|
|
4128
|
-
const
|
|
4129
|
-
|
|
4611
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "docId");
|
|
4612
|
+
const { input } = args;
|
|
4613
|
+
const effectiveDocId = (await this.assertCanExecuteOperation(documentIdOrSlug, op.name, ctx)).fetchIdentifier;
|
|
4614
|
+
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
4615
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
4131
4616
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
4132
4617
|
try {
|
|
4133
|
-
return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)]));
|
|
4618
|
+
return toGqlPhDocument(await this.reactorClient.execute(effectiveDocId, "main", [action(input)], void 0, this.viewSubject(ctx)));
|
|
4134
4619
|
} catch (error) {
|
|
4135
4620
|
throw new GraphQLError(error instanceof Error ? error.message : `Failed to ${op.name}`);
|
|
4136
4621
|
}
|
|
4137
4622
|
};
|
|
4138
4623
|
mutations[`${camelCase(op.name)}Async`] = async (_, args, ctx) => {
|
|
4139
|
-
const
|
|
4140
|
-
const
|
|
4141
|
-
|
|
4624
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "docId");
|
|
4625
|
+
const { input } = args;
|
|
4626
|
+
const effectiveDocId = (await this.assertCanExecuteOperation(documentIdOrSlug, op.name, ctx)).fetchIdentifier;
|
|
4627
|
+
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
4628
|
const action = this.documentModel.actions[camelCase(op.name)];
|
|
4143
4629
|
if (!action) throw new GraphQLError(`Action ${op.name} not found`);
|
|
4144
4630
|
try {
|
|
@@ -4162,7 +4648,7 @@ const DRIVE_ID_HEADER = "drive-id";
|
|
|
4162
4648
|
* in the ownership set yet); other create-shaped operations that may
|
|
4163
4649
|
* synthesize a drive must be added here too.
|
|
4164
4650
|
*/
|
|
4165
|
-
const CACHE_BYPASS_OPERATIONS = new Set(["createDocument", "createEmptyDocument"]);
|
|
4651
|
+
const CACHE_BYPASS_OPERATIONS = /* @__PURE__ */ new Set(["createDocument", "createEmptyDocument"]);
|
|
4166
4652
|
const driveIdMap = /* @__PURE__ */ new WeakMap();
|
|
4167
4653
|
/** Internal — only `graphql-manager.ts` should call this. */
|
|
4168
4654
|
function getRequestDriveId(request) {
|
|
@@ -4232,6 +4718,7 @@ function wrongShardResponse(driveId) {
|
|
|
4232
4718
|
* 421 response.
|
|
4233
4719
|
*/
|
|
4234
4720
|
var DriveOwnershipCache = class {
|
|
4721
|
+
reactorClient;
|
|
4235
4722
|
drives = /* @__PURE__ */ new Set();
|
|
4236
4723
|
constructor(reactorClient) {
|
|
4237
4724
|
this.reactorClient = reactorClient;
|
|
@@ -4363,8 +4850,8 @@ var GraphQLManager = class {
|
|
|
4363
4850
|
subgraphHandlerCache = /* @__PURE__ */ new Map();
|
|
4364
4851
|
/** subgraphPath → the http adapter handle of its mounted route. */
|
|
4365
4852
|
subgraphRouteHandles = /* @__PURE__ */ new Map();
|
|
4366
|
-
/**
|
|
4367
|
-
|
|
4853
|
+
/** Mounted SSE routes, keyed by path: the supergraph's and each subgraph's. */
|
|
4854
|
+
sseRouteHandles = /* @__PURE__ */ new Map();
|
|
4368
4855
|
/**
|
|
4369
4856
|
* Package name → the subgraph instances registered from it, keyed by
|
|
4370
4857
|
* subgraph name. Tracks the package association so that removed packages
|
|
@@ -4383,26 +4870,43 @@ var GraphQLManager = class {
|
|
|
4383
4870
|
authorizationService;
|
|
4384
4871
|
/** Route service in use: the injected one, or one built on first need. */
|
|
4385
4872
|
#fallbackRoutes;
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
this.
|
|
4405
|
-
this.
|
|
4873
|
+
path;
|
|
4874
|
+
httpServer;
|
|
4875
|
+
wsServer;
|
|
4876
|
+
reactorClient;
|
|
4877
|
+
relationalDb;
|
|
4878
|
+
analyticsStore;
|
|
4879
|
+
syncManager;
|
|
4880
|
+
logger;
|
|
4881
|
+
httpAdapter;
|
|
4882
|
+
gatewayAdapter;
|
|
4883
|
+
authService;
|
|
4884
|
+
documentPermissionService;
|
|
4885
|
+
featureFlags;
|
|
4886
|
+
port;
|
|
4887
|
+
syncServingGate;
|
|
4888
|
+
httpRoutes;
|
|
4889
|
+
attachments;
|
|
4890
|
+
constructor(options) {
|
|
4891
|
+
this.path = options.path;
|
|
4892
|
+
this.httpServer = options.httpServer;
|
|
4893
|
+
this.wsServer = options.wsServer;
|
|
4894
|
+
this.reactorClient = options.reactorClient;
|
|
4895
|
+
this.relationalDb = options.relationalDb;
|
|
4896
|
+
this.analyticsStore = options.analyticsStore;
|
|
4897
|
+
this.syncManager = options.syncManager;
|
|
4898
|
+
this.logger = options.logger;
|
|
4899
|
+
this.httpAdapter = options.httpAdapter;
|
|
4900
|
+
this.gatewayAdapter = options.gatewayAdapter;
|
|
4901
|
+
this.authService = options.authService;
|
|
4902
|
+
this.documentPermissionService = options.documentPermissionService;
|
|
4903
|
+
this.featureFlags = options.featureFlags ?? DefaultFeatureFlags;
|
|
4904
|
+
this.port = options.port ?? 4001;
|
|
4905
|
+
this.authorizationService = options.authorizationService;
|
|
4906
|
+
this.reactorDriveClient = options.reactorDriveClient;
|
|
4907
|
+
this.syncServingGate = options.syncServingGate;
|
|
4908
|
+
this.httpRoutes = options.httpRoutes;
|
|
4909
|
+
this.attachments = options.attachments;
|
|
4406
4910
|
this.driveOwnershipCache = new DriveOwnershipCache(this.reactorClient);
|
|
4407
4911
|
this.wsServer.setMaxListeners(0);
|
|
4408
4912
|
}
|
|
@@ -4425,23 +4929,25 @@ var GraphQLManager = class {
|
|
|
4425
4929
|
const matched = driveMatcher(url.pathname);
|
|
4426
4930
|
const driveIdOrSlug = matched ? matched.params.drive : void 0;
|
|
4427
4931
|
if (!driveIdOrSlug) return Response.json({ error: "Drive ID or slug is required" }, { status: 400 });
|
|
4932
|
+
const user = getAuthContext(request)?.user;
|
|
4428
4933
|
try {
|
|
4429
|
-
const driveDoc = await this.reactorClient.get(driveIdOrSlug);
|
|
4430
|
-
if (!await this.authorizationService.canRead(driveDoc.header.id,
|
|
4934
|
+
const driveDoc = await this.reactorClient.get(driveIdOrSlug, { subject: callerSubject(user) });
|
|
4935
|
+
if (!await this.authorizationService.canRead(driveDoc.header.id, user?.address)) {
|
|
4431
4936
|
this.logger.debug(`Drive read refused: ${driveIdOrSlug}`);
|
|
4432
4937
|
return Response.json({ error: "Drive not found" }, { status: 404 });
|
|
4433
4938
|
}
|
|
4434
4939
|
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`;
|
|
4940
|
+
const global = driveDoc.state.global;
|
|
4435
4941
|
return Response.json({
|
|
4436
4942
|
id: driveDoc.header.id,
|
|
4437
4943
|
slug: driveDoc.header.slug,
|
|
4438
4944
|
meta: driveDoc.header.meta,
|
|
4439
|
-
name:
|
|
4440
|
-
icon:
|
|
4945
|
+
name: global?.name || driveDoc.header.name,
|
|
4946
|
+
icon: global?.icon ?? void 0,
|
|
4441
4947
|
...graphqlEndpoint && { graphqlEndpoint }
|
|
4442
4948
|
});
|
|
4443
4949
|
} catch (error) {
|
|
4444
|
-
this.logger.debug(
|
|
4950
|
+
this.logger.debug("Drive not found: @drive (@error)", driveIdOrSlug, error);
|
|
4445
4951
|
return Response.json({ error: "Drive not found" }, { status: 404 });
|
|
4446
4952
|
}
|
|
4447
4953
|
};
|
|
@@ -4474,7 +4980,7 @@ var GraphQLManager = class {
|
|
|
4474
4980
|
for (const subgraph of coreSubgraphs) try {
|
|
4475
4981
|
await this.registerSubgraph(subgraph, supergraph, true);
|
|
4476
4982
|
} catch (error) {
|
|
4477
|
-
this.logger.error(
|
|
4983
|
+
this.logger.error("Failed to setup core subgraph @subgraph: @error", subgraph.name, error);
|
|
4478
4984
|
}
|
|
4479
4985
|
return this.#setupSubgraphs(this.coreSubgraphsMap);
|
|
4480
4986
|
}
|
|
@@ -4495,12 +5001,13 @@ var GraphQLManager = class {
|
|
|
4495
5001
|
path: this.path,
|
|
4496
5002
|
documentPermissionService: this.documentPermissionService,
|
|
4497
5003
|
authorizationService: this.authorizationService,
|
|
4498
|
-
syncServingGate: this.syncServingGate
|
|
5004
|
+
syncServingGate: this.syncServingGate,
|
|
5005
|
+
attachments: this.attachments
|
|
4499
5006
|
});
|
|
4500
5007
|
await this.#addSubgraphInstance(subgraphInstance, supergraph, false, DOCUMENT_MODEL_SUBGRAPH_SOURCE);
|
|
4501
5008
|
registeredNames.add(subgraphInstance.name);
|
|
4502
5009
|
} catch (error) {
|
|
4503
|
-
this.logger.error(
|
|
5010
|
+
this.logger.error("Failed to setup document model subgraph for @documentType: @error", documentModel.documentModel.global.id, error instanceof Error ? error.message : error);
|
|
4504
5011
|
this.logger.debug("@error", error);
|
|
4505
5012
|
}
|
|
4506
5013
|
}
|
|
@@ -4562,6 +5069,7 @@ var GraphQLManager = class {
|
|
|
4562
5069
|
routeHandle.dispose();
|
|
4563
5070
|
this.subgraphRouteHandles.delete(subgraphPath);
|
|
4564
5071
|
}
|
|
5072
|
+
this.#disposeSSERoute(subgraphPath);
|
|
4565
5073
|
const wsDisposer = this.subgraphWsDisposers.get(subgraphPath);
|
|
4566
5074
|
if (wsDisposer) {
|
|
4567
5075
|
this.subgraphWsDisposers.delete(subgraphPath);
|
|
@@ -4631,7 +5139,8 @@ var GraphQLManager = class {
|
|
|
4631
5139
|
path: this.path,
|
|
4632
5140
|
documentPermissionService: this.documentPermissionService,
|
|
4633
5141
|
authorizationService: this.authorizationService,
|
|
4634
|
-
syncServingGate: this.syncServingGate
|
|
5142
|
+
syncServingGate: this.syncServingGate,
|
|
5143
|
+
attachments: this.attachments
|
|
4635
5144
|
});
|
|
4636
5145
|
return this.#addSubgraphInstance(subgraphInstance, supergraph, core, packageName);
|
|
4637
5146
|
}
|
|
@@ -4658,7 +5167,7 @@ var GraphQLManager = class {
|
|
|
4658
5167
|
try {
|
|
4659
5168
|
routes?.disposeScope(packageName);
|
|
4660
5169
|
} catch (error) {
|
|
4661
|
-
this.logger.warn(`Failed to release HTTP routes for "
|
|
5170
|
+
this.logger.warn(`Failed to release HTTP routes for "@package": @error`, packageName, error);
|
|
4662
5171
|
}
|
|
4663
5172
|
}
|
|
4664
5173
|
/**
|
|
@@ -4758,15 +5267,12 @@ var GraphQLManager = class {
|
|
|
4758
5267
|
authService: this.authService,
|
|
4759
5268
|
requireAuthenticatedCaller: () => this.#requireAuthMiddleware !== void 0,
|
|
4760
5269
|
logger: this.logger,
|
|
4761
|
-
buildContext: (connectionParams, user) => {
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
if (user) context.user = user;
|
|
4768
|
-
return context;
|
|
4769
|
-
}
|
|
5270
|
+
buildContext: (connectionParams, user) => ({
|
|
5271
|
+
headers: connectionParams,
|
|
5272
|
+
db: this.relationalDb,
|
|
5273
|
+
...this.getAdditionalContextFields(),
|
|
5274
|
+
user
|
|
5275
|
+
})
|
|
4770
5276
|
});
|
|
4771
5277
|
}
|
|
4772
5278
|
setSupergraph(supergraph, subgraphs) {
|
|
@@ -4901,10 +5407,7 @@ var GraphQLManager = class {
|
|
|
4901
5407
|
const allSubgraphs = this.#getAllSubgraphs();
|
|
4902
5408
|
const modules = Array.from(allSubgraphs.values()).filter((subgraph) => subgraph.hasSubscriptions).map((subgraph) => this.#buildSubgraphSchemaModule(subgraph));
|
|
4903
5409
|
if (modules.length === 0) {
|
|
4904
|
-
|
|
4905
|
-
this.sseRouteHandle.dispose();
|
|
4906
|
-
this.sseRouteHandle = void 0;
|
|
4907
|
-
}
|
|
5410
|
+
this.#disposeSSERoute(superGraphPath);
|
|
4908
5411
|
return;
|
|
4909
5412
|
}
|
|
4910
5413
|
try {
|
|
@@ -4930,8 +5433,16 @@ var GraphQLManager = class {
|
|
|
4930
5433
|
contextFactory: this.#makeContextFactory()
|
|
4931
5434
|
});
|
|
4932
5435
|
const handler = this.#composeFetchMiddleware(rawHandler);
|
|
4933
|
-
|
|
4934
|
-
this.
|
|
5436
|
+
this.#disposeSSERoute(basePath);
|
|
5437
|
+
this.sseRouteHandles.set(ssePath, this.httpAdapter.mount(ssePath, handler, { exact: true }));
|
|
5438
|
+
}
|
|
5439
|
+
#disposeSSERoute(basePath) {
|
|
5440
|
+
const ssePath = basePath + "/stream";
|
|
5441
|
+
const handle = this.sseRouteHandles.get(ssePath);
|
|
5442
|
+
if (handle !== void 0) {
|
|
5443
|
+
handle.dispose();
|
|
5444
|
+
this.sseRouteHandles.delete(ssePath);
|
|
5445
|
+
}
|
|
4935
5446
|
}
|
|
4936
5447
|
/**
|
|
4937
5448
|
* Compose the request-level fetch middleware chain. Auth runs first
|
|
@@ -5215,7 +5726,6 @@ const DocumentModelStateDTO = z.object({
|
|
|
5215
5726
|
}).strip();
|
|
5216
5727
|
const DocumentModelResultPageDTO = z.object({
|
|
5217
5728
|
items: z.array(DocumentModelStateDTO),
|
|
5218
|
-
totalCount: z.number().int(),
|
|
5219
5729
|
hasNextPage: z.boolean(),
|
|
5220
5730
|
hasPreviousPage: z.boolean(),
|
|
5221
5731
|
cursor: z.string().nullable().optional()
|
|
@@ -5236,7 +5746,20 @@ const PHDocumentDTO = z.object({
|
|
|
5236
5746
|
}).strip();
|
|
5237
5747
|
const PHDocumentResultPageDTO = z.object({
|
|
5238
5748
|
items: z.array(PHDocumentDTO),
|
|
5239
|
-
|
|
5749
|
+
hasNextPage: z.boolean(),
|
|
5750
|
+
hasPreviousPage: z.boolean(),
|
|
5751
|
+
cursor: z.string().nullable().optional()
|
|
5752
|
+
}).strip();
|
|
5753
|
+
const DocumentRelationshipDTO = z.object({
|
|
5754
|
+
sourceId: z.string(),
|
|
5755
|
+
targetId: z.string(),
|
|
5756
|
+
relationshipType: z.string(),
|
|
5757
|
+
metadata: JSONObjectDTO.nullable().optional(),
|
|
5758
|
+
createdAt: DateTimeDTO,
|
|
5759
|
+
updatedAt: DateTimeDTO
|
|
5760
|
+
}).strip();
|
|
5761
|
+
const DocumentRelationshipResultPageDTO = z.object({
|
|
5762
|
+
items: z.array(DocumentRelationshipDTO),
|
|
5240
5763
|
hasNextPage: z.boolean(),
|
|
5241
5764
|
hasPreviousPage: z.boolean(),
|
|
5242
5765
|
cursor: z.string().nullable().optional()
|
|
@@ -5291,7 +5814,6 @@ const OperationDTO = z.object({
|
|
|
5291
5814
|
}).strip();
|
|
5292
5815
|
const OperationResultPageDTO = z.object({
|
|
5293
5816
|
items: z.array(OperationDTO),
|
|
5294
|
-
totalCount: z.number().int(),
|
|
5295
5817
|
hasNextPage: z.boolean(),
|
|
5296
5818
|
hasPreviousPage: z.boolean(),
|
|
5297
5819
|
cursor: z.string().nullable().optional()
|
|
@@ -5344,6 +5866,12 @@ const operationValidators = {
|
|
|
5344
5866
|
GetDocumentIncomingRelationships: (data) => {
|
|
5345
5867
|
if (data.documentIncomingRelationships) PHDocumentResultPageDTO.parse(data.documentIncomingRelationships);
|
|
5346
5868
|
},
|
|
5869
|
+
GetDocumentOutgoingRelationshipEdges: (data) => {
|
|
5870
|
+
if (data.documentOutgoingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentOutgoingRelationshipEdges);
|
|
5871
|
+
},
|
|
5872
|
+
GetDocumentIncomingRelationshipEdges: (data) => {
|
|
5873
|
+
if (data.documentIncomingRelationshipEdges) DocumentRelationshipResultPageDTO.parse(data.documentIncomingRelationshipEdges);
|
|
5874
|
+
},
|
|
5347
5875
|
FindDocuments: (data) => {
|
|
5348
5876
|
if (data.findDocuments) PHDocumentResultPageDTO.parse(data.findDocuments);
|
|
5349
5877
|
},
|
|
@@ -5377,6 +5905,9 @@ const operationValidators = {
|
|
|
5377
5905
|
AddRelationship: (data) => {
|
|
5378
5906
|
if (data.addRelationship) PHDocumentDTO.parse(data.addRelationship);
|
|
5379
5907
|
},
|
|
5908
|
+
UpdateRelationship: (data) => {
|
|
5909
|
+
if (data.updateRelationship) PHDocumentDTO.parse(data.updateRelationship);
|
|
5910
|
+
},
|
|
5380
5911
|
RemoveRelationship: (data) => {
|
|
5381
5912
|
if (data.removeRelationship) PHDocumentDTO.parse(data.removeRelationship);
|
|
5382
5913
|
},
|
|
@@ -5418,7 +5949,7 @@ function createReactorGraphQLClient(url, fetchImpl = fetch, headers) {
|
|
|
5418
5949
|
}
|
|
5419
5950
|
//#endregion
|
|
5420
5951
|
//#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";
|
|
5952
|
+
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
5953
|
//#endregion
|
|
5423
5954
|
//#region src/graphql/reactor/pubsub.ts
|
|
5424
5955
|
const pubSub = new PubSub();
|
|
@@ -5429,26 +5960,79 @@ const SUBSCRIPTION_TRIGGERS = {
|
|
|
5429
5960
|
DOCUMENT_CHANGES: "DOCUMENT_CHANGES",
|
|
5430
5961
|
JOB_CHANGES: "JOB_CHANGES"
|
|
5431
5962
|
};
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5963
|
+
function subjectKey(subject) {
|
|
5964
|
+
return `${subject.address ?? ""}|${subject.key ?? ""}`;
|
|
5965
|
+
}
|
|
5966
|
+
/**
|
|
5967
|
+
* The reactor's change feed, read once per distinct subject and shared by that
|
|
5968
|
+
* subject's GraphQL subscriptions. Each feed is read as its subject, so the
|
|
5969
|
+
* client withholds what that subject may not read; one feed read with no view
|
|
5970
|
+
* would gate every subscriber as the host.
|
|
5971
|
+
*/
|
|
5972
|
+
var DocumentChangeFeed = class {
|
|
5973
|
+
reactorClient;
|
|
5974
|
+
#pubSub = new PubSub();
|
|
5975
|
+
#feeds = /* @__PURE__ */ new Map();
|
|
5976
|
+
constructor(reactorClient) {
|
|
5977
|
+
this.reactorClient = reactorClient;
|
|
5978
|
+
}
|
|
5979
|
+
subscribe(subject) {
|
|
5980
|
+
const key = subjectKey(subject);
|
|
5981
|
+
const topic = `${SUBSCRIPTION_TRIGGERS.DOCUMENT_CHANGES}:${key}`;
|
|
5982
|
+
const iterator = this.#pubSub.asyncIterableIterator(topic);
|
|
5983
|
+
this.#acquire(key, topic, subject);
|
|
5984
|
+
let released = false;
|
|
5985
|
+
const release = () => {
|
|
5986
|
+
if (!released) {
|
|
5987
|
+
released = true;
|
|
5988
|
+
this.#release(key);
|
|
5989
|
+
}
|
|
5440
5990
|
};
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5991
|
+
return {
|
|
5992
|
+
next: () => iterator.next(),
|
|
5993
|
+
return: () => {
|
|
5994
|
+
release();
|
|
5995
|
+
return iterator.return();
|
|
5996
|
+
},
|
|
5997
|
+
throw: (error) => {
|
|
5998
|
+
release();
|
|
5999
|
+
return iterator.throw(error);
|
|
6000
|
+
},
|
|
6001
|
+
[Symbol.asyncIterator]() {
|
|
6002
|
+
return this;
|
|
6003
|
+
}
|
|
6004
|
+
};
|
|
6005
|
+
}
|
|
6006
|
+
#acquire(key, topic, subject) {
|
|
6007
|
+
const feed = this.#feeds.get(key);
|
|
6008
|
+
if (feed) {
|
|
6009
|
+
feed.subscribers++;
|
|
6010
|
+
return;
|
|
5449
6011
|
}
|
|
5450
|
-
|
|
5451
|
-
}
|
|
6012
|
+
const view = { subject };
|
|
6013
|
+
const unsubscribe = this.reactorClient.subscribe({}, (event) => {
|
|
6014
|
+
const payload = {
|
|
6015
|
+
documentChanges: event,
|
|
6016
|
+
search: {},
|
|
6017
|
+
view
|
|
6018
|
+
};
|
|
6019
|
+
this.#pubSub.publish(topic, payload);
|
|
6020
|
+
}, view);
|
|
6021
|
+
this.#feeds.set(key, {
|
|
6022
|
+
unsubscribe,
|
|
6023
|
+
subscribers: 1
|
|
6024
|
+
});
|
|
6025
|
+
}
|
|
6026
|
+
#release(key) {
|
|
6027
|
+
const feed = this.#feeds.get(key);
|
|
6028
|
+
if (!feed) return;
|
|
6029
|
+
feed.subscribers--;
|
|
6030
|
+
if (feed.subscribers === 0) {
|
|
6031
|
+
this.#feeds.delete(key);
|
|
6032
|
+
feed.unsubscribe();
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
};
|
|
5452
6036
|
const activeJobSubscriptions = /* @__PURE__ */ new Map();
|
|
5453
6037
|
function ensureJobSubscription(reactorClient, jobId) {
|
|
5454
6038
|
let subscription = activeJobSubscriptions.get(jobId);
|
|
@@ -5465,7 +6049,7 @@ function ensureJobSubscription(reactorClient, jobId) {
|
|
|
5465
6049
|
createdAt: jobInfo.createdAtUtcIso,
|
|
5466
6050
|
completedAt: jobInfo.completedAtUtcIso ?? null,
|
|
5467
6051
|
error: jobInfo.error?.message ?? null,
|
|
5468
|
-
result: jobInfo.result ??
|
|
6052
|
+
result: jobInfo.result ?? null
|
|
5469
6053
|
},
|
|
5470
6054
|
jobId,
|
|
5471
6055
|
documentId: jobInfo.documentId
|
|
@@ -5513,6 +6097,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5513
6097
|
}
|
|
5514
6098
|
name = "r";
|
|
5515
6099
|
hasSubscriptions = true;
|
|
6100
|
+
#documentChanges;
|
|
6101
|
+
get documentChanges() {
|
|
6102
|
+
this.#documentChanges ??= new DocumentChangeFeed(this.reactorClient);
|
|
6103
|
+
return this.#documentChanges;
|
|
6104
|
+
}
|
|
5516
6105
|
/**
|
|
5517
6106
|
* Check operation-level permissions for an array of actions.
|
|
5518
6107
|
* Delegates to base assertCanExecuteOperation for each action.
|
|
@@ -5543,6 +6132,38 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5543
6132
|
}
|
|
5544
6133
|
}
|
|
5545
6134
|
/**
|
|
6135
|
+
* Drops the relationship edges whose far-end document the caller cannot read.
|
|
6136
|
+
*
|
|
6137
|
+
* An edge names two documents, so returning it discloses the far end's id. The
|
|
6138
|
+
* document-shaped relationship queries already withhold far-end documents the
|
|
6139
|
+
* caller cannot read; the edge-shaped queries have to withhold the same ones,
|
|
6140
|
+
* or they become a way to enumerate around that check. Supreme admins read
|
|
6141
|
+
* everything, so the per-edge check is skipped for them.
|
|
6142
|
+
*
|
|
6143
|
+
* The cursor describes where to resume in the underlying unfiltered stream,
|
|
6144
|
+
* so it is left as it came. A filtered page is therefore often shorter than
|
|
6145
|
+
* the requested limit, and that is the stream continuing rather than a sign
|
|
6146
|
+
* it ended.
|
|
6147
|
+
*/
|
|
6148
|
+
async filterRelationshipEdges(page, farEnd, ctx) {
|
|
6149
|
+
if (this.authorizationService.isSupremeAdmin(ctx.user?.address)) return page;
|
|
6150
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
6151
|
+
const items = [];
|
|
6152
|
+
for (const edge of page.items) {
|
|
6153
|
+
const documentId = edge[farEnd];
|
|
6154
|
+
let canRead = decisions.get(documentId);
|
|
6155
|
+
if (canRead === void 0) {
|
|
6156
|
+
canRead = await this.canReadDocument(documentId, ctx);
|
|
6157
|
+
decisions.set(documentId, canRead);
|
|
6158
|
+
}
|
|
6159
|
+
if (canRead) items.push(edge);
|
|
6160
|
+
}
|
|
6161
|
+
return {
|
|
6162
|
+
...page,
|
|
6163
|
+
items
|
|
6164
|
+
};
|
|
6165
|
+
}
|
|
6166
|
+
/**
|
|
5546
6167
|
* Adds to `forbidden` the canonical document ids in `syncOps` that the caller
|
|
5547
6168
|
* cannot read, checking each distinct id once. Sync operation document ids are
|
|
5548
6169
|
* canonical (never slugs), so no resolution is needed.
|
|
@@ -5575,9 +6196,9 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5575
6196
|
* reports the liveness that keeps it from being reclaimed -- but they never
|
|
5576
6197
|
* adopt, since the push path has no drive check to clear the claimant.
|
|
5577
6198
|
*
|
|
5578
|
-
* Nothing is enforced or adopted without a serving gate
|
|
5579
|
-
*
|
|
5580
|
-
* belong to
|
|
6199
|
+
* Nothing is enforced or adopted without a serving gate: with no gate the
|
|
6200
|
+
* channel serves every subject alike, so there is no subject for it to
|
|
6201
|
+
* belong to.
|
|
5581
6202
|
*/
|
|
5582
6203
|
async #bindOrRefuseChannel(channelId, ctx, options = {}) {
|
|
5583
6204
|
if (!this.syncServingGate) return;
|
|
@@ -5607,7 +6228,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5607
6228
|
await this.assertCanReadCanonical(parent.id, ctx);
|
|
5608
6229
|
try {
|
|
5609
6230
|
const filter = {
|
|
5610
|
-
|
|
6231
|
+
documentIdOrSlug: parent.id,
|
|
5611
6232
|
branch: args.filter?.branch,
|
|
5612
6233
|
scopes: args.filter?.scopes,
|
|
5613
6234
|
actionTypes: args.filter?.actionTypes,
|
|
@@ -5637,10 +6258,11 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5637
6258
|
document: async (_parent, args, ctx) => {
|
|
5638
6259
|
this.logger.debug("document(@args)", args);
|
|
5639
6260
|
try {
|
|
5640
|
-
const
|
|
6261
|
+
const idOrSlug = requireOneOf(args, "idOrSlug", "identifier");
|
|
6262
|
+
const handle = await this.assertCanRead(idOrSlug, ctx);
|
|
5641
6263
|
return await document(this.reactorClient, {
|
|
5642
|
-
|
|
5643
|
-
|
|
6264
|
+
idOrSlug: handle.fetchIdentifier,
|
|
6265
|
+
view: args.view
|
|
5644
6266
|
}, this.viewSubject(ctx));
|
|
5645
6267
|
} catch (error) {
|
|
5646
6268
|
this.logger.error("Error in document: @Error", error);
|
|
@@ -5650,10 +6272,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5650
6272
|
documentOutgoingRelationships: async (_parent, args, ctx) => {
|
|
5651
6273
|
this.logger.debug("documentOutgoingRelationships(@args)", args);
|
|
5652
6274
|
try {
|
|
5653
|
-
const
|
|
6275
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6276
|
+
const handle = await this.assertCanRead(sourceIdOrSlug, ctx);
|
|
5654
6277
|
return await documentOutgoingRelationships(this.reactorClient, {
|
|
5655
|
-
|
|
5656
|
-
|
|
6278
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6279
|
+
relationshipType: args.relationshipType,
|
|
6280
|
+
view: args.view,
|
|
6281
|
+
paging: args.paging
|
|
5657
6282
|
}, this.viewSubject(ctx));
|
|
5658
6283
|
} catch (error) {
|
|
5659
6284
|
this.logger.error("Error in documentOutgoingRelationships: @Error", error);
|
|
@@ -5663,10 +6288,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5663
6288
|
documentIncomingRelationships: async (_parent, args, ctx) => {
|
|
5664
6289
|
this.logger.debug("documentIncomingRelationships(@args)", args);
|
|
5665
6290
|
try {
|
|
5666
|
-
const
|
|
6291
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6292
|
+
const handle = await this.assertCanRead(targetIdOrSlug, ctx);
|
|
5667
6293
|
const result = await documentIncomingRelationships(this.reactorClient, {
|
|
5668
|
-
|
|
5669
|
-
|
|
6294
|
+
targetIdOrSlug: handle.fetchIdentifier,
|
|
6295
|
+
relationshipType: args.relationshipType,
|
|
6296
|
+
view: args.view,
|
|
6297
|
+
paging: args.paging
|
|
5670
6298
|
}, this.viewSubject(ctx));
|
|
5671
6299
|
if (!this.authorizationService.isSupremeAdmin(ctx.user?.address)) {
|
|
5672
6300
|
const filteredItems = [];
|
|
@@ -5682,6 +6310,40 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5682
6310
|
throw error;
|
|
5683
6311
|
}
|
|
5684
6312
|
},
|
|
6313
|
+
documentOutgoingRelationshipEdges: async (_parent, args, ctx) => {
|
|
6314
|
+
this.logger.debug("documentOutgoingRelationshipEdges(@args)", args);
|
|
6315
|
+
try {
|
|
6316
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6317
|
+
const handle = await this.assertCanRead(sourceIdOrSlug, ctx);
|
|
6318
|
+
const result = await documentOutgoingRelationshipEdges(this.reactorClient, {
|
|
6319
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6320
|
+
relationshipType: args.relationshipType,
|
|
6321
|
+
view: args.view,
|
|
6322
|
+
paging: args.paging
|
|
6323
|
+
}, this.viewSubject(ctx));
|
|
6324
|
+
return await this.filterRelationshipEdges(result, "targetId", ctx);
|
|
6325
|
+
} catch (error) {
|
|
6326
|
+
this.logger.error("Error in documentOutgoingRelationshipEdges: @Error", error);
|
|
6327
|
+
throw error;
|
|
6328
|
+
}
|
|
6329
|
+
},
|
|
6330
|
+
documentIncomingRelationshipEdges: async (_parent, args, ctx) => {
|
|
6331
|
+
this.logger.debug("documentIncomingRelationshipEdges(@args)", args);
|
|
6332
|
+
try {
|
|
6333
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6334
|
+
const handle = await this.assertCanRead(targetIdOrSlug, ctx);
|
|
6335
|
+
const result = await documentIncomingRelationshipEdges(this.reactorClient, {
|
|
6336
|
+
targetIdOrSlug: handle.fetchIdentifier,
|
|
6337
|
+
relationshipType: args.relationshipType,
|
|
6338
|
+
view: args.view,
|
|
6339
|
+
paging: args.paging
|
|
6340
|
+
}, this.viewSubject(ctx));
|
|
6341
|
+
return await this.filterRelationshipEdges(result, "sourceId", ctx);
|
|
6342
|
+
} catch (error) {
|
|
6343
|
+
this.logger.error("Error in documentIncomingRelationshipEdges: @Error", error);
|
|
6344
|
+
throw error;
|
|
6345
|
+
}
|
|
6346
|
+
},
|
|
5685
6347
|
findDocuments: async (_parent, args, ctx) => {
|
|
5686
6348
|
this.logger.debug("findDocuments(@args)", args);
|
|
5687
6349
|
try {
|
|
@@ -5703,10 +6365,10 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5703
6365
|
throw error;
|
|
5704
6366
|
}
|
|
5705
6367
|
},
|
|
5706
|
-
jobStatus: async (_parent, args) => {
|
|
6368
|
+
jobStatus: async (_parent, args, ctx) => {
|
|
5707
6369
|
this.logger.debug("jobStatus(@args)", args);
|
|
5708
6370
|
try {
|
|
5709
|
-
return await jobStatus(this.reactorClient, args);
|
|
6371
|
+
return await jobStatus(this.reactorClient, args, (id) => this.servesDocument(id, ctx));
|
|
5710
6372
|
} catch (error) {
|
|
5711
6373
|
this.logger.error("Error in jobStatus: @Error", error);
|
|
5712
6374
|
throw error;
|
|
@@ -5715,32 +6377,63 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5715
6377
|
documentOperations: async (_parent, args, ctx) => {
|
|
5716
6378
|
this.logger.debug("documentOperations(@args)", args);
|
|
5717
6379
|
try {
|
|
5718
|
-
const
|
|
6380
|
+
const { documentIdOrSlug, documentId, ...filter } = args.filter;
|
|
6381
|
+
const handle = await this.assertCanRead(requireOneOf({
|
|
6382
|
+
documentIdOrSlug,
|
|
6383
|
+
documentId
|
|
6384
|
+
}, "documentIdOrSlug", "documentId"), ctx);
|
|
5719
6385
|
return await documentOperations(this.reactorClient, {
|
|
5720
|
-
...args,
|
|
5721
6386
|
filter: {
|
|
5722
|
-
...
|
|
5723
|
-
|
|
5724
|
-
}
|
|
6387
|
+
...filter,
|
|
6388
|
+
documentIdOrSlug: handle.fetchIdentifier
|
|
6389
|
+
},
|
|
6390
|
+
paging: args.paging
|
|
5725
6391
|
}, this.viewSubject(ctx));
|
|
5726
6392
|
} catch (error) {
|
|
5727
6393
|
this.logger.error("Error in documentOperations: @Error", error);
|
|
5728
6394
|
throw error;
|
|
5729
6395
|
}
|
|
5730
6396
|
},
|
|
6397
|
+
/**
|
|
6398
|
+
* Gated on reading the document, the same gate every other read here
|
|
6399
|
+
* carries: a verdict is derived from the document's policy, so a caller
|
|
6400
|
+
* who may not read the document may not preflight against it either.
|
|
6401
|
+
* That gate decides whether the query runs; the answer comes entirely
|
|
6402
|
+
* from the reactor's decision model.
|
|
6403
|
+
*/
|
|
5731
6404
|
evaluateActions: async (_parent, args, ctx) => {
|
|
5732
6405
|
this.logger.debug("evaluateActions(@args)", args);
|
|
5733
6406
|
try {
|
|
5734
|
-
const
|
|
6407
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6408
|
+
const handle = await this.assertCanRead(documentIdOrSlug, ctx);
|
|
5735
6409
|
return await evaluateActions(this.reactorClient, {
|
|
5736
|
-
|
|
5737
|
-
|
|
6410
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6411
|
+
branch: args.branch,
|
|
6412
|
+
candidates: args.candidates
|
|
5738
6413
|
}, this.viewSubject(ctx));
|
|
5739
6414
|
} catch (error) {
|
|
5740
6415
|
this.logger.error("Error in evaluateActions: @Error", error);
|
|
5741
6416
|
throw error;
|
|
5742
6417
|
}
|
|
5743
6418
|
},
|
|
6419
|
+
syncHolds: async (_parent, args, ctx) => {
|
|
6420
|
+
const address = ctx.user?.address;
|
|
6421
|
+
if (!this.authorizationService.isSupremeAdmin(address)) {
|
|
6422
|
+
let bound;
|
|
6423
|
+
try {
|
|
6424
|
+
bound = args.remoteName ? this.syncManager.getByName(args.remoteName).meta.options.boundAddress : void 0;
|
|
6425
|
+
} catch {
|
|
6426
|
+
bound = void 0;
|
|
6427
|
+
}
|
|
6428
|
+
if (address === void 0 || bound !== address) throw new ForbiddenError("to list sync holds");
|
|
6429
|
+
}
|
|
6430
|
+
return syncHolds(this.syncManager, args);
|
|
6431
|
+
},
|
|
6432
|
+
peerAgreement: (_parent, args, ctx) => {
|
|
6433
|
+
const address = ctx.user?.address;
|
|
6434
|
+
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");
|
|
6435
|
+
return peerAgreement(this.syncManager, args);
|
|
6436
|
+
},
|
|
5744
6437
|
pollSyncEnvelopes: async (_parent, args, ctx) => {
|
|
5745
6438
|
this.logger.debug("pollSyncEnvelopes(@args)", args);
|
|
5746
6439
|
try {
|
|
@@ -5754,19 +6447,18 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5754
6447
|
const isAdmin = this.authorizationService.isSupremeAdmin(ctx.user?.address);
|
|
5755
6448
|
if (!isAdmin) await this.assertCanReadCanonical(driveId, ctx);
|
|
5756
6449
|
await this.#bindOrRefuseChannel(args.channelId, ctx);
|
|
6450
|
+
holdPollRefusals(this.syncManager, args.channelId, args.refusals);
|
|
6451
|
+
await recordPollMarkerRefusals(this.syncManager, args.channelId, args.refusals);
|
|
6452
|
+
await silenceUnversionedPoll(this.syncManager, args.channelId, args.manifestRevision);
|
|
5757
6453
|
const forbiddenIds = /* @__PURE__ */ new Set();
|
|
5758
6454
|
if (!isAdmin) {
|
|
5759
6455
|
if (await this.authorizationService.canRead(driveId, void 0)) await this.#collectForbiddenDocuments(remote.channel.outbox.items, forbiddenIds, ctx);
|
|
5760
6456
|
await this.#collectForbiddenDocuments(remote.channel.deadLetter.items, forbiddenIds, ctx);
|
|
5761
6457
|
}
|
|
5762
|
-
const
|
|
5763
|
-
const
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
ackOrdinal,
|
|
5767
|
-
deadLetters,
|
|
5768
|
-
hasMore
|
|
5769
|
-
};
|
|
6458
|
+
const gate = this.syncServingGate;
|
|
6459
|
+
const gated = gate ? [...remote.channel.outbox.items, ...remote.channel.deadLetter.items] : void 0;
|
|
6460
|
+
const heldOpIds = gate && gated ? await collectHeldSyncOperations(gated, gate, this.viewSubject(ctx)) : /* @__PURE__ */ new Set();
|
|
6461
|
+
return pollSyncEnvelopes(this.syncManager, args, forbiddenIds, heldOpIds, gated && new Set(gated.map((syncOp) => syncOp.id)));
|
|
5770
6462
|
} catch (error) {
|
|
5771
6463
|
this.logger.error("Error in pollSyncEnvelopes(@args): @Error", args, error);
|
|
5772
6464
|
throw error;
|
|
@@ -5777,11 +6469,15 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5777
6469
|
createDocument: async (_parent, args, ctx) => {
|
|
5778
6470
|
this.logger.debug("createDocument(@args)", args);
|
|
5779
6471
|
try {
|
|
5780
|
-
|
|
5781
|
-
|
|
6472
|
+
const parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
|
|
6473
|
+
if (parentIdOrSlug) {
|
|
6474
|
+
const parent = await document(this.reactorClient, { idOrSlug: parentIdOrSlug });
|
|
5782
6475
|
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
5783
6476
|
} else this.assertCanCreate(ctx);
|
|
5784
|
-
const result = await createDocument(this.reactorClient,
|
|
6477
|
+
const result = await createDocument(this.reactorClient, {
|
|
6478
|
+
document: args.document,
|
|
6479
|
+
parentIdOrSlug
|
|
6480
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
5785
6481
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
5786
6482
|
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
5787
6483
|
return result;
|
|
@@ -5793,11 +6489,15 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5793
6489
|
createEmptyDocument: async (_parent, args, ctx) => {
|
|
5794
6490
|
this.logger.debug("createEmptyDocument(@args)", args);
|
|
5795
6491
|
try {
|
|
5796
|
-
|
|
5797
|
-
|
|
6492
|
+
const parentIdOrSlug = optionalOneOf(args, "parentIdOrSlug", "parentIdentifier");
|
|
6493
|
+
if (parentIdOrSlug) {
|
|
6494
|
+
const parent = await document(this.reactorClient, { idOrSlug: parentIdOrSlug });
|
|
5798
6495
|
await this.assertCanWriteCanonical(parent.document.id, ctx);
|
|
5799
6496
|
} else this.assertCanCreate(ctx);
|
|
5800
|
-
const result = await createEmptyDocument(this.reactorClient,
|
|
6497
|
+
const result = await createEmptyDocument(this.reactorClient, {
|
|
6498
|
+
documentType: args.documentType,
|
|
6499
|
+
parentIdOrSlug
|
|
6500
|
+
}, this.graphqlManager.reactorDriveClient, this.viewSubject(ctx));
|
|
5801
6501
|
if (result?.id && isDriveContainerType(result.documentType)) this.graphqlManager.driveOwnershipCache.add(result.id);
|
|
5802
6502
|
if (ctx.user?.address && result?.id) await this.documentPermissionService?.initializeDocumentProtection(result.id, ctx.user.address, this.authorizationService.config.defaultProtection);
|
|
5803
6503
|
return result;
|
|
@@ -5809,11 +6509,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5809
6509
|
execute: async (_parent, args, ctx) => {
|
|
5810
6510
|
this.logger.debug("execute(@args)", args);
|
|
5811
6511
|
try {
|
|
5812
|
-
const
|
|
6512
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6513
|
+
const handle = await this.assertCanExecuteOperations(documentIdOrSlug, args.actions, ctx);
|
|
5813
6514
|
return await execute(this.reactorClient, {
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
6515
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6516
|
+
actions: args.actions,
|
|
6517
|
+
branch: args.branch
|
|
6518
|
+
}, this.viewSubject(ctx));
|
|
5817
6519
|
} catch (error) {
|
|
5818
6520
|
this.logger.error("Error in execute(@args): @Error", args, error);
|
|
5819
6521
|
throw error;
|
|
@@ -5822,10 +6524,12 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5822
6524
|
executeAsync: async (_parent, args, ctx) => {
|
|
5823
6525
|
this.logger.debug("executeAsync(@args)", args);
|
|
5824
6526
|
try {
|
|
5825
|
-
const
|
|
6527
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6528
|
+
const handle = await this.assertCanExecuteOperations(documentIdOrSlug, args.actions, ctx);
|
|
5826
6529
|
return await executeAsync(this.reactorClient, {
|
|
5827
|
-
|
|
5828
|
-
|
|
6530
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6531
|
+
actions: args.actions,
|
|
6532
|
+
branch: args.branch
|
|
5829
6533
|
});
|
|
5830
6534
|
} catch (error) {
|
|
5831
6535
|
this.logger.error("Error in executeAsync(@args): @Error", args, error);
|
|
@@ -5839,7 +6543,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5839
6543
|
return await mutateDocument(this.reactorClient, {
|
|
5840
6544
|
...args,
|
|
5841
6545
|
documentIdentifier: handle.fetchIdentifier
|
|
5842
|
-
});
|
|
6546
|
+
}, this.viewSubject(ctx));
|
|
5843
6547
|
} catch (error) {
|
|
5844
6548
|
this.logger.error("Error in mutateDocument(@args): @Error", args, error);
|
|
5845
6549
|
throw error;
|
|
@@ -5861,11 +6565,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5861
6565
|
renameDocument: async (_parent, args, ctx) => {
|
|
5862
6566
|
this.logger.debug("renameDocument(@args)", args);
|
|
5863
6567
|
try {
|
|
5864
|
-
const
|
|
6568
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6569
|
+
const handle = await this.assertCanWrite(documentIdOrSlug, ctx);
|
|
5865
6570
|
return await renameDocument(this.reactorClient, {
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
6571
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6572
|
+
name: args.name,
|
|
6573
|
+
branch: args.branch
|
|
6574
|
+
}, void 0, this.viewSubject(ctx));
|
|
5869
6575
|
} catch (error) {
|
|
5870
6576
|
this.logger.error("Error in renameDocument(@args): @Error", args, error);
|
|
5871
6577
|
throw error;
|
|
@@ -5874,11 +6580,13 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5874
6580
|
setPreferredEditor: async (_parent, args, ctx) => {
|
|
5875
6581
|
this.logger.debug("setPreferredEditor(@args)", args);
|
|
5876
6582
|
try {
|
|
5877
|
-
const
|
|
6583
|
+
const documentIdOrSlug = requireOneOf(args, "documentIdOrSlug", "documentIdentifier");
|
|
6584
|
+
const handle = await this.assertCanWrite(documentIdOrSlug, ctx);
|
|
5878
6585
|
return await setPreferredEditor(this.reactorClient, {
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
6586
|
+
documentIdOrSlug: handle.fetchIdentifier,
|
|
6587
|
+
preferredEditor: args.preferredEditor,
|
|
6588
|
+
branch: args.branch
|
|
6589
|
+
}, void 0, this.viewSubject(ctx));
|
|
5882
6590
|
} catch (error) {
|
|
5883
6591
|
this.logger.error("Error in setPreferredEditor(@args): @Error", args, error);
|
|
5884
6592
|
throw error;
|
|
@@ -5887,24 +6595,51 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5887
6595
|
addRelationship: async (_parent, args, ctx) => {
|
|
5888
6596
|
this.logger.debug("addRelationship(@args)", args);
|
|
5889
6597
|
try {
|
|
5890
|
-
const
|
|
6598
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6599
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6600
|
+
const handle = await this.assertCanWrite(sourceIdOrSlug, ctx);
|
|
5891
6601
|
return await addRelationship(this.reactorClient, {
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
6602
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6603
|
+
targetIdOrSlug,
|
|
6604
|
+
relationshipType: args.relationshipType,
|
|
6605
|
+
metadata: args.metadata,
|
|
6606
|
+
branch: args.branch
|
|
6607
|
+
}, this.viewSubject(ctx));
|
|
5895
6608
|
} catch (error) {
|
|
5896
6609
|
this.logger.error("Error in addRelationship(@args): @Error", args, error);
|
|
5897
6610
|
throw error;
|
|
5898
6611
|
}
|
|
5899
6612
|
},
|
|
6613
|
+
updateRelationship: async (_parent, args, ctx) => {
|
|
6614
|
+
this.logger.debug("updateRelationship(@args)", args);
|
|
6615
|
+
try {
|
|
6616
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6617
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6618
|
+
const handle = await this.assertCanWrite(sourceIdOrSlug, ctx);
|
|
6619
|
+
return await updateRelationship(this.reactorClient, {
|
|
6620
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6621
|
+
targetIdOrSlug,
|
|
6622
|
+
relationshipType: args.relationshipType,
|
|
6623
|
+
metadata: args.metadata,
|
|
6624
|
+
branch: args.branch
|
|
6625
|
+
}, this.viewSubject(ctx));
|
|
6626
|
+
} catch (error) {
|
|
6627
|
+
this.logger.error("Error in updateRelationship(@args): @Error", args, error);
|
|
6628
|
+
throw error;
|
|
6629
|
+
}
|
|
6630
|
+
},
|
|
5900
6631
|
removeRelationship: async (_parent, args, ctx) => {
|
|
5901
6632
|
this.logger.debug("removeRelationship(@args)", args);
|
|
5902
6633
|
try {
|
|
5903
|
-
const
|
|
6634
|
+
const sourceIdOrSlug = requireOneOf(args, "sourceIdOrSlug", "sourceIdentifier");
|
|
6635
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6636
|
+
const handle = await this.assertCanWrite(sourceIdOrSlug, ctx);
|
|
5904
6637
|
return await removeRelationship(this.reactorClient, {
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
6638
|
+
sourceIdOrSlug: handle.fetchIdentifier,
|
|
6639
|
+
targetIdOrSlug,
|
|
6640
|
+
relationshipType: args.relationshipType,
|
|
6641
|
+
branch: args.branch
|
|
6642
|
+
}, this.viewSubject(ctx));
|
|
5908
6643
|
} catch (error) {
|
|
5909
6644
|
this.logger.error("Error in removeRelationship(@args): @Error", args, error);
|
|
5910
6645
|
throw error;
|
|
@@ -5913,26 +6648,31 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5913
6648
|
moveRelationship: async (_parent, args, ctx) => {
|
|
5914
6649
|
this.logger.debug("moveRelationship(@args)", args);
|
|
5915
6650
|
try {
|
|
5916
|
-
const
|
|
5917
|
-
const
|
|
6651
|
+
const sourceParentIdOrSlug = requireOneOf(args, "sourceParentIdOrSlug", "sourceParentIdentifier");
|
|
6652
|
+
const targetParentIdOrSlug = requireOneOf(args, "targetParentIdOrSlug", "targetParentIdentifier");
|
|
6653
|
+
const targetIdOrSlug = requireOneOf(args, "targetIdOrSlug", "targetIdentifier");
|
|
6654
|
+
const sourceHandle = await this.assertCanWrite(sourceParentIdOrSlug, ctx);
|
|
6655
|
+
const targetHandle = await this.assertCanWrite(targetParentIdOrSlug, ctx);
|
|
5918
6656
|
return await moveRelationship(this.reactorClient, {
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
6657
|
+
sourceParentIdOrSlug: sourceHandle.fetchIdentifier,
|
|
6658
|
+
targetParentIdOrSlug: targetHandle.fetchIdentifier,
|
|
6659
|
+
targetIdOrSlug,
|
|
6660
|
+
relationshipType: args.relationshipType,
|
|
6661
|
+
branch: args.branch
|
|
6662
|
+
}, this.viewSubject(ctx));
|
|
5923
6663
|
} catch (error) {
|
|
5924
|
-
this.logger.error("Error in moveRelationship(@args): @Error
|
|
6664
|
+
this.logger.error("Error in moveRelationship(@args): @Error", args, error);
|
|
5925
6665
|
throw error;
|
|
5926
6666
|
}
|
|
5927
6667
|
},
|
|
5928
6668
|
deleteDocument: async (_parent, args, ctx) => {
|
|
5929
6669
|
this.logger.debug("deleteDocument(@args)", args);
|
|
5930
6670
|
try {
|
|
5931
|
-
const
|
|
5932
|
-
const driveIdToInvalidate = await this.#resolveDriveId(
|
|
6671
|
+
const idOrSlug = (await this.assertCanWrite(requireOneOf(args, "idOrSlug", "identifier"), ctx)).fetchIdentifier;
|
|
6672
|
+
const driveIdToInvalidate = await this.#resolveDriveId(idOrSlug);
|
|
5933
6673
|
const result = await deleteDocument(this.reactorClient, {
|
|
5934
|
-
|
|
5935
|
-
|
|
6674
|
+
idOrSlug,
|
|
6675
|
+
propagate: args.propagate
|
|
5936
6676
|
}, this.graphqlManager.reactorDriveClient);
|
|
5937
6677
|
if (result && driveIdToInvalidate) this.graphqlManager.driveOwnershipCache.remove(driveIdToInvalidate);
|
|
5938
6678
|
return result;
|
|
@@ -5944,14 +6684,14 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
5944
6684
|
deleteDocuments: async (_parent, args, ctx) => {
|
|
5945
6685
|
this.logger.debug("deleteDocuments(@args)", args);
|
|
5946
6686
|
try {
|
|
5947
|
-
const
|
|
5948
|
-
for (const
|
|
5949
|
-
const handle = await this.assertCanWrite(
|
|
5950
|
-
|
|
6687
|
+
const idsOrSlugs = [];
|
|
6688
|
+
for (const idOrSlug of requireOneOf(args, "idsOrSlugs", "identifiers")) {
|
|
6689
|
+
const handle = await this.assertCanWrite(idOrSlug, ctx);
|
|
6690
|
+
idsOrSlugs.push(handle.fetchIdentifier);
|
|
5951
6691
|
}
|
|
5952
6692
|
return await deleteDocuments(this.reactorClient, {
|
|
5953
|
-
|
|
5954
|
-
|
|
6693
|
+
idsOrSlugs,
|
|
6694
|
+
propagate: args.propagate
|
|
5955
6695
|
});
|
|
5956
6696
|
} catch (error) {
|
|
5957
6697
|
this.logger.error("Error in deleteDocuments(@args): @Error", args, error);
|
|
@@ -6030,10 +6770,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
6030
6770
|
documentChanges: {
|
|
6031
6771
|
subscribe: (rootValue, args, ctx, info) => {
|
|
6032
6772
|
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) => {
|
|
6773
|
+
return withFilter(() => this.documentChanges.subscribe(this.viewSubject(ctx)), async (payload, filterArgs, filterCtx) => {
|
|
6037
6774
|
if (!payload) return false;
|
|
6038
6775
|
const search = {
|
|
6039
6776
|
type: filterArgs?.search?.type ?? void 0,
|
|
@@ -6060,6 +6797,7 @@ var ReactorSubgraph = class extends BaseSubgraph {
|
|
|
6060
6797
|
}, async (payload, filterArgs, filterCtx) => {
|
|
6061
6798
|
if (!payload || !filterArgs) return false;
|
|
6062
6799
|
if (!matchesJobFilter(payload, filterArgs)) return false;
|
|
6800
|
+
if (!await this.servesDocument(payload.documentId, filterCtx)) return false;
|
|
6063
6801
|
if (this.authorizationService.isSupremeAdmin(filterCtx?.user?.address)) return true;
|
|
6064
6802
|
if (!payload.documentId) return false;
|
|
6065
6803
|
return this.canReadDocument(payload.documentId, filterCtx);
|
|
@@ -6088,10 +6826,10 @@ const ADMIN_USERS = getAdminUsers();
|
|
|
6088
6826
|
//#endregion
|
|
6089
6827
|
//#region src/graphql/system/version.ts
|
|
6090
6828
|
function getVersion() {
|
|
6091
|
-
return "6.2.3-dev.
|
|
6829
|
+
return "6.2.3-dev.41";
|
|
6092
6830
|
}
|
|
6093
6831
|
function getGitHash() {
|
|
6094
|
-
return "
|
|
6832
|
+
return "84f56c54bfdb09f7789448e0c77f6c57454a47a6";
|
|
6095
6833
|
}
|
|
6096
6834
|
function getGitUrl() {
|
|
6097
6835
|
return buildTreeUrl(getGitHash());
|
|
@@ -6119,7 +6857,446 @@ var SystemSubgraph = class extends BaseSubgraph {
|
|
|
6119
6857
|
}) } };
|
|
6120
6858
|
};
|
|
6121
6859
|
//#endregion
|
|
6860
|
+
//#region src/packages/registry-cache.ts
|
|
6861
|
+
/** Entry files a registry package's node build is served from, CDN-relative. */
|
|
6862
|
+
const PACKAGE_ENTRIES = {
|
|
6863
|
+
documentModels: "node/document-models/index.mjs",
|
|
6864
|
+
subgraphs: "node/subgraphs/index.mjs",
|
|
6865
|
+
processors: "node/processors/index.mjs"
|
|
6866
|
+
};
|
|
6867
|
+
const REGISTRY_ENTRY_ABSENT = "ERR_REGISTRY_ENTRY_ABSENT";
|
|
6868
|
+
const MANIFEST_FILE = "manifest.json";
|
|
6869
|
+
function defaultRegistryCacheDir(cwd = process.cwd()) {
|
|
6870
|
+
return path.join(cwd, ".ph", "registry-packages");
|
|
6871
|
+
}
|
|
6872
|
+
const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
6873
|
+
const PACKAGE_NAME = /^(@[a-z0-9][-a-z0-9._]*\/)?[a-z0-9][-a-z0-9._]*$/i;
|
|
6874
|
+
function isValidPackageName(name) {
|
|
6875
|
+
return PACKAGE_NAME.test(name) && !name.includes("..") && name.length <= 214;
|
|
6876
|
+
}
|
|
6877
|
+
const MAX_FILE_BYTES = 16777216;
|
|
6878
|
+
const MAX_TOTAL_BYTES = 134217728;
|
|
6879
|
+
const MAX_FILES = 2e3;
|
|
6880
|
+
const MAX_IN_FLIGHT = 4;
|
|
6881
|
+
const FETCH_TIMEOUT_MS = 3e4;
|
|
6882
|
+
const DEAD_PID_GRACE_MS = 6e4;
|
|
6883
|
+
const ABANDONED_AFTER_MS = 36e5;
|
|
6884
|
+
const LEFTOVER = /\.(?:tmp|stale)-(\d+)-[0-9a-f]{8}$/;
|
|
6885
|
+
const STATIC_IMPORT = 1;
|
|
6886
|
+
const DYNAMIC_IMPORT = 2;
|
|
6887
|
+
var RegistryPackageCache = class {
|
|
6888
|
+
cacheDir;
|
|
6889
|
+
registryUrl;
|
|
6890
|
+
logger;
|
|
6891
|
+
fetchTimeoutMs;
|
|
6892
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
6893
|
+
verified = /* @__PURE__ */ new Map();
|
|
6894
|
+
constructor(options) {
|
|
6895
|
+
this.registryUrl = options.registryUrl.endsWith("/") ? options.registryUrl : `${options.registryUrl}/`;
|
|
6896
|
+
this.cacheDir = path.resolve(options.cacheDir ?? defaultRegistryCacheDir());
|
|
6897
|
+
this.logger = options.logger ?? childLogger(["reactor-api", "registry-cache"]);
|
|
6898
|
+
this.fetchTimeoutMs = options.fetchTimeoutMs ?? FETCH_TIMEOUT_MS;
|
|
6899
|
+
}
|
|
6900
|
+
/** Base URL every file of `name@version` is fetched under. */
|
|
6901
|
+
baseUrl(name, version) {
|
|
6902
|
+
return `${this.registryUrl}-/cdn/${name}@${version}/`;
|
|
6903
|
+
}
|
|
6904
|
+
entryDir(name, version) {
|
|
6905
|
+
assertCoordinate(name, version);
|
|
6906
|
+
const dir = path.join(this.cacheDir, `${name}@${version}`);
|
|
6907
|
+
assertWithin(dir, this.cacheDir);
|
|
6908
|
+
return dir;
|
|
6909
|
+
}
|
|
6910
|
+
/** Complete cached versions of `name`, newest-agnostic. */
|
|
6911
|
+
async cachedVersions(name) {
|
|
6912
|
+
if (!isValidPackageName(name)) return [];
|
|
6913
|
+
const parent = path.dirname(path.join(this.cacheDir, `${name}@0.0.0`));
|
|
6914
|
+
const leaf = path.basename(name);
|
|
6915
|
+
let names;
|
|
6916
|
+
try {
|
|
6917
|
+
names = await readdir(parent);
|
|
6918
|
+
} catch {
|
|
6919
|
+
return [];
|
|
6920
|
+
}
|
|
6921
|
+
const versions = [];
|
|
6922
|
+
for (const entry of names) {
|
|
6923
|
+
if (!entry.startsWith(`${leaf}@`)) continue;
|
|
6924
|
+
const version = entry.slice(leaf.length + 1);
|
|
6925
|
+
if (!EXACT_VERSION.test(version)) continue;
|
|
6926
|
+
try {
|
|
6927
|
+
await stat(path.join(parent, entry, MANIFEST_FILE));
|
|
6928
|
+
versions.push(version);
|
|
6929
|
+
} catch {}
|
|
6930
|
+
}
|
|
6931
|
+
return versions;
|
|
6932
|
+
}
|
|
6933
|
+
async ensurePackage(name, version) {
|
|
6934
|
+
const dir = this.entryDir(name, version);
|
|
6935
|
+
const done = this.verified.get(dir);
|
|
6936
|
+
if (done) return {
|
|
6937
|
+
...done,
|
|
6938
|
+
source: "cache"
|
|
6939
|
+
};
|
|
6940
|
+
const pending = this.inFlight.get(dir);
|
|
6941
|
+
if (pending) return pending;
|
|
6942
|
+
const started = this.resolveEntry(name, version, dir).finally(() => this.inFlight.delete(dir));
|
|
6943
|
+
this.inFlight.set(dir, started);
|
|
6944
|
+
return started;
|
|
6945
|
+
}
|
|
6946
|
+
async resolveEntry(name, version, dir) {
|
|
6947
|
+
let manifest = await this.verify(dir, name, version);
|
|
6948
|
+
let source = "cache";
|
|
6949
|
+
if (!manifest) {
|
|
6950
|
+
manifest = await this.fill(name, version, dir);
|
|
6951
|
+
source = "download";
|
|
6952
|
+
}
|
|
6953
|
+
await this.linkBareImports(manifest.bareImports);
|
|
6954
|
+
const entries = {};
|
|
6955
|
+
for (const [kind, rel] of Object.entries(manifest.entries)) entries[kind] = path.join(dir, rel);
|
|
6956
|
+
const result = {
|
|
6957
|
+
name,
|
|
6958
|
+
version,
|
|
6959
|
+
dir,
|
|
6960
|
+
entries,
|
|
6961
|
+
source
|
|
6962
|
+
};
|
|
6963
|
+
this.verified.set(dir, result);
|
|
6964
|
+
return result;
|
|
6965
|
+
}
|
|
6966
|
+
/** The manifest when every listed file is present and hashes to it. */
|
|
6967
|
+
async verify(dir, name, version) {
|
|
6968
|
+
let manifest;
|
|
6969
|
+
try {
|
|
6970
|
+
manifest = parseManifest(await readFile(path.join(dir, MANIFEST_FILE), "utf8"));
|
|
6971
|
+
} catch (error) {
|
|
6972
|
+
if (error.code === "ENOENT") return null;
|
|
6973
|
+
this.logger.warn("Unreadable cache manifest in @dir: @error", dir, error);
|
|
6974
|
+
await this.discard(dir);
|
|
6975
|
+
return null;
|
|
6976
|
+
}
|
|
6977
|
+
if (manifest.name !== name || manifest.version !== version || Object.values(manifest.entries).some((rel) => !(rel in manifest.files))) {
|
|
6978
|
+
this.logger.warn("Cache manifest in @dir names another package", dir);
|
|
6979
|
+
await this.discard(dir);
|
|
6980
|
+
return null;
|
|
6981
|
+
}
|
|
6982
|
+
for (const [rel, expected] of Object.entries(manifest.files)) {
|
|
6983
|
+
let actual;
|
|
6984
|
+
try {
|
|
6985
|
+
const file = path.join(dir, rel);
|
|
6986
|
+
assertWithin(file, dir);
|
|
6987
|
+
actual = sha256(await readFile(file));
|
|
6988
|
+
} catch {
|
|
6989
|
+
actual = null;
|
|
6990
|
+
}
|
|
6991
|
+
if (actual !== expected) {
|
|
6992
|
+
this.logger.warn("Cached @file of @package failed verification; fetching it again", rel, `${name}@${version}`);
|
|
6993
|
+
await this.discard(dir);
|
|
6994
|
+
return null;
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
return manifest;
|
|
6998
|
+
}
|
|
6999
|
+
async discard(dir) {
|
|
7000
|
+
const aside = `${dir}.stale-${process.pid}-${randomUUID().slice(0, 8)}`;
|
|
7001
|
+
try {
|
|
7002
|
+
await rename(dir, aside);
|
|
7003
|
+
} catch {
|
|
7004
|
+
return;
|
|
7005
|
+
}
|
|
7006
|
+
await rm(aside, {
|
|
7007
|
+
recursive: true,
|
|
7008
|
+
force: true
|
|
7009
|
+
});
|
|
7010
|
+
}
|
|
7011
|
+
async fill(name, version, dir) {
|
|
7012
|
+
const staging = `${dir}.tmp-${process.pid}-${randomUUID().slice(0, 8)}`;
|
|
7013
|
+
assertWithin(staging, this.cacheDir);
|
|
7014
|
+
await sweepLeftovers(path.dirname(dir));
|
|
7015
|
+
await mkdir(staging, { recursive: true });
|
|
7016
|
+
try {
|
|
7017
|
+
const manifest = await this.download(name, version, staging);
|
|
7018
|
+
await writeFile(path.join(staging, MANIFEST_FILE), JSON.stringify(manifest, null, 2));
|
|
7019
|
+
await chmod(staging, 493);
|
|
7020
|
+
try {
|
|
7021
|
+
await rename(staging, dir);
|
|
7022
|
+
} catch {
|
|
7023
|
+
const theirs = await this.verify(dir, name, version);
|
|
7024
|
+
if (theirs) return theirs;
|
|
7025
|
+
await this.discard(dir);
|
|
7026
|
+
await rename(staging, dir);
|
|
7027
|
+
}
|
|
7028
|
+
return manifest;
|
|
7029
|
+
} finally {
|
|
7030
|
+
await rm(staging, {
|
|
7031
|
+
recursive: true,
|
|
7032
|
+
force: true
|
|
7033
|
+
});
|
|
7034
|
+
}
|
|
7035
|
+
}
|
|
7036
|
+
async download(name, version, staging) {
|
|
7037
|
+
await init;
|
|
7038
|
+
const base = this.baseUrl(name, version);
|
|
7039
|
+
const files = {};
|
|
7040
|
+
const bare = /* @__PURE__ */ new Set();
|
|
7041
|
+
const entryRels = new Set(Object.values(PACKAGE_ENTRIES));
|
|
7042
|
+
const queue = [...entryRels];
|
|
7043
|
+
const seen = new Set(queue);
|
|
7044
|
+
let totalBytes = 0;
|
|
7045
|
+
const processOne = async (rel) => {
|
|
7046
|
+
const url = new URL(rel, base).href;
|
|
7047
|
+
const body = await this.fetchBounded(url, entryRels.has(rel));
|
|
7048
|
+
if (!body) return;
|
|
7049
|
+
totalBytes += body.byteLength;
|
|
7050
|
+
if (totalBytes > MAX_TOTAL_BYTES) throw new Error(`${name}@${version} exceeds the ${MAX_TOTAL_BYTES}-byte cache cap`);
|
|
7051
|
+
const target = path.join(staging, rel);
|
|
7052
|
+
assertWithin(target, staging);
|
|
7053
|
+
for (const dep of this.dependenciesOf(body.toString("utf8"), url, base)) {
|
|
7054
|
+
if (dep.kind === "bare") {
|
|
7055
|
+
bare.add(dep.specifier);
|
|
7056
|
+
continue;
|
|
7057
|
+
}
|
|
7058
|
+
if (seen.has(dep.rel)) continue;
|
|
7059
|
+
if (seen.size >= MAX_FILES) throw new Error(`${name}@${version} imports more than ${MAX_FILES} files`);
|
|
7060
|
+
seen.add(dep.rel);
|
|
7061
|
+
queue.push(dep.rel);
|
|
7062
|
+
}
|
|
7063
|
+
files[rel] = sha256(body);
|
|
7064
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
7065
|
+
await writeFile(target, body);
|
|
7066
|
+
};
|
|
7067
|
+
const active = /* @__PURE__ */ new Set();
|
|
7068
|
+
let failure;
|
|
7069
|
+
while ((queue.length > 0 || active.size > 0) && failure === void 0) {
|
|
7070
|
+
while (queue.length > 0 && active.size < MAX_IN_FLIGHT) {
|
|
7071
|
+
const task = processOne(queue.shift()).then(() => {
|
|
7072
|
+
active.delete(task);
|
|
7073
|
+
}, (error) => {
|
|
7074
|
+
active.delete(task);
|
|
7075
|
+
failure ??= error instanceof Error ? error : new Error(String(error));
|
|
7076
|
+
});
|
|
7077
|
+
active.add(task);
|
|
7078
|
+
}
|
|
7079
|
+
if (active.size > 0) await Promise.race(active);
|
|
7080
|
+
}
|
|
7081
|
+
if (failure !== void 0) {
|
|
7082
|
+
await Promise.allSettled(active);
|
|
7083
|
+
throw failure;
|
|
7084
|
+
}
|
|
7085
|
+
const entries = {};
|
|
7086
|
+
for (const [kind, rel] of Object.entries(PACKAGE_ENTRIES)) if (rel in files) entries[kind] = rel;
|
|
7087
|
+
if (Object.keys(entries).length === 0) throw new Error(`${name}@${version} serves no document models, subgraphs or processors`);
|
|
7088
|
+
return {
|
|
7089
|
+
name,
|
|
7090
|
+
version,
|
|
7091
|
+
entries,
|
|
7092
|
+
files: Object.fromEntries(Object.entries(files).sort(([a], [b]) => a.localeCompare(b))),
|
|
7093
|
+
bareImports: [...bare].sort()
|
|
7094
|
+
};
|
|
7095
|
+
}
|
|
7096
|
+
dependenciesOf(source, url, base) {
|
|
7097
|
+
const [imports] = parse$1(source, url);
|
|
7098
|
+
const deps = [];
|
|
7099
|
+
for (const imp of imports) {
|
|
7100
|
+
const kind = imp.t;
|
|
7101
|
+
if (kind !== STATIC_IMPORT && kind !== DYNAMIC_IMPORT) continue;
|
|
7102
|
+
const specifier = imp.n;
|
|
7103
|
+
if (specifier === void 0) {
|
|
7104
|
+
if (kind === DYNAMIC_IMPORT) this.logger.warn("Non-literal dynamic import in @url is not cached: @expr", url, source.slice(imp.s, imp.e));
|
|
7105
|
+
continue;
|
|
7106
|
+
}
|
|
7107
|
+
if (isBuiltin(specifier)) continue;
|
|
7108
|
+
if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || /^https?:\/\//i.test(specifier)) {
|
|
7109
|
+
deps.push({
|
|
7110
|
+
kind: "file",
|
|
7111
|
+
rel: relativeTo(base, specifier, url)
|
|
7112
|
+
});
|
|
7113
|
+
continue;
|
|
7114
|
+
}
|
|
7115
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(specifier)) throw new Error(`${url} imports an unsupported URL: ${specifier}`);
|
|
7116
|
+
deps.push({
|
|
7117
|
+
kind: "bare",
|
|
7118
|
+
specifier
|
|
7119
|
+
});
|
|
7120
|
+
}
|
|
7121
|
+
return deps;
|
|
7122
|
+
}
|
|
7123
|
+
async fetchBounded(url, optional = false) {
|
|
7124
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(this.fetchTimeoutMs) });
|
|
7125
|
+
if (optional && response.status === 404) {
|
|
7126
|
+
await response.body?.cancel();
|
|
7127
|
+
return null;
|
|
7128
|
+
}
|
|
7129
|
+
if (!response.ok) {
|
|
7130
|
+
await response.body?.cancel();
|
|
7131
|
+
throw new Error(`${url} responded ${response.status}`);
|
|
7132
|
+
}
|
|
7133
|
+
const declared = response.headers.get("content-length");
|
|
7134
|
+
if (declared && Number(declared) > MAX_FILE_BYTES) {
|
|
7135
|
+
await response.body?.cancel();
|
|
7136
|
+
throw new Error(`${url} exceeds the ${MAX_FILE_BYTES}-byte file cap`);
|
|
7137
|
+
}
|
|
7138
|
+
if (!response.body) return Buffer.from(await response.arrayBuffer());
|
|
7139
|
+
const reader = response.body.getReader();
|
|
7140
|
+
const chunks = [];
|
|
7141
|
+
let total = 0;
|
|
7142
|
+
for (;;) {
|
|
7143
|
+
const { done, value } = await reader.read();
|
|
7144
|
+
if (done) break;
|
|
7145
|
+
total += value.byteLength;
|
|
7146
|
+
if (total > MAX_FILE_BYTES) {
|
|
7147
|
+
await reader.cancel();
|
|
7148
|
+
throw new Error(`${url} exceeds the ${MAX_FILE_BYTES}-byte file cap`);
|
|
7149
|
+
}
|
|
7150
|
+
chunks.push(value);
|
|
7151
|
+
}
|
|
7152
|
+
return Buffer.concat(chunks);
|
|
7153
|
+
}
|
|
7154
|
+
async linkBareImports(specifiers) {
|
|
7155
|
+
const outsideUrl = pathToFileURL(path.join(path.dirname(this.cacheDir), "/")).href;
|
|
7156
|
+
const linkRoot = path.join(this.cacheDir, "node_modules");
|
|
7157
|
+
for (const specifier of specifiers) {
|
|
7158
|
+
const pkg = packageNameOf(specifier);
|
|
7159
|
+
if (!pkg || !isValidPackageName(pkg)) continue;
|
|
7160
|
+
const link = path.join(linkRoot, pkg);
|
|
7161
|
+
assertWithin(link, linkRoot);
|
|
7162
|
+
if (packageRoot(specifier, pkg, outsideUrl)) {
|
|
7163
|
+
await removeLink(link);
|
|
7164
|
+
continue;
|
|
7165
|
+
}
|
|
7166
|
+
const fallback = packageRoot(specifier, pkg, import.meta.url);
|
|
7167
|
+
if (!fallback) {
|
|
7168
|
+
this.logger.warn("Registry package import @specifier resolves from neither the project nor reactor-api", specifier);
|
|
7169
|
+
continue;
|
|
7170
|
+
}
|
|
7171
|
+
try {
|
|
7172
|
+
await ensureLink(link, await realpath(fallback));
|
|
7173
|
+
} catch (error) {
|
|
7174
|
+
this.logger.warn("Could not link @pkg for cached registry packages: @error", pkg, error);
|
|
7175
|
+
}
|
|
7176
|
+
}
|
|
7177
|
+
}
|
|
7178
|
+
};
|
|
7179
|
+
function sha256(data) {
|
|
7180
|
+
return createHash("sha256").update(data).digest("hex");
|
|
7181
|
+
}
|
|
7182
|
+
function assertCoordinate(name, version) {
|
|
7183
|
+
if (!isValidPackageName(name)) throw new Error(`Invalid registry package name: ${name}`);
|
|
7184
|
+
if (!EXACT_VERSION.test(version)) throw new Error(`Invalid registry package version: ${version}`);
|
|
7185
|
+
}
|
|
7186
|
+
function assertWithin(target, root) {
|
|
7187
|
+
const resolvedRoot = path.resolve(root);
|
|
7188
|
+
const resolved = path.resolve(target);
|
|
7189
|
+
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + path.sep)) throw new Error(`Path escapes the registry cache: ${target}`);
|
|
7190
|
+
}
|
|
7191
|
+
function relativeTo(base, specifier, fromUrl) {
|
|
7192
|
+
const resolved = new URL(specifier, fromUrl);
|
|
7193
|
+
resolved.search = "";
|
|
7194
|
+
resolved.hash = "";
|
|
7195
|
+
if (!resolved.href.startsWith(base)) throw new Error(`${fromUrl} imports ${specifier}, which is outside the package`);
|
|
7196
|
+
const rel = decodeURIComponent(resolved.href.slice(base.length));
|
|
7197
|
+
if (rel === "" || rel === "manifest.json" || rel.includes("\\") || rel.includes("\0") || rel.split("/").some((s) => s === "" || s === "." || s === "..")) throw new Error(`${fromUrl} imports an unusable path: ${specifier}`);
|
|
7198
|
+
return rel;
|
|
7199
|
+
}
|
|
7200
|
+
function parseManifest(raw) {
|
|
7201
|
+
const value = JSON.parse(raw);
|
|
7202
|
+
if (typeof value.name !== "string" || typeof value.version !== "string" || typeof value.entries !== "object" || value.entries === null || typeof value.files !== "object" || value.files === null || !Array.isArray(value.bareImports)) throw new Error("malformed manifest");
|
|
7203
|
+
return value;
|
|
7204
|
+
}
|
|
7205
|
+
function packageNameOf(specifier) {
|
|
7206
|
+
const parts = specifier.split("/");
|
|
7207
|
+
if (specifier.startsWith("@")) return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
|
|
7208
|
+
return parts[0] || null;
|
|
7209
|
+
}
|
|
7210
|
+
function packageRoot(specifier, pkg, base) {
|
|
7211
|
+
let found;
|
|
7212
|
+
try {
|
|
7213
|
+
found = findPackageJSON(specifier, base);
|
|
7214
|
+
} catch {
|
|
7215
|
+
return null;
|
|
7216
|
+
}
|
|
7217
|
+
if (!found) return null;
|
|
7218
|
+
let dir = path.dirname(found);
|
|
7219
|
+
for (let i = 0; i < 16; i++) {
|
|
7220
|
+
if (dir.endsWith(`${path.sep}node_modules${path.sep}${pkg}`)) return dir;
|
|
7221
|
+
if (readPackageName(path.join(dir, "package.json")) === pkg) return dir;
|
|
7222
|
+
const parent = path.dirname(dir);
|
|
7223
|
+
if (parent === dir) break;
|
|
7224
|
+
dir = parent;
|
|
7225
|
+
}
|
|
7226
|
+
return null;
|
|
7227
|
+
}
|
|
7228
|
+
function readPackageName(file) {
|
|
7229
|
+
try {
|
|
7230
|
+
return JSON.parse(readFileSync(file, "utf8")).name;
|
|
7231
|
+
} catch {
|
|
7232
|
+
return;
|
|
7233
|
+
}
|
|
7234
|
+
}
|
|
7235
|
+
async function sweepLeftovers(parent) {
|
|
7236
|
+
let names;
|
|
7237
|
+
try {
|
|
7238
|
+
names = await readdir(parent);
|
|
7239
|
+
} catch {
|
|
7240
|
+
return;
|
|
7241
|
+
}
|
|
7242
|
+
const now = Date.now();
|
|
7243
|
+
for (const name of names) {
|
|
7244
|
+
const match = LEFTOVER.exec(name);
|
|
7245
|
+
if (!match) continue;
|
|
7246
|
+
const pid = Number(match[1]);
|
|
7247
|
+
if (pid === process.pid) continue;
|
|
7248
|
+
const full = path.join(parent, name);
|
|
7249
|
+
let age;
|
|
7250
|
+
try {
|
|
7251
|
+
age = now - (await lstat(full)).mtimeMs;
|
|
7252
|
+
} catch {
|
|
7253
|
+
continue;
|
|
7254
|
+
}
|
|
7255
|
+
if (age > ABANDONED_AFTER_MS || age > DEAD_PID_GRACE_MS && !isAlive(pid)) await rm(full, {
|
|
7256
|
+
recursive: true,
|
|
7257
|
+
force: true
|
|
7258
|
+
});
|
|
7259
|
+
}
|
|
7260
|
+
}
|
|
7261
|
+
function isAlive(pid) {
|
|
7262
|
+
try {
|
|
7263
|
+
process.kill(pid, 0);
|
|
7264
|
+
return true;
|
|
7265
|
+
} catch (error) {
|
|
7266
|
+
return error.code === "EPERM";
|
|
7267
|
+
}
|
|
7268
|
+
}
|
|
7269
|
+
async function ensureLink(link, target) {
|
|
7270
|
+
try {
|
|
7271
|
+
if (await readlink(link) === target) return;
|
|
7272
|
+
} catch {}
|
|
7273
|
+
await mkdir(path.dirname(link), { recursive: true });
|
|
7274
|
+
await sweepLeftovers(path.dirname(link));
|
|
7275
|
+
const temp = `${link}.tmp-${process.pid}-${randomUUID().slice(0, 8)}`;
|
|
7276
|
+
await symlink(target, temp, "junction");
|
|
7277
|
+
try {
|
|
7278
|
+
await rename(temp, link);
|
|
7279
|
+
} catch (error) {
|
|
7280
|
+
await rm(temp, { force: true });
|
|
7281
|
+
throw error;
|
|
7282
|
+
}
|
|
7283
|
+
}
|
|
7284
|
+
async function removeLink(link) {
|
|
7285
|
+
try {
|
|
7286
|
+
await readlink(link);
|
|
7287
|
+
} catch {
|
|
7288
|
+
return;
|
|
7289
|
+
}
|
|
7290
|
+
await rm(link, { force: true });
|
|
7291
|
+
}
|
|
7292
|
+
//#endregion
|
|
6122
7293
|
//#region src/packages/http-loader.ts
|
|
7294
|
+
function piecesBaseUrl(registryUrl, packageName, version) {
|
|
7295
|
+
return `${registryUrl.endsWith("/") ? registryUrl : `${registryUrl}/`}-/cdn/${packageName}@${version}/node/pieces/`;
|
|
7296
|
+
}
|
|
7297
|
+
function documentModelsOf(module) {
|
|
7298
|
+
return Object.values(module).filter((m) => m !== null && typeof m === "object" && "documentModel" in m && m.documentModel !== null);
|
|
7299
|
+
}
|
|
6123
7300
|
/**
|
|
6124
7301
|
* Extract subgraph classes from the imported subgraphs/index.mjs module shape.
|
|
6125
7302
|
*
|
|
@@ -6146,8 +7323,14 @@ var HttpPackageLoader = class {
|
|
|
6146
7323
|
logger = childLogger(["reactor-api", "http-loader"]);
|
|
6147
7324
|
name = "HttpPackageLoader";
|
|
6148
7325
|
documentModelLoader;
|
|
7326
|
+
cache;
|
|
7327
|
+
versions = /* @__PURE__ */ new Map();
|
|
6149
7328
|
constructor(options) {
|
|
6150
7329
|
this.registryUrl = options.registryUrl.endsWith("/") ? options.registryUrl : `${options.registryUrl}/`;
|
|
7330
|
+
this.cache = new RegistryPackageCache({
|
|
7331
|
+
registryUrl: this.registryUrl,
|
|
7332
|
+
cacheDir: options.cacheDir
|
|
7333
|
+
});
|
|
6151
7334
|
this.documentModelLoader = new HttpDocumentModelLoader(this);
|
|
6152
7335
|
}
|
|
6153
7336
|
/**
|
|
@@ -6179,38 +7362,95 @@ var HttpPackageLoader = class {
|
|
|
6179
7362
|
tag: void 0
|
|
6180
7363
|
};
|
|
6181
7364
|
}
|
|
7365
|
+
resolveVersion(packageSpec) {
|
|
7366
|
+
const known = this.versions.get(packageSpec);
|
|
7367
|
+
if (known) return known;
|
|
7368
|
+
const lookup = this.lookUpVersion(packageSpec).then((version) => {
|
|
7369
|
+
if (!version) this.versions.delete(packageSpec);
|
|
7370
|
+
return version;
|
|
7371
|
+
});
|
|
7372
|
+
this.versions.set(packageSpec, lookup);
|
|
7373
|
+
return lookup;
|
|
7374
|
+
}
|
|
7375
|
+
async lookUpVersion(packageSpec) {
|
|
7376
|
+
const { name, tag } = this.parsePackageSpec(packageSpec);
|
|
7377
|
+
if (tag && EXACT_VERSION.test(tag)) return tag;
|
|
7378
|
+
const version = await this.packageVersion(packageSpec);
|
|
7379
|
+
if (version) return version;
|
|
7380
|
+
const cached = await this.cache.cachedVersions(name);
|
|
7381
|
+
if (cached.length === 1) {
|
|
7382
|
+
this.logger.warn("Could not resolve a version for @package; using cached @version", packageSpec, cached[0]);
|
|
7383
|
+
return cached[0];
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
/** Caches the pinned version of `packageSpec` on disk. */
|
|
7387
|
+
async cachePackage(packageSpec) {
|
|
7388
|
+
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
7389
|
+
if (!isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
|
|
7390
|
+
const version = await this.resolveVersion(packageSpec);
|
|
7391
|
+
if (!version) throw new Error(`No exact version found for ${packageSpec}`);
|
|
7392
|
+
const cached = await this.cache.ensurePackage(packageName, version);
|
|
7393
|
+
this.logger.verbose(`Package ${packageName}@${version} at ${cached.dir} (${cached.source})`);
|
|
7394
|
+
return cached;
|
|
7395
|
+
}
|
|
7396
|
+
/** One entry module of the package, plus the cached file it came from. */
|
|
7397
|
+
async importEntry(packageSpec, kind) {
|
|
7398
|
+
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
7399
|
+
if (!isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
|
|
7400
|
+
const version = await this.resolveVersion(packageSpec);
|
|
7401
|
+
let cached;
|
|
7402
|
+
if (version) try {
|
|
7403
|
+
cached = await this.cache.ensurePackage(packageName, version);
|
|
7404
|
+
} catch (error) {
|
|
7405
|
+
this.logger.warn("Could not cache @package, importing it over HTTP: @error", packageSpec, error);
|
|
7406
|
+
}
|
|
7407
|
+
if (cached) {
|
|
7408
|
+
const filePath = cached.entries[kind];
|
|
7409
|
+
if (!filePath) throw Object.assign(/* @__PURE__ */ new Error(`${packageName}@${cached.version} serves no ${kind}`), { code: REGISTRY_ENTRY_ABSENT });
|
|
7410
|
+
return {
|
|
7411
|
+
module: await import(
|
|
7412
|
+
/* @vite-ignore */
|
|
7413
|
+
pathToFileURL(filePath).href
|
|
7414
|
+
),
|
|
7415
|
+
filePath
|
|
7416
|
+
};
|
|
7417
|
+
}
|
|
7418
|
+
const pinned = version ? `${packageName}@${version}` : packageSpec;
|
|
7419
|
+
const url = `${this.registryUrl}-/cdn/${pinned}/${PACKAGE_ENTRIES[kind]}`;
|
|
7420
|
+
this.logger.verbose(`Importing ${kind} from: ${url}`);
|
|
7421
|
+
return { module: await import(
|
|
7422
|
+
/* @vite-ignore */
|
|
7423
|
+
url
|
|
7424
|
+
) };
|
|
7425
|
+
}
|
|
7426
|
+
importDocumentModels(packageSpec) {
|
|
7427
|
+
return this.importEntry(packageSpec, "documentModels");
|
|
7428
|
+
}
|
|
6182
7429
|
async loadDocumentModels(packageSpec) {
|
|
6183
7430
|
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
6184
|
-
|
|
6185
|
-
const
|
|
6186
|
-
this.logger.verbose(`Importing document-models from: ${url}`);
|
|
6187
|
-
const module = await import(url);
|
|
6188
|
-
const models = Object.values(module).filter((m) => m !== null && typeof m === "object" && "documentModel" in m && m.documentModel !== null);
|
|
7431
|
+
const { module } = await this.importDocumentModels(packageSpec);
|
|
7432
|
+
const models = documentModelsOf(module);
|
|
6189
7433
|
this.logger.verbose(`Loaded ${models.length} document models from ${packageName}`);
|
|
6190
7434
|
return models;
|
|
6191
7435
|
}
|
|
6192
7436
|
async loadUpgradeManifests(packageSpec) {
|
|
6193
7437
|
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
6194
|
-
|
|
6195
|
-
const manifests = extractUpgradeManifests(
|
|
7438
|
+
const { module } = await this.importDocumentModels(packageSpec);
|
|
7439
|
+
const manifests = extractUpgradeManifests(module);
|
|
6196
7440
|
if (manifests.length > 0) this.logger.verbose(`Loaded ${manifests.length} upgrade manifests from ${packageName}`);
|
|
6197
7441
|
return manifests;
|
|
6198
7442
|
}
|
|
6199
7443
|
async loadSubgraphs(packageSpec) {
|
|
6200
7444
|
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
6201
|
-
|
|
6202
|
-
const
|
|
6203
|
-
this.logger.verbose(`Importing subgraphs from: ${url}`);
|
|
6204
|
-
const subgraphs = extractSubgraphsFromModule(await import(url));
|
|
7445
|
+
const { module } = await this.importEntry(packageSpec, "subgraphs");
|
|
7446
|
+
const subgraphs = extractSubgraphsFromModule(module);
|
|
6205
7447
|
this.logger.verbose(`Loaded ${subgraphs.length} subgraphs from ${packageName}`);
|
|
6206
7448
|
return subgraphs;
|
|
6207
7449
|
}
|
|
6208
7450
|
async loadProcessors(packageSpec) {
|
|
6209
7451
|
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
6210
|
-
|
|
6211
|
-
const
|
|
6212
|
-
this.logger.verbose(`Importing processors from: ${url}`);
|
|
6213
|
-
const factory = (await import(url)).processorFactory;
|
|
7452
|
+
const { module } = await this.importEntry(packageSpec, "processors");
|
|
7453
|
+
const factory = module.processorFactory;
|
|
6214
7454
|
if (factory && typeof factory === "function") {
|
|
6215
7455
|
this.logger.verbose(`Loaded processor factory from ${packageName}`);
|
|
6216
7456
|
return factory;
|
|
@@ -6218,6 +7458,27 @@ var HttpPackageLoader = class {
|
|
|
6218
7458
|
this.logger.verbose(`No processor factory found in ${packageName}`);
|
|
6219
7459
|
return null;
|
|
6220
7460
|
}
|
|
7461
|
+
async loadPieces(packageSpec) {
|
|
7462
|
+
const { name: packageName } = this.parsePackageSpec(packageSpec);
|
|
7463
|
+
if (!isValidPackageName(packageName)) throw new Error(`Invalid package name: ${packageName}`);
|
|
7464
|
+
const version = await this.resolveVersion(packageSpec);
|
|
7465
|
+
if (!version) {
|
|
7466
|
+
this.logger.verbose(`No package version found for: ${packageSpec}`);
|
|
7467
|
+
return [];
|
|
7468
|
+
}
|
|
7469
|
+
const base = piecesBaseUrl(this.registryUrl, packageName, version);
|
|
7470
|
+
this.logger.verbose(`Importing pieces from: ${base}index.mjs`);
|
|
7471
|
+
let module;
|
|
7472
|
+
try {
|
|
7473
|
+
module = await import(`${base}index.mjs`);
|
|
7474
|
+
} catch (error) {
|
|
7475
|
+
this.logger.verbose("No pieces found for @package: @error", packageName, error);
|
|
7476
|
+
return [];
|
|
7477
|
+
}
|
|
7478
|
+
const pieces = piecesFromCdnList(module, base, packageName, this.logger, version);
|
|
7479
|
+
this.logger.verbose(`Loaded ${pieces.length} pieces from ${packageName}`);
|
|
7480
|
+
return pieces;
|
|
7481
|
+
}
|
|
6221
7482
|
/**
|
|
6222
7483
|
* Load document models from multiple packages.
|
|
6223
7484
|
* Continues loading even if some packages fail.
|
|
@@ -6234,28 +7495,30 @@ var HttpPackageLoader = class {
|
|
|
6234
7495
|
logger?.info(logMsg);
|
|
6235
7496
|
this.logger.info(logMsg);
|
|
6236
7497
|
} catch (error) {
|
|
6237
|
-
|
|
6238
|
-
logger
|
|
6239
|
-
this.logger.error(errMsg, error);
|
|
7498
|
+
logger?.error(`Failed to load package ${trimmedName}`, error);
|
|
7499
|
+
this.logger.error("Failed to load package @package: @error", trimmedName, error);
|
|
6240
7500
|
}
|
|
6241
7501
|
}
|
|
6242
7502
|
return allModels;
|
|
6243
7503
|
}
|
|
6244
|
-
|
|
6245
|
-
|
|
7504
|
+
async packageVersion(packageSpec) {
|
|
7505
|
+
try {
|
|
7506
|
+
const response = await fetch(`${this.registryUrl}-/cdn/${packageSpec}/package.json`);
|
|
7507
|
+
if (!response.ok) return void 0;
|
|
7508
|
+
const pkg = await response.json();
|
|
7509
|
+
return typeof pkg.version === "string" && EXACT_VERSION.test(pkg.version) ? pkg.version : void 0;
|
|
7510
|
+
} catch (error) {
|
|
7511
|
+
this.logger.verbose("Could not read the version of @package: @error", packageSpec, error);
|
|
7512
|
+
return;
|
|
7513
|
+
}
|
|
6246
7514
|
}
|
|
6247
7515
|
};
|
|
6248
|
-
/**
|
|
6249
|
-
* Returns live modules (host-only sources): dynamically loaded models are
|
|
6250
|
-
* registered on the host registry but never reach executor worker threads.
|
|
6251
|
-
* Making them worker-executable means returning an importable source here
|
|
6252
|
-
* (the CDN URL as a package specifier, given process-wide https hooks).
|
|
6253
|
-
*/
|
|
6254
7516
|
var HttpDocumentModelLoader = class {
|
|
6255
7517
|
loader;
|
|
6256
7518
|
logger = childLogger(["reactor-api", "http-document-model-loader"]);
|
|
6257
7519
|
documentTypeCache = /* @__PURE__ */ new Map();
|
|
6258
7520
|
packageModulesCache = /* @__PURE__ */ new Map();
|
|
7521
|
+
packageFileCache = /* @__PURE__ */ new Map();
|
|
6259
7522
|
onModelLoaded;
|
|
6260
7523
|
constructor(loader) {
|
|
6261
7524
|
this.loader = loader;
|
|
@@ -6266,15 +7529,16 @@ var HttpDocumentModelLoader = class {
|
|
|
6266
7529
|
clearCache() {
|
|
6267
7530
|
this.documentTypeCache.clear();
|
|
6268
7531
|
this.packageModulesCache.clear();
|
|
7532
|
+
this.packageFileCache.clear();
|
|
6269
7533
|
}
|
|
6270
7534
|
async load(documentType) {
|
|
6271
7535
|
const packageName = await this.findPackageByDocumentType(documentType);
|
|
6272
|
-
let models;
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
models = await this.loader.loadDocumentModels(packageName);
|
|
7536
|
+
let models = this.packageModulesCache.get(packageName);
|
|
7537
|
+
if (!models) {
|
|
7538
|
+
const { module, filePath } = await this.loader.importDocumentModels(packageName);
|
|
7539
|
+
models = documentModelsOf(module);
|
|
6277
7540
|
this.packageModulesCache.set(packageName, models);
|
|
7541
|
+
if (filePath) this.packageFileCache.set(packageName, filePath);
|
|
6278
7542
|
}
|
|
6279
7543
|
const model = models.find((m) => m.documentModel.global.id === documentType);
|
|
6280
7544
|
if (!model) {
|
|
@@ -6283,7 +7547,8 @@ var HttpDocumentModelLoader = class {
|
|
|
6283
7547
|
}
|
|
6284
7548
|
this.logger.info(`Loaded document model "${documentType}" from package "${packageName}"`);
|
|
6285
7549
|
if (this.onModelLoaded) this.onModelLoaded(model);
|
|
6286
|
-
|
|
7550
|
+
const filePath = this.packageFileCache.get(packageName);
|
|
7551
|
+
return filePath ? { filePath } : model;
|
|
6287
7552
|
}
|
|
6288
7553
|
async findPackageByDocumentType(documentType) {
|
|
6289
7554
|
const cached = this.documentTypeCache.get(documentType);
|
|
@@ -6306,6 +7571,7 @@ var HttpDocumentModelLoader = class {
|
|
|
6306
7571
|
}
|
|
6307
7572
|
removeFromCache(packageName) {
|
|
6308
7573
|
this.packageModulesCache.delete(packageName);
|
|
7574
|
+
this.packageFileCache.delete(packageName);
|
|
6309
7575
|
for (const [docType, pkg] of this.documentTypeCache) if (pkg === packageName) this.documentTypeCache.delete(docType);
|
|
6310
7576
|
}
|
|
6311
7577
|
};
|
|
@@ -6315,8 +7581,13 @@ var HttpDocumentModelLoader = class {
|
|
|
6315
7581
|
* This class is used to load packages using the import keyword.
|
|
6316
7582
|
*/
|
|
6317
7583
|
var ImportPackageLoader = class {
|
|
7584
|
+
hostDir;
|
|
6318
7585
|
logger = childLogger(["reactor-api", "import-loader"]);
|
|
6319
7586
|
name = "ImportPackageLoader";
|
|
7587
|
+
constructor(hostDir = process.cwd()) {
|
|
7588
|
+
this.hostDir = hostDir;
|
|
7589
|
+
}
|
|
7590
|
+
reportedUnbuilt = /* @__PURE__ */ new Set();
|
|
6320
7591
|
async loadDocumentModels(identifier) {
|
|
6321
7592
|
this.logger.verbose(`Loading document models from package: ${identifier}`);
|
|
6322
7593
|
const pkgModule = await loadDocumentModels(identifier);
|
|
@@ -6359,6 +7630,31 @@ var ImportPackageLoader = class {
|
|
|
6359
7630
|
this.logger.verbose(` ➜ No Processor Factory found: ${identifier}`);
|
|
6360
7631
|
return null;
|
|
6361
7632
|
}
|
|
7633
|
+
async loadPieces(identifier) {
|
|
7634
|
+
this.logger.verbose(`Loading pieces from package: ${identifier}`);
|
|
7635
|
+
const { root, listPath } = pieceListLocation(identifier, this.hostDir);
|
|
7636
|
+
if (!existsSync(listPath)) {
|
|
7637
|
+
this.reportUnbuilt(identifier, root, listPath);
|
|
7638
|
+
return [];
|
|
7639
|
+
}
|
|
7640
|
+
const module = await import(
|
|
7641
|
+
/* @vite-ignore */
|
|
7642
|
+
pathToFileURL(listPath).href
|
|
7643
|
+
);
|
|
7644
|
+
const pieces = piecesFromListModule(module, {
|
|
7645
|
+
root,
|
|
7646
|
+
identifier,
|
|
7647
|
+
logger: this.logger
|
|
7648
|
+
});
|
|
7649
|
+
this.logger.verbose(` ➜ Loaded ${pieces.length} Pieces from: ${identifier}`);
|
|
7650
|
+
return pieces;
|
|
7651
|
+
}
|
|
7652
|
+
reportUnbuilt(identifier, root, listPath) {
|
|
7653
|
+
if (!isAbsolute(identifier) || this.reportedUnbuilt.has(identifier)) return;
|
|
7654
|
+
if (!existsSync(join(root, PIECE_SOURCE_LIST))) return;
|
|
7655
|
+
this.reportedUnbuilt.add(identifier);
|
|
7656
|
+
this.logger.warn("@pkg declares pieces but @list was never built; run `ph build`", identifier, listPath);
|
|
7657
|
+
}
|
|
6362
7658
|
};
|
|
6363
7659
|
//#endregion
|
|
6364
7660
|
//#region src/packages/package-manager.ts
|
|
@@ -6374,10 +7670,13 @@ var ImportPackageLoader = class {
|
|
|
6374
7670
|
* either bare (`'pkg'`) or as a subpath (`'pkg/subgraphs'`), since loaders
|
|
6375
7671
|
* import sub-entries like `${pkg}/subgraphs` / `${pkg}/document-models`.
|
|
6376
7672
|
*/
|
|
6377
|
-
function isExpectedLoaderMiss(error, pkg) {
|
|
7673
|
+
function isExpectedLoaderMiss(error, pkg, subPath) {
|
|
6378
7674
|
if (!(error instanceof Error)) return false;
|
|
6379
7675
|
const code = error.code;
|
|
6380
7676
|
if (code === "ERR_UNSUPPORTED_DIR_IMPORT") return true;
|
|
7677
|
+
if (code === "ERR_REGISTRY_ENTRY_ABSENT") return true;
|
|
7678
|
+
if (code === "ERR_PACKAGE_PATH_NOT_EXPORTED" && subPath !== void 0 && error.message.includes(`'./${subPath}'`)) return true;
|
|
7679
|
+
if (subPath !== void 0 && new RegExp(`^Failed to fetch \\S*/${subPath}/index\\.mjs: 404$`).test(error.message)) return true;
|
|
6381
7680
|
if (error.message.startsWith("Invalid package name:")) return true;
|
|
6382
7681
|
if (code === "ERR_MODULE_NOT_FOUND") return error.message.includes(`'${pkg}'`) || error.message.includes(`"${pkg}"`) || error.message.includes(`'${pkg}/`) || error.message.includes(`"${pkg}/`);
|
|
6383
7682
|
return false;
|
|
@@ -6393,12 +7692,14 @@ function getUniqueUpgradeManifests(...upgradeManifests) {
|
|
|
6393
7692
|
return Array.from(uniqueManifests.values());
|
|
6394
7693
|
}
|
|
6395
7694
|
var PackageManager = class {
|
|
7695
|
+
options;
|
|
6396
7696
|
logger = childLogger(["reactor-api", "package-manager"]);
|
|
6397
7697
|
loaders;
|
|
6398
7698
|
docModelsMap = /* @__PURE__ */ new Map();
|
|
6399
7699
|
upgradeManifestsMap = /* @__PURE__ */ new Map();
|
|
6400
7700
|
subgraphsMap = /* @__PURE__ */ new Map();
|
|
6401
7701
|
processorMap = /* @__PURE__ */ new Map();
|
|
7702
|
+
piecesMap = /* @__PURE__ */ new Map();
|
|
6402
7703
|
configWatcher;
|
|
6403
7704
|
debouncedUpdateCallbacks = /* @__PURE__ */ new Map();
|
|
6404
7705
|
eventEmitter = new EventEmitter();
|
|
@@ -6417,10 +7718,12 @@ var PackageManager = class {
|
|
|
6417
7718
|
const upgradeManifestsMap = await this.loadUpgradeManifests(packages);
|
|
6418
7719
|
const subgraphsMap = await this.loadSubgraphs(packages);
|
|
6419
7720
|
const processorsMap = await this.loadProcessors(packages);
|
|
7721
|
+
const piecesMap = await this.loadPieces(packages);
|
|
6420
7722
|
this.upgradeManifestsMap = upgradeManifestsMap;
|
|
6421
7723
|
this.updatePackagesMap(documentModelsMap);
|
|
6422
7724
|
this.updateSubgraphsMap(subgraphsMap);
|
|
6423
7725
|
this.updateProcessorsMap(processorsMap);
|
|
7726
|
+
this.updatePiecesMap(piecesMap);
|
|
6424
7727
|
try {
|
|
6425
7728
|
this.subscribePackages(packages);
|
|
6426
7729
|
} catch (error) {
|
|
@@ -6430,7 +7733,8 @@ var PackageManager = class {
|
|
|
6430
7733
|
documentModels: getUniqueDocumentModels(...Array.from(documentModelsMap.values())),
|
|
6431
7734
|
upgradeManifests: this.getUniqueUpgradeManifests(),
|
|
6432
7735
|
subgraphs: subgraphsMap,
|
|
6433
|
-
processors: processorsMap
|
|
7736
|
+
processors: processorsMap,
|
|
7737
|
+
pieces: piecesMap
|
|
6434
7738
|
};
|
|
6435
7739
|
}
|
|
6436
7740
|
async loadDocumentModels(packages) {
|
|
@@ -6499,7 +7803,7 @@ var PackageManager = class {
|
|
|
6499
7803
|
loader: loader.name,
|
|
6500
7804
|
error
|
|
6501
7805
|
});
|
|
6502
|
-
this.logger.debug(
|
|
7806
|
+
this.logger.debug("[@loader] Failed to load subgraphs from package @package: @error", loader.name, pkg, error);
|
|
6503
7807
|
}
|
|
6504
7808
|
this.maybeWarnAllLoadersFailed("subgraphs", pkg, succeeded, failures);
|
|
6505
7809
|
subgraphsMap.set(pkg, allSubgraphs);
|
|
@@ -6523,16 +7827,41 @@ var PackageManager = class {
|
|
|
6523
7827
|
loader: loader.name,
|
|
6524
7828
|
error
|
|
6525
7829
|
});
|
|
6526
|
-
this.logger.debug(
|
|
7830
|
+
this.logger.debug("[@loader] Failed to load processors from package @package: @error", loader.name, pkg, error);
|
|
6527
7831
|
}
|
|
6528
7832
|
this.maybeWarnAllLoadersFailed("processors", pkg, succeeded, failures);
|
|
6529
7833
|
processorsMap.set(pkg, allProcessors);
|
|
6530
7834
|
}
|
|
6531
7835
|
return processorsMap;
|
|
6532
7836
|
}
|
|
7837
|
+
async loadPieces(packages) {
|
|
7838
|
+
this.logger.debug(`Loading pieces from packages: ${packages.join(", ")}`);
|
|
7839
|
+
const piecesMap = /* @__PURE__ */ new Map();
|
|
7840
|
+
for (const pkg of packages) {
|
|
7841
|
+
const allPieces = [];
|
|
7842
|
+
const failures = [];
|
|
7843
|
+
let succeeded = false;
|
|
7844
|
+
for (const loader of this.loaders) try {
|
|
7845
|
+
allPieces.push(...await loader.loadPieces(pkg));
|
|
7846
|
+
succeeded = true;
|
|
7847
|
+
break;
|
|
7848
|
+
} catch (error) {
|
|
7849
|
+
failures.push({
|
|
7850
|
+
loader: loader.name,
|
|
7851
|
+
error
|
|
7852
|
+
});
|
|
7853
|
+
this.logger.debug("[@loader] Failed to load pieces from package @package: @error", loader.name, pkg, error);
|
|
7854
|
+
}
|
|
7855
|
+
this.maybeWarnAllLoadersFailed("pieces", pkg, succeeded, failures);
|
|
7856
|
+
if (allPieces.length > 0) this.logger.info("Loaded @count piece(s) from package @pkg: @pieces", allPieces.length, pkg, allPieces.map((piece) => piece.name).join(", "));
|
|
7857
|
+
piecesMap.set(pkg, allPieces);
|
|
7858
|
+
}
|
|
7859
|
+
return piecesMap;
|
|
7860
|
+
}
|
|
6533
7861
|
maybeWarnAllLoadersFailed(kind, pkg, succeeded, failures) {
|
|
6534
7862
|
if (succeeded || failures.length === 0) return;
|
|
6535
|
-
const
|
|
7863
|
+
const subPath = kind === "document models" ? "document-models" : kind;
|
|
7864
|
+
const realFailures = failures.filter(({ error }) => !isExpectedLoaderMiss(error, pkg, subPath));
|
|
6536
7865
|
if (realFailures.length === 0) return;
|
|
6537
7866
|
const details = realFailures.map(({ loader, error }) => {
|
|
6538
7867
|
return ` [${loader}] ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -6572,6 +7901,15 @@ var PackageManager = class {
|
|
|
6572
7901
|
else processorsMap.set(pkg, pkgProcessors);
|
|
6573
7902
|
this.updateProcessorsMap(processorsMap);
|
|
6574
7903
|
}
|
|
7904
|
+
async updatePiecesForPackage(pkg) {
|
|
7905
|
+
this.logger.debug(`Updating pieces for package: ${pkg}`);
|
|
7906
|
+
const pieces = await this.loadPieces([pkg]);
|
|
7907
|
+
const piecesMap = new Map(this.piecesMap);
|
|
7908
|
+
const pkgPieces = pieces.get(pkg) ?? [];
|
|
7909
|
+
if (pkgPieces.length === 0) piecesMap.delete(pkg);
|
|
7910
|
+
else piecesMap.set(pkg, pkgPieces);
|
|
7911
|
+
this.updatePiecesMap(piecesMap);
|
|
7912
|
+
}
|
|
6575
7913
|
/** Debounced per key so repeated subscribePackages calls reuse one timer. */
|
|
6576
7914
|
getDebouncedUpdateCallback(key, update) {
|
|
6577
7915
|
if (!this.debouncedUpdateCallbacks.has(key)) {
|
|
@@ -6590,10 +7928,12 @@ var PackageManager = class {
|
|
|
6590
7928
|
const onDocumentModels = this.getDebouncedUpdateCallback(`${pkg}:document-models`, () => this.updateDocumentModelsForPackage(pkg));
|
|
6591
7929
|
const onSubgraphs = this.getDebouncedUpdateCallback(`${pkg}:subgraphs`, () => this.updateSubgraphsForPackage(pkg));
|
|
6592
7930
|
const onProcessors = this.getDebouncedUpdateCallback(`${pkg}:processors`, () => this.updateProcessorsForPackage(pkg));
|
|
7931
|
+
const onPieces = this.getDebouncedUpdateCallback(`${pkg}:pieces`, () => this.updatePiecesForPackage(pkg));
|
|
6593
7932
|
for (const loader of this.loaders) {
|
|
6594
7933
|
if (loader.onDocumentModelsChange) unsubs.push(loader.onDocumentModelsChange(pkg, onDocumentModels));
|
|
6595
7934
|
if (loader.onSubgraphsChange) unsubs.push(loader.onSubgraphsChange(pkg, onSubgraphs));
|
|
6596
7935
|
if (loader.onProcessorsChange) unsubs.push(loader.onProcessorsChange(pkg, onProcessors));
|
|
7936
|
+
if (loader.onPiecesChange) unsubs.push(loader.onPiecesChange(pkg, onPieces));
|
|
6597
7937
|
}
|
|
6598
7938
|
}
|
|
6599
7939
|
return () => {
|
|
@@ -6641,6 +7981,19 @@ var PackageManager = class {
|
|
|
6641
7981
|
this.processorMap = processorsMap;
|
|
6642
7982
|
this.eventEmitter.emit("processorsChange", processorsMap);
|
|
6643
7983
|
}
|
|
7984
|
+
updatePiecesMap(piecesMap) {
|
|
7985
|
+
const oldPackages = Array.from(this.piecesMap.keys());
|
|
7986
|
+
const newPackages = Array.from(piecesMap.keys());
|
|
7987
|
+
oldPackages.filter((pkg) => !newPackages.includes(pkg)).forEach((pkg) => {
|
|
7988
|
+
this.logger.info("Removed pieces from: @pkg", pkg);
|
|
7989
|
+
});
|
|
7990
|
+
this.piecesMap = piecesMap;
|
|
7991
|
+
this.eventEmitter.emit("piecesChange", piecesMap);
|
|
7992
|
+
}
|
|
7993
|
+
/** The pieces every loaded package ships, keyed by the package. */
|
|
7994
|
+
getPieces() {
|
|
7995
|
+
return new Map(this.piecesMap);
|
|
7996
|
+
}
|
|
6644
7997
|
/**
|
|
6645
7998
|
* Remove `pkg` from all package maps and emit the change events, tearing
|
|
6646
7999
|
* down everything the package registered (subgraphs, processors, document
|
|
@@ -6659,9 +8012,11 @@ var PackageManager = class {
|
|
|
6659
8012
|
this.upgradeManifestsMap = dropKey(this.upgradeManifestsMap);
|
|
6660
8013
|
this.subgraphsMap = dropKey(this.subgraphsMap);
|
|
6661
8014
|
this.processorMap = dropKey(this.processorMap);
|
|
8015
|
+
this.piecesMap = dropKey(this.piecesMap);
|
|
6662
8016
|
this.updatePackagesMap(this.docModelsMap);
|
|
6663
8017
|
this.updateSubgraphsMap(this.subgraphsMap);
|
|
6664
8018
|
this.updateProcessorsMap(this.processorMap);
|
|
8019
|
+
this.updatePiecesMap(this.piecesMap);
|
|
6665
8020
|
}
|
|
6666
8021
|
onDocumentModelsChange(handler) {
|
|
6667
8022
|
this.eventEmitter.on("documentModelsChange", handler);
|
|
@@ -6672,6 +8027,9 @@ var PackageManager = class {
|
|
|
6672
8027
|
onProcessorsChange(handler) {
|
|
6673
8028
|
this.eventEmitter.on("processorsChange", handler);
|
|
6674
8029
|
}
|
|
8030
|
+
onPiecesChange(handler) {
|
|
8031
|
+
this.eventEmitter.on("piecesChange", handler);
|
|
8032
|
+
}
|
|
6675
8033
|
};
|
|
6676
8034
|
//#endregion
|
|
6677
8035
|
//#region src/config.ts
|
|
@@ -6689,20 +8047,55 @@ async function createStartupAttachmentBackend({ db, env = process.env, s3Depende
|
|
|
6689
8047
|
}
|
|
6690
8048
|
//#endregion
|
|
6691
8049
|
//#region src/services/attachment-access.service.ts
|
|
8050
|
+
/** The scope an attachment's bytes belong to: the document's own domain state. */
|
|
8051
|
+
const ATTACHMENT_SCOPE = "global";
|
|
8052
|
+
/** The branch an attachment reference is resolved against. */
|
|
8053
|
+
const ATTACHMENT_BRANCH = "main";
|
|
6692
8054
|
const HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
6693
8055
|
/**
|
|
6694
8056
|
* Composes document authorization with the projected document/ref
|
|
6695
8057
|
* relationship. Order is fixed: validate ref, resolve the canonical document
|
|
6696
|
-
* id,
|
|
6697
|
-
*
|
|
6698
|
-
* attachment
|
|
8058
|
+
* id, decide the read, check the reactor's read gate serves the document, then
|
|
8059
|
+
* check the reference index and that the subject may read a scope whose
|
|
8060
|
+
* operations reference the attachment. A denied document never reaches the
|
|
8061
|
+
* reference reader, and the facade never touches attachment metadata, storage
|
|
8062
|
+
* backends, or presigners.
|
|
8063
|
+
*
|
|
8064
|
+
* The named document decides alone: an attachment several documents reference
|
|
8065
|
+
* is readable through each of them on that document's own terms.
|
|
8066
|
+
*
|
|
8067
|
+
* The read is decided by the document's own policy when this composition has a
|
|
8068
|
+
* model to enforce it with, and by the host's permission tables when it does
|
|
8069
|
+
* not. That is not a preference between two equivalent answers: a host running
|
|
8070
|
+
* document policies keeps no rows in the permission tables, so asking them
|
|
8071
|
+
* about such a document returns whatever the host-wide policy says — under
|
|
8072
|
+
* `OPEN`, `true`, for everyone. Serving bytes on that answer hands the file to
|
|
8073
|
+
* anyone who learns its hash, which the document's own state may well have told
|
|
8074
|
+
* them. Both are kept because they are the two halves of one rule: an
|
|
8075
|
+
* attachment is readable by whoever may read the document that references it,
|
|
8076
|
+
* and each composition can only express that in its own terms. The caller
|
|
8077
|
+
* decision mirrors the routes' `requireAuth`.
|
|
6699
8078
|
*/
|
|
6700
8079
|
var AttachmentAccessService = class {
|
|
6701
|
-
|
|
8080
|
+
resolveCanonicalId;
|
|
8081
|
+
authorization;
|
|
8082
|
+
references;
|
|
8083
|
+
projection;
|
|
8084
|
+
readGate;
|
|
8085
|
+
scopeGate;
|
|
8086
|
+
options;
|
|
8087
|
+
constructor(resolveCanonicalId, authorization, references, projection, readGate, scopeGate, options = {}) {
|
|
6702
8088
|
this.resolveCanonicalId = resolveCanonicalId;
|
|
6703
8089
|
this.authorization = authorization;
|
|
6704
8090
|
this.references = references;
|
|
6705
8091
|
this.projection = projection;
|
|
8092
|
+
this.readGate = readGate;
|
|
8093
|
+
this.scopeGate = scopeGate;
|
|
8094
|
+
this.options = options;
|
|
8095
|
+
}
|
|
8096
|
+
admitCaller(request) {
|
|
8097
|
+
if ((request.intent === "read" ? this.options.refuseAnonymousReads ?? false : this.options.refuseAnonymousWrites ?? true) && !request.userAddress) return Promise.resolve({ kind: "unauthenticated" });
|
|
8098
|
+
return Promise.resolve({ kind: "admitted" });
|
|
6706
8099
|
}
|
|
6707
8100
|
async canReadAttachment(request) {
|
|
6708
8101
|
if (this.projection.status !== "available") return { kind: "projection-unavailable" };
|
|
@@ -6714,14 +8107,51 @@ var AttachmentAccessService = class {
|
|
|
6714
8107
|
} catch {
|
|
6715
8108
|
return { kind: "denied" };
|
|
6716
8109
|
}
|
|
6717
|
-
if (!await this.
|
|
6718
|
-
|
|
8110
|
+
if (!await this.canReadDocument(documentId, request)) return { kind: "denied" };
|
|
8111
|
+
const subject = {
|
|
8112
|
+
address: request.userAddress,
|
|
8113
|
+
key: request.appKey
|
|
8114
|
+
};
|
|
8115
|
+
let served;
|
|
8116
|
+
try {
|
|
8117
|
+
served = await this.readGate.isServed(documentId, { subject });
|
|
8118
|
+
} catch {
|
|
8119
|
+
return { kind: "denied" };
|
|
8120
|
+
}
|
|
8121
|
+
if (!served) return { kind: "denied" };
|
|
8122
|
+
const scopes = await this.references.referencingScopes(documentId, ref);
|
|
8123
|
+
if (scopes.length === 0) return { kind: "denied" };
|
|
8124
|
+
let held;
|
|
8125
|
+
try {
|
|
8126
|
+
held = (await this.readGate.get(documentId, {
|
|
8127
|
+
subject,
|
|
8128
|
+
scopes
|
|
8129
|
+
})).state;
|
|
8130
|
+
} catch {
|
|
8131
|
+
return { kind: "denied" };
|
|
8132
|
+
}
|
|
8133
|
+
if (!scopes.some((scope) => scope in held)) return { kind: "denied" };
|
|
6719
8134
|
return {
|
|
6720
8135
|
kind: "allowed",
|
|
6721
8136
|
documentId,
|
|
6722
8137
|
ref
|
|
6723
8138
|
};
|
|
6724
8139
|
}
|
|
8140
|
+
/**
|
|
8141
|
+
* Whether the caller may read the state the reference lives in.
|
|
8142
|
+
*
|
|
8143
|
+
* A failure here is rethrown rather than turned into a denial. A read side
|
|
8144
|
+
* that is down must not read as a policy: a denial nobody can distinguish
|
|
8145
|
+
* from a refusal is one nobody investigates, and the route answers 500 for a
|
|
8146
|
+
* reason.
|
|
8147
|
+
*/
|
|
8148
|
+
async canReadDocument(documentId, request) {
|
|
8149
|
+
if (!this.scopeGate) return this.authorization.canRead(documentId, request.userAddress);
|
|
8150
|
+
return (await this.scopeGate.scopePredicateById(documentId, {
|
|
8151
|
+
address: request.userAddress,
|
|
8152
|
+
key: request.appKey
|
|
8153
|
+
}, ATTACHMENT_BRANCH))(ATTACHMENT_SCOPE);
|
|
8154
|
+
}
|
|
6725
8155
|
};
|
|
6726
8156
|
/**
|
|
6727
8157
|
* Parses and canonicalizes a caller-supplied ref through the shared parser.
|
|
@@ -6742,6 +8172,140 @@ function normalizeAttachmentRef(value) {
|
|
|
6742
8172
|
return createRef(hash, version);
|
|
6743
8173
|
}
|
|
6744
8174
|
//#endregion
|
|
8175
|
+
//#region src/services/authorized-attachment.service.ts
|
|
8176
|
+
/** Matches the attachment routes' `documentId` bound. */
|
|
8177
|
+
const MAX_DOCUMENT_ID_LEN = 512;
|
|
8178
|
+
/** The reference index is not maintained in this composition. */
|
|
8179
|
+
var AttachmentAccessUnavailable = class extends Error {
|
|
8180
|
+
constructor() {
|
|
8181
|
+
super("Attachment access is unavailable");
|
|
8182
|
+
this.name = "AttachmentAccessUnavailable";
|
|
8183
|
+
}
|
|
8184
|
+
};
|
|
8185
|
+
/** The access decision itself failed; the cause is logged, not surfaced. */
|
|
8186
|
+
var AttachmentAccessFailed = class extends Error {
|
|
8187
|
+
constructor(cause) {
|
|
8188
|
+
super("Attachment access decision failed", { cause });
|
|
8189
|
+
this.name = "AttachmentAccessFailed";
|
|
8190
|
+
}
|
|
8191
|
+
};
|
|
8192
|
+
/** An `IAttachmentService` whose reads and writes are decided for one subject. */
|
|
8193
|
+
var AuthorizedAttachmentService = class {
|
|
8194
|
+
inner;
|
|
8195
|
+
access;
|
|
8196
|
+
subject;
|
|
8197
|
+
logger;
|
|
8198
|
+
constructor(inner, access, subject, logger) {
|
|
8199
|
+
this.inner = inner;
|
|
8200
|
+
this.access = access;
|
|
8201
|
+
this.subject = subject;
|
|
8202
|
+
this.logger = logger;
|
|
8203
|
+
}
|
|
8204
|
+
/** Hash-first only: an upload-first handle can send after it expires. */
|
|
8205
|
+
async reserve(options) {
|
|
8206
|
+
const { mimeType, fileName, extension, clientHash, sizeBytes } = options;
|
|
8207
|
+
if (clientHash === void 0) throw new Error("Attachment reservations require a client hash");
|
|
8208
|
+
const copy = {
|
|
8209
|
+
mimeType,
|
|
8210
|
+
fileName,
|
|
8211
|
+
extension,
|
|
8212
|
+
clientHash,
|
|
8213
|
+
sizeBytes
|
|
8214
|
+
};
|
|
8215
|
+
await this.admit("write");
|
|
8216
|
+
validateReserveMetadata(copy);
|
|
8217
|
+
return this.inner.reserve(copy);
|
|
8218
|
+
}
|
|
8219
|
+
async stat(ref, options) {
|
|
8220
|
+
const decision = await this.decideRead(ref, options);
|
|
8221
|
+
return this.inner.stat(decision.ref, {
|
|
8222
|
+
...options,
|
|
8223
|
+
documentId: decision.documentId
|
|
8224
|
+
});
|
|
8225
|
+
}
|
|
8226
|
+
async get(ref, options) {
|
|
8227
|
+
const decision = await this.decideRead(ref, options);
|
|
8228
|
+
return this.inner.get(decision.ref, {
|
|
8229
|
+
...options,
|
|
8230
|
+
documentId: decision.documentId
|
|
8231
|
+
});
|
|
8232
|
+
}
|
|
8233
|
+
async getDownloadTarget(ref, options) {
|
|
8234
|
+
const decision = await this.decideRead(ref, options);
|
|
8235
|
+
return this.inner.getDownloadTarget(decision.ref, {
|
|
8236
|
+
...options,
|
|
8237
|
+
documentId: decision.documentId
|
|
8238
|
+
});
|
|
8239
|
+
}
|
|
8240
|
+
async decideRead(ref, options) {
|
|
8241
|
+
const documentId = readDocumentId(options);
|
|
8242
|
+
if (documentId === null) throw new AttachmentNotFound(ref);
|
|
8243
|
+
await this.admit("read");
|
|
8244
|
+
let decision;
|
|
8245
|
+
try {
|
|
8246
|
+
decision = await this.access.canReadAttachment({
|
|
8247
|
+
documentId,
|
|
8248
|
+
attachmentRef: ref,
|
|
8249
|
+
userAddress: this.subject.address,
|
|
8250
|
+
appKey: this.subject.key
|
|
8251
|
+
});
|
|
8252
|
+
} catch (err) {
|
|
8253
|
+
throw this.accessFailed(err);
|
|
8254
|
+
}
|
|
8255
|
+
if (decision.kind === "allowed") return {
|
|
8256
|
+
documentId: decision.documentId,
|
|
8257
|
+
ref: decision.ref
|
|
8258
|
+
};
|
|
8259
|
+
if (decision.kind === "projection-unavailable") throw new AttachmentAccessUnavailable();
|
|
8260
|
+
throw new AttachmentNotFound(ref);
|
|
8261
|
+
}
|
|
8262
|
+
async admit(intent) {
|
|
8263
|
+
let result;
|
|
8264
|
+
try {
|
|
8265
|
+
result = await this.access.admitCaller({
|
|
8266
|
+
intent,
|
|
8267
|
+
userAddress: this.subject.address,
|
|
8268
|
+
appKey: this.subject.key
|
|
8269
|
+
});
|
|
8270
|
+
} catch (err) {
|
|
8271
|
+
throw this.accessFailed(err);
|
|
8272
|
+
}
|
|
8273
|
+
if (result.kind !== "admitted") throw new AuthenticationRequiredError();
|
|
8274
|
+
}
|
|
8275
|
+
accessFailed(err) {
|
|
8276
|
+
this.logger.error("Attachment access decision failed: @error", err);
|
|
8277
|
+
return new AttachmentAccessFailed(err);
|
|
8278
|
+
}
|
|
8279
|
+
};
|
|
8280
|
+
/** The options' `documentId` when it is one the routes would accept, else null. */
|
|
8281
|
+
function readDocumentId(options) {
|
|
8282
|
+
if (typeof options !== "object" || options === null) return null;
|
|
8283
|
+
if (isAbortSignal(options)) return null;
|
|
8284
|
+
const value = options.documentId;
|
|
8285
|
+
if (typeof value !== "string") return null;
|
|
8286
|
+
if (value.trim().length === 0 || value.length > MAX_DOCUMENT_ID_LEN) return null;
|
|
8287
|
+
return value;
|
|
8288
|
+
}
|
|
8289
|
+
/** Duck-checked too, so a signal from another realm is still a signal. */
|
|
8290
|
+
function isAbortSignal(value) {
|
|
8291
|
+
if (value instanceof AbortSignal) return true;
|
|
8292
|
+
const candidate = value;
|
|
8293
|
+
return typeof candidate.aborted === "boolean" && typeof candidate.addEventListener === "function";
|
|
8294
|
+
}
|
|
8295
|
+
var AttachmentClientProvider = class {
|
|
8296
|
+
service;
|
|
8297
|
+
access;
|
|
8298
|
+
logger;
|
|
8299
|
+
constructor(service, access, logger) {
|
|
8300
|
+
this.service = service;
|
|
8301
|
+
this.access = access;
|
|
8302
|
+
this.logger = logger;
|
|
8303
|
+
}
|
|
8304
|
+
forSubject(subject) {
|
|
8305
|
+
return createAttachmentClient(new AuthorizedAttachmentService(this.service, this.access, subject, this.logger));
|
|
8306
|
+
}
|
|
8307
|
+
};
|
|
8308
|
+
//#endregion
|
|
6745
8309
|
//#region src/graphql/gateway/require-auth-middleware.ts
|
|
6746
8310
|
/**
|
|
6747
8311
|
* Returns a fetch middleware that rejects anonymous callers with a 401.
|
|
@@ -6767,14 +8331,42 @@ function normalizeAttachmentRef(value) {
|
|
|
6767
8331
|
* context has no caller, and is rejected. The server never composes this
|
|
6768
8332
|
* middleware without the auth middleware in the chain (see
|
|
6769
8333
|
* `assertRequireAuthenticatedCallerAllowed` in `server.ts`).
|
|
8334
|
+
*
|
|
8335
|
+
* `exemptPaths` names the mounted paths that stay reachable anonymously — a
|
|
8336
|
+
* deployment whose product has a flow that runs before sign-in, such as
|
|
8337
|
+
* previewing an invitation from its code, needs somewhere to serve it from.
|
|
8338
|
+
* Each entry is a hole in the floor, so the match is deliberately the
|
|
8339
|
+
* narrowest one that works: the request's pathname, compared in full. A
|
|
8340
|
+
* prefix rule would exempt `/graphql/public-admin` along with
|
|
8341
|
+
* `/graphql/public`, and an exempt surface nobody can enumerate by reading
|
|
8342
|
+
* the configuration is not one anybody audits.
|
|
6770
8343
|
*/
|
|
6771
|
-
function createRequireAuthFetchMiddleware() {
|
|
8344
|
+
function createRequireAuthFetchMiddleware(exemptPaths = []) {
|
|
8345
|
+
const exempt = new Set(exemptPaths.map(normalizeExemptPath));
|
|
6772
8346
|
return (next) => async (request) => {
|
|
6773
8347
|
if (request.method === "OPTIONS") return next(request);
|
|
8348
|
+
if (exempt.size > 0 && exempt.has(normalizeExemptPath(new URL(request.url).pathname))) return next(request);
|
|
6774
8349
|
if (getAuthContext(request)?.user === void 0) return new globalThis.Response(JSON.stringify({ error: "Authentication required" }), { status: 401 });
|
|
6775
8350
|
return next(request);
|
|
6776
8351
|
};
|
|
6777
8352
|
}
|
|
8353
|
+
/**
|
|
8354
|
+
* The form both sides of the comparison are reduced to: surrounding whitespace
|
|
8355
|
+
* gone, and one trailing slash gone, so `/graphql/public/` and
|
|
8356
|
+
* `/graphql/public` are the same path. A client that appends a slash is not
|
|
8357
|
+
* asking for a different route, and a configuration that carries one is not
|
|
8358
|
+
* asking for a different rule.
|
|
8359
|
+
*
|
|
8360
|
+
* Deliberately NOT normalized: case, percent-encoding, and a missing leading
|
|
8361
|
+
* slash. The first two would let two spellings of one path diverge from how
|
|
8362
|
+
* the router itself matches; the third is a configuration error, and
|
|
8363
|
+
* `assertRequireAuthenticatedCallerAllowed` refuses to boot on it rather than
|
|
8364
|
+
* leaving an exemption that silently never applies.
|
|
8365
|
+
*/
|
|
8366
|
+
function normalizeExemptPath(path) {
|
|
8367
|
+
const trimmed = path.trim();
|
|
8368
|
+
return trimmed.length > 1 && trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
8369
|
+
}
|
|
6778
8370
|
//#endregion
|
|
6779
8371
|
//#region src/migrations/001_create_document_permissions.ts
|
|
6780
8372
|
var _001_create_document_permissions_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -7177,6 +8769,7 @@ async function createRenownCredentialVerifier(config = {}) {
|
|
|
7177
8769
|
* - Users can be granted permission to execute specific operations
|
|
7178
8770
|
*/
|
|
7179
8771
|
var DocumentPermissionService = class {
|
|
8772
|
+
db;
|
|
7180
8773
|
config;
|
|
7181
8774
|
constructor(db, config = { defaultProtection: false }) {
|
|
7182
8775
|
this.db = db;
|
|
@@ -7252,8 +8845,38 @@ var DocumentPermissionService = class {
|
|
|
7252
8845
|
* Delete all permissions for a document (used when deleting a document)
|
|
7253
8846
|
*/
|
|
7254
8847
|
async deleteAllDocumentPermissions(documentId) {
|
|
7255
|
-
await this.db.deleteFrom("DocumentPermission").where("documentId", "=", documentId).execute();
|
|
7256
|
-
await this.db.deleteFrom("OperationUserPermission").where("documentId", "=", documentId).execute();
|
|
8848
|
+
const permissions = await this.db.deleteFrom("DocumentPermission").where("documentId", "=", documentId).returning("id").execute();
|
|
8849
|
+
const operations = await this.db.deleteFrom("OperationUserPermission").where("documentId", "=", documentId).returning("id").execute();
|
|
8850
|
+
return {
|
|
8851
|
+
DocumentPermission: permissions.length,
|
|
8852
|
+
OperationUserPermission: operations.length
|
|
8853
|
+
};
|
|
8854
|
+
}
|
|
8855
|
+
/** Delete a document's protection row, which names its owner. */
|
|
8856
|
+
async deleteDocumentProtection(documentId) {
|
|
8857
|
+
return (await this.db.deleteFrom("DocumentProtection").where("documentId", "=", documentId).returning("documentId").execute()).length;
|
|
8858
|
+
}
|
|
8859
|
+
/** Every row that names the address, for a subject access request. */
|
|
8860
|
+
async rowsForAddress(address) {
|
|
8861
|
+
const lower = address.toLowerCase();
|
|
8862
|
+
return {
|
|
8863
|
+
permissions: await this.db.selectFrom("DocumentPermission").select([
|
|
8864
|
+
"documentId",
|
|
8865
|
+
"userAddress",
|
|
8866
|
+
"permission",
|
|
8867
|
+
"grantedBy",
|
|
8868
|
+
"createdAt",
|
|
8869
|
+
"updatedAt"
|
|
8870
|
+
]).where((eb) => eb.or([eb("userAddress", "=", lower), eb("grantedBy", "=", lower)])).orderBy("documentId").execute(),
|
|
8871
|
+
operationPermissions: await this.db.selectFrom("OperationUserPermission").select([
|
|
8872
|
+
"documentId",
|
|
8873
|
+
"operationType",
|
|
8874
|
+
"userAddress",
|
|
8875
|
+
"grantedBy",
|
|
8876
|
+
"createdAt"
|
|
8877
|
+
]).where((eb) => eb.or([eb("userAddress", "=", lower), eb("grantedBy", "=", lower)])).orderBy("documentId").execute(),
|
|
8878
|
+
ownedDocumentIds: (await this.db.selectFrom("DocumentProtection").select("documentId").where("ownerAddress", "=", lower).orderBy("documentId").execute()).map((row) => row.documentId)
|
|
8879
|
+
};
|
|
7257
8880
|
}
|
|
7258
8881
|
/**
|
|
7259
8882
|
* Grant a user permission to execute an operation on a document
|
|
@@ -7447,12 +9070,16 @@ function createGetParentIdsFn(reactorClient) {
|
|
|
7447
9070
|
* Authorization gate for the /mcp endpoint (AUTH_REVIEW S-C1). MCP tools have
|
|
7448
9071
|
* unrestricted reactor access, so access is limited to supreme admins when
|
|
7449
9072
|
* auth is enabled. OPEN policy is public; any other policy without an
|
|
7450
|
-
* AuthService fails closed.
|
|
9073
|
+
* AuthService fails closed. The tools read as the caller: anonymous under OPEN,
|
|
9074
|
+
* else the admin's address and the app key its token carries.
|
|
7451
9075
|
*/
|
|
7452
9076
|
function createMcpRequestAuthorizer(authService, authorizationService) {
|
|
7453
9077
|
return async (req) => {
|
|
7454
9078
|
if (!authService) {
|
|
7455
|
-
if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return {
|
|
9079
|
+
if (authorizationService.config.policy === AuthorizationPolicy.OPEN) return {
|
|
9080
|
+
authorized: true,
|
|
9081
|
+
subject: {}
|
|
9082
|
+
};
|
|
7456
9083
|
return {
|
|
7457
9084
|
authorized: false,
|
|
7458
9085
|
status: 401,
|
|
@@ -7475,10 +9102,39 @@ function createMcpRequestAuthorizer(authService, authorizationService) {
|
|
|
7475
9102
|
status: 403,
|
|
7476
9103
|
message: "Forbidden: MCP access requires an administrator"
|
|
7477
9104
|
};
|
|
7478
|
-
return {
|
|
9105
|
+
return {
|
|
9106
|
+
authorized: true,
|
|
9107
|
+
subject: {
|
|
9108
|
+
address: context.user.address,
|
|
9109
|
+
key: context.user.appKey
|
|
9110
|
+
}
|
|
9111
|
+
};
|
|
7479
9112
|
};
|
|
7480
9113
|
}
|
|
7481
9114
|
//#endregion
|
|
9115
|
+
//#region src/services/sync-serving-gate.ts
|
|
9116
|
+
/**
|
|
9117
|
+
* The gate sync serving evaluates a document's own policy through, deciding
|
|
9118
|
+
* what the reactor client's read gate decides at the same flag level.
|
|
9119
|
+
*
|
|
9120
|
+
* Under `authEnforcement` it is built here rather than taken off the client
|
|
9121
|
+
* because it also carries the host's closes-by-default setting, which withholds
|
|
9122
|
+
* the domain scopes of a document nobody has policied yet. That answer belongs
|
|
9123
|
+
* to serving alone -- replay must keep reading an uninitialized document in
|
|
9124
|
+
* full -- so the two gates are separate objects over the same model.
|
|
9125
|
+
*
|
|
9126
|
+
* Below it the registered model ignores the auth scope, so the client reads
|
|
9127
|
+
* through the policy alone, and so does serving: a policy written below the
|
|
9128
|
+
* flag still withholds, where serving ungated would sync to a peer what `get`
|
|
9129
|
+
* refuses it.
|
|
9130
|
+
*/
|
|
9131
|
+
function buildSyncServingGate(reactorModule, authorizationConfig, logger) {
|
|
9132
|
+
if (!reactorModule) return;
|
|
9133
|
+
const model = readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry);
|
|
9134
|
+
if (!model) return new SyncScopeGate(new BareReadGate(), reactorModule.documentView, logger);
|
|
9135
|
+
return new SyncScopeGate(new ModelReadGate(model, reactorModule.documentView, reactorModule.featureFlags.authGroups, reactorModule.operationIndex, logger, { withholdUninitialized: authorizationConfig.defaultProtection }), reactorModule.documentView, logger);
|
|
9136
|
+
}
|
|
9137
|
+
//#endregion
|
|
7482
9138
|
//#region src/services/renown-config.ts
|
|
7483
9139
|
/** Reading credentials from this reactor's own read model needs a host-supplied
|
|
7484
9140
|
* verifier: core has no idea which subgraph serves that read model. */
|
|
@@ -7660,10 +9316,36 @@ function assertSkipCredentialVerificationAllowed(resolvesCallerIdentity, skipCre
|
|
|
7660
9316
|
* require-authenticated-caller middleware would reject every caller —
|
|
7661
9317
|
* authenticated ones included. Refuse to boot rather than run broken.
|
|
7662
9318
|
*/
|
|
7663
|
-
function assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolvesCallerIdentity) {
|
|
9319
|
+
function assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolvesCallerIdentity, exemptPaths = []) {
|
|
9320
|
+
/**
|
|
9321
|
+
* An exemption is only ever a hole in this floor, so configuring one while
|
|
9322
|
+
* the floor is off is not a harmless no-op: it reads, to anyone auditing the
|
|
9323
|
+
* configuration, as a surface that was deliberately opened — and therefore
|
|
9324
|
+
* as a floor that exists. Refuse rather than let the two drift.
|
|
9325
|
+
*/
|
|
9326
|
+
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.");
|
|
9327
|
+
/**
|
|
9328
|
+
* A path that cannot match anything is worse than no path: the operator
|
|
9329
|
+
* believes the flow is reachable, and finds out when the first user cannot
|
|
9330
|
+
* claim an invitation. `URL.pathname` is always absolute, so an entry that
|
|
9331
|
+
* does not start with `/` never matches, whatever the router does.
|
|
9332
|
+
*/
|
|
9333
|
+
const relative = exemptPaths.filter((path) => !path.trim().startsWith("/"));
|
|
9334
|
+
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
9335
|
if (!requireAuthenticatedCaller || resolvesCallerIdentity) return;
|
|
7665
9336
|
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
9337
|
}
|
|
9338
|
+
/**
|
|
9339
|
+
* Deciding an attachment read by the document's policy needs a policy model to
|
|
9340
|
+
* decide with, and that is what auth enforcement supplies. Without one there is
|
|
9341
|
+
* nothing to consult, so the setting would silently leave the permission tables
|
|
9342
|
+
* in charge — configuration that describes a protection the server is not
|
|
9343
|
+
* applying, which is worse than no configuration at all. Refuse instead.
|
|
9344
|
+
*/
|
|
9345
|
+
function assertAttachmentPolicyReadsAllowed(attachmentReadsFollowDocumentPolicy, hasDecisionModel) {
|
|
9346
|
+
if (!attachmentReadsFollowDocumentPolicy || hasDecisionModel) return;
|
|
9347
|
+
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).");
|
|
9348
|
+
}
|
|
7667
9349
|
function createReadinessGate() {
|
|
7668
9350
|
let ready = false;
|
|
7669
9351
|
return {
|
|
@@ -7718,27 +9400,6 @@ function makeDbClosers(knexInstance, pglite) {
|
|
|
7718
9400
|
return closers;
|
|
7719
9401
|
}
|
|
7720
9402
|
/**
|
|
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
9403
|
* Resolves the gateway adapter type from the `GATEWAY_ADAPTER` env var.
|
|
7743
9404
|
* Defaults to "apollo" (the federation gateway, production behavior).
|
|
7744
9405
|
* "stitching" selects the in-process graphql-tools merge gateway (#1565
|
|
@@ -7754,8 +9415,28 @@ function resolveGatewayAdapterType(logger) {
|
|
|
7754
9415
|
/**
|
|
7755
9416
|
* Sets up the subgraph manager and registers subgraphs
|
|
7756
9417
|
*/
|
|
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(
|
|
9418
|
+
async function setupGraphQLManager({ httpAdapter, authFetchMiddleware, requireAuthFetchMiddleware, httpServer, wsServer, client, relationalDb, analyticsStore, syncManager, subgraphs, logger, authorizationService, authService, documentPermissionService, enableDocumentModelSubgraphs, port, reactorDriveClient, syncServingGate, httpRoutes, attachments }) {
|
|
9419
|
+
const graphqlManager = new GraphQLManager({
|
|
9420
|
+
path: config.basePath,
|
|
9421
|
+
httpServer,
|
|
9422
|
+
wsServer,
|
|
9423
|
+
reactorClient: client,
|
|
9424
|
+
relationalDb,
|
|
9425
|
+
analyticsStore,
|
|
9426
|
+
syncManager,
|
|
9427
|
+
logger,
|
|
9428
|
+
httpAdapter,
|
|
9429
|
+
gatewayAdapter: await createGatewayAdapter(resolveGatewayAdapterType(logger), logger),
|
|
9430
|
+
authService,
|
|
9431
|
+
documentPermissionService,
|
|
9432
|
+
featureFlags: { enableDocumentModelSubgraphs },
|
|
9433
|
+
port,
|
|
9434
|
+
authorizationService,
|
|
9435
|
+
reactorDriveClient,
|
|
9436
|
+
syncServingGate,
|
|
9437
|
+
httpRoutes,
|
|
9438
|
+
attachments
|
|
9439
|
+
});
|
|
7759
9440
|
await graphqlManager.init(subgraphs.core, authFetchMiddleware, requireAuthFetchMiddleware);
|
|
7760
9441
|
for (const [packageName, collection] of subgraphs.extended.entries()) for (const subgraph of collection) await graphqlManager.registerSubgraph(subgraph, "graphql", false, packageName);
|
|
7761
9442
|
await graphqlManager.updateRouter();
|
|
@@ -7853,6 +9534,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7853
9534
|
let authEnabled = false;
|
|
7854
9535
|
let configuredResolveIdentity;
|
|
7855
9536
|
let configuredRequireAuth;
|
|
9537
|
+
let configuredExemptPaths;
|
|
9538
|
+
let configuredAttachmentPolicyReads;
|
|
7856
9539
|
let configuredRenown;
|
|
7857
9540
|
if (options.configFile) {
|
|
7858
9541
|
const config = getConfig(options.configFile);
|
|
@@ -7864,8 +9547,10 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7864
9547
|
authEnabled = options.auth.enabled;
|
|
7865
9548
|
configuredResolveIdentity = options.auth.resolveIdentity;
|
|
7866
9549
|
configuredRequireAuth = options.auth.requireAuthenticatedCaller;
|
|
9550
|
+
configuredExemptPaths = options.auth.requireAuthenticatedCallerExemptPaths;
|
|
9551
|
+
configuredAttachmentPolicyReads = options.auth.attachmentReadsFollowDocumentPolicy;
|
|
7867
9552
|
}
|
|
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;
|
|
9553
|
+
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
9554
|
if (AUTH_ENABLED !== void 0) authEnabled = AUTH_ENABLED === "true";
|
|
7870
9555
|
/**
|
|
7871
9556
|
* Whether the auth middleware reads the bearer and populates `ctx.user`,
|
|
@@ -7896,6 +9581,30 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7896
9581
|
*/
|
|
7897
9582
|
let requireAuthenticatedCaller = configuredRequireAuth ?? false;
|
|
7898
9583
|
if (REQUIRE_AUTHENTICATED_CALLER !== void 0) requireAuthenticatedCaller = REQUIRE_AUTHENTICATED_CALLER === "true";
|
|
9584
|
+
/**
|
|
9585
|
+
* The paths the floor above does not apply to. Comma-separated, like
|
|
9586
|
+
* `ADMINS`, because a deployment configures this the same way it configures
|
|
9587
|
+
* everything else: one variable, one line, however many values it has.
|
|
9588
|
+
*
|
|
9589
|
+
* Empty entries are dropped rather than refused, so a trailing comma or a
|
|
9590
|
+
* value split across lines in a manifest is not a boot failure; a value that
|
|
9591
|
+
* cannot ever match is refused, in
|
|
9592
|
+
* {@link assertRequireAuthenticatedCallerAllowed}.
|
|
9593
|
+
*/
|
|
9594
|
+
let requireAuthenticatedCallerExemptPaths = configuredExemptPaths ?? [];
|
|
9595
|
+
if (REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS !== void 0) requireAuthenticatedCallerExemptPaths = REQUIRE_AUTHENTICATED_CALLER_EXEMPT_PATHS.split(",").map((path) => path.trim()).filter((path) => path.length > 0);
|
|
9596
|
+
/**
|
|
9597
|
+
* Whether an attachment read is decided by the referencing document's own
|
|
9598
|
+
* policy rather than by the host's permission tables.
|
|
9599
|
+
*
|
|
9600
|
+
* Off by default, and a deployment's choice rather than something derived
|
|
9601
|
+
* from the flags around it. Which model governs those bytes is configuration
|
|
9602
|
+
* in the same sense the storage backend behind them is: a host that has said
|
|
9603
|
+
* nothing keeps exactly the behaviour it has, and one that wants the change
|
|
9604
|
+
* asks for it and can take it back without disturbing anything else.
|
|
9605
|
+
*/
|
|
9606
|
+
let attachmentReadsFollowDocumentPolicy = configuredAttachmentPolicyReads ?? false;
|
|
9607
|
+
if (ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY !== void 0) attachmentReadsFollowDocumentPolicy = ATTACHMENT_READS_FOLLOW_DOCUMENT_POLICY === "true";
|
|
7899
9608
|
if (ADMINS !== void 0) admins = ADMINS.split(",").map((a) => a.toLowerCase());
|
|
7900
9609
|
let defaultProtection = false;
|
|
7901
9610
|
if (DEFAULT_PROTECTION !== void 0) defaultProtection = DEFAULT_PROTECTION.toLowerCase() === "true";
|
|
@@ -7910,7 +9619,7 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7910
9619
|
const documentPermissionsRequested = options.documentPermissionService !== void 0 || DOCUMENT_PERMISSIONS_ENABLED === "true";
|
|
7911
9620
|
assertAuthRequiredForDocumentPermissions(authEnabled, documentPermissionsRequested);
|
|
7912
9621
|
assertSkipCredentialVerificationAllowed(resolveCallerIdentity, skipCredentialVerification, process.env);
|
|
7913
|
-
assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolveCallerIdentity);
|
|
9622
|
+
assertRequireAuthenticatedCallerAllowed(requireAuthenticatedCaller, resolveCallerIdentity, requireAuthenticatedCallerExemptPaths);
|
|
7914
9623
|
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
9624
|
httpAdapter.getRoute("/health", () => new Response("OK", { status: 200 }));
|
|
7916
9625
|
const readiness = createReadinessGate();
|
|
@@ -7946,8 +9655,8 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7946
9655
|
});
|
|
7947
9656
|
authFetchMiddleware = createAuthFetchMiddleware(authService);
|
|
7948
9657
|
if (requireAuthenticatedCaller) {
|
|
7949
|
-
requireAuthFetchMiddleware = createRequireAuthFetchMiddleware();
|
|
7950
|
-
logger.info("Require-authenticated-caller middleware enabled: anonymous callers are rejected with a 401 before any subgraph");
|
|
9658
|
+
requireAuthFetchMiddleware = createRequireAuthFetchMiddleware(requireAuthenticatedCallerExemptPaths);
|
|
9659
|
+
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
9660
|
} 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
9661
|
}
|
|
7953
9662
|
const dbClosers = [];
|
|
@@ -7972,7 +9681,7 @@ async function _setupCommonInfrastructure(options) {
|
|
|
7972
9681
|
await mkdir(attachmentStoragePath, { recursive: true });
|
|
7973
9682
|
const { db: attachmentDb, knex: attachmentKnex, pglite: attachmentPglite } = getDbClient(options.dbPath, options.pgliteFactory);
|
|
7974
9683
|
dbClosers.push(...makeDbClosers(attachmentKnex, attachmentPglite));
|
|
7975
|
-
const ATTACHMENT_SWEEP_INTERVAL_MS =
|
|
9684
|
+
const ATTACHMENT_SWEEP_INTERVAL_MS = 36e5;
|
|
7976
9685
|
const attachmentBackend = await createStartupAttachmentBackend({ db: attachmentDb });
|
|
7977
9686
|
const attachmentBuilder = new AttachmentBuilder(attachmentDb, attachmentStoragePath).withReservationSweepMs(ATTACHMENT_SWEEP_INTERVAL_MS);
|
|
7978
9687
|
if (attachmentBackend) attachmentBuilder.withBackend(attachmentBackend);
|
|
@@ -8008,6 +9717,7 @@ async function _setupCommonInfrastructure(options) {
|
|
|
8008
9717
|
httpRoutes,
|
|
8009
9718
|
authFetchMiddleware,
|
|
8010
9719
|
requireAuthFetchMiddleware,
|
|
9720
|
+
authEnabled,
|
|
8011
9721
|
authService,
|
|
8012
9722
|
relationalDb,
|
|
8013
9723
|
analyticsStore,
|
|
@@ -8017,13 +9727,14 @@ async function _setupCommonInfrastructure(options) {
|
|
|
8017
9727
|
attachmentReferenceIndex,
|
|
8018
9728
|
packages,
|
|
8019
9729
|
dbClosers,
|
|
8020
|
-
readiness
|
|
9730
|
+
readiness,
|
|
9731
|
+
attachmentReadsFollowDocumentPolicy
|
|
8021
9732
|
};
|
|
8022
9733
|
}
|
|
8023
9734
|
/**
|
|
8024
9735
|
* Private helper function containing common setup logic for API initialization
|
|
8025
9736
|
*/
|
|
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) {
|
|
9737
|
+
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
9738
|
const hostModuleBase = {
|
|
8028
9739
|
...createReactorHostModuleBase({
|
|
8029
9740
|
client: reactorClient,
|
|
@@ -8048,7 +9759,7 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
8048
9759
|
try {
|
|
8049
9760
|
return fn(moduleFor(packageName));
|
|
8050
9761
|
} catch (e) {
|
|
8051
|
-
logger.error(
|
|
9762
|
+
logger.error("Error initializing processor factory for package @package: @error", packageName, e);
|
|
8052
9763
|
return null;
|
|
8053
9764
|
}
|
|
8054
9765
|
}))).filter((factory) => factory.status === "fulfilled" && factory.value !== null && typeof factory.value === "function");
|
|
@@ -8065,17 +9776,44 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
8065
9776
|
const { httpServer, wsServer } = await startServer(httpAdapter, port, options.https, logger);
|
|
8066
9777
|
const authorizationService = createAuthorizationService(authorizationConfig, documentPermissionService, createGetParentIdsFn(reactorClient));
|
|
8067
9778
|
logger.info(`Authorization service initialized (policy: ${authorizationConfig.policy})`);
|
|
8068
|
-
|
|
9779
|
+
assertAttachmentPolicyReadsAllowed(attachmentReadsFollowDocumentPolicy, syncServingGate !== void 0);
|
|
9780
|
+
const floor = requireAuthFetchMiddleware !== void 0;
|
|
9781
|
+
const attachmentAccess = new AttachmentAccessService(createCanonicalDocumentIdResolver(reactorClient), authorizationService, attachmentReferenceIndex.store, attachmentReferenceProjection, reactorClient, attachmentReadsFollowDocumentPolicy ? syncServingGate : void 0, {
|
|
9782
|
+
refuseAnonymousWrites: authEnabled || floor,
|
|
9783
|
+
refuseAnonymousReads: floor
|
|
9784
|
+
});
|
|
9785
|
+
const attachmentClientProvider = new AttachmentClientProvider(attachments.service, attachmentAccess, logger);
|
|
8069
9786
|
const coreSubgraphs = DefaultCoreSubgraphs.slice();
|
|
8070
9787
|
coreSubgraphs.push(ReactorSubgraph);
|
|
8071
9788
|
if (documentPermissionService) {
|
|
8072
9789
|
coreSubgraphs.push(AuthSubgraph);
|
|
8073
9790
|
logger.info("Auth subgraph registered (document permissions enabled)");
|
|
8074
9791
|
}
|
|
8075
|
-
const graphqlManager = await setupGraphQLManager(
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
9792
|
+
const graphqlManager = await setupGraphQLManager({
|
|
9793
|
+
httpAdapter,
|
|
9794
|
+
authFetchMiddleware,
|
|
9795
|
+
requireAuthFetchMiddleware,
|
|
9796
|
+
httpServer,
|
|
9797
|
+
wsServer,
|
|
9798
|
+
client: reactorClient,
|
|
9799
|
+
relationalDb,
|
|
9800
|
+
analyticsStore,
|
|
9801
|
+
syncManager,
|
|
9802
|
+
subgraphs: {
|
|
9803
|
+
extended: subgraphs,
|
|
9804
|
+
core: coreSubgraphs
|
|
9805
|
+
},
|
|
9806
|
+
logger: logger.child(["graphql-manager"]),
|
|
9807
|
+
authorizationService,
|
|
9808
|
+
authService,
|
|
9809
|
+
documentPermissionService,
|
|
9810
|
+
enableDocumentModelSubgraphs: options.enableDocumentModelSubgraphs,
|
|
9811
|
+
port,
|
|
9812
|
+
reactorDriveClient,
|
|
9813
|
+
syncServingGate,
|
|
9814
|
+
httpRoutes,
|
|
9815
|
+
attachments: attachmentClientProvider
|
|
9816
|
+
});
|
|
8079
9817
|
setupEventListeners(packages, graphqlManager, reactorProcessorManager, moduleFor, documentModelRegistry);
|
|
8080
9818
|
if (mcpServerEnabled) {
|
|
8081
9819
|
await setupMcpServer({
|
|
@@ -8101,7 +9839,11 @@ async function _setupAPI(reactorClient, syncManager, reactorProcessorManager, ht
|
|
|
8101
9839
|
attachments,
|
|
8102
9840
|
attachmentReferenceIndex,
|
|
8103
9841
|
attachmentAccess,
|
|
9842
|
+
attachmentClientProvider,
|
|
8104
9843
|
authService,
|
|
9844
|
+
requireAuthenticatedCaller: floor,
|
|
9845
|
+
authorizationService,
|
|
9846
|
+
relationalDb,
|
|
8105
9847
|
dispose
|
|
8106
9848
|
};
|
|
8107
9849
|
}
|
|
@@ -8148,7 +9890,7 @@ function buildApiDispose(args) {
|
|
|
8148
9890
|
};
|
|
8149
9891
|
}
|
|
8150
9892
|
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);
|
|
9893
|
+
const { port, httpAdapter, httpRoutes, authFetchMiddleware, requireAuthFetchMiddleware, authEnabled, authService, relationalDb, analyticsStore, documentPermissionService, authorizationConfig, attachments, attachmentReferenceIndex, packages, dbClosers, readiness, attachmentReadsFollowDocumentPolicy } = await _setupCommonInfrastructure(options);
|
|
8152
9894
|
const { documentModels, upgradeManifests, processors, subgraphs } = await packages.init();
|
|
8153
9895
|
const { module: reactorClientModule, reactorDriveClient, attachmentReferenceProjection = {
|
|
8154
9896
|
status: "unavailable",
|
|
@@ -8165,13 +9907,14 @@ async function initializeAndStartAPI(clientInitializer, options, processorApp) {
|
|
|
8165
9907
|
const documentModelRegistry = reactorClientModule.reactorModule?.documentModelRegistry;
|
|
8166
9908
|
if (!documentModelRegistry) throw new Error("DocumentModelRegistry not available from InProcessReactorClientModule");
|
|
8167
9909
|
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),
|
|
9910
|
+
...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
9911
|
client: reactorClient,
|
|
8170
9912
|
syncManager,
|
|
8171
9913
|
documentModelRegistry,
|
|
8172
9914
|
readiness,
|
|
8173
9915
|
attachmentReferenceProjection,
|
|
8174
|
-
packageManager: packages
|
|
9916
|
+
packageManager: packages,
|
|
9917
|
+
documentPermissionService
|
|
8175
9918
|
};
|
|
8176
9919
|
}
|
|
8177
9920
|
/**
|
|
@@ -8195,6 +9938,55 @@ function withScheme(origin) {
|
|
|
8195
9938
|
return /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
8196
9939
|
}
|
|
8197
9940
|
//#endregion
|
|
9941
|
+
//#region src/services/privacy-permissions.ts
|
|
9942
|
+
/**
|
|
9943
|
+
* reactor-privacy's permission lookup and eraser, over this host's
|
|
9944
|
+
* DocumentPermissionService. Typed structurally on the privacy side.
|
|
9945
|
+
*/
|
|
9946
|
+
var PrivacyPermissionAdapter = class {
|
|
9947
|
+
service;
|
|
9948
|
+
constructor(service) {
|
|
9949
|
+
this.service = service;
|
|
9950
|
+
}
|
|
9951
|
+
async rowsForAddress(address) {
|
|
9952
|
+
const lower = address.toLowerCase();
|
|
9953
|
+
const found = await this.service.rowsForAddress(address);
|
|
9954
|
+
const rows = [];
|
|
9955
|
+
for (const row of found.permissions) for (const column of ["userAddress", "grantedBy"]) {
|
|
9956
|
+
if (row[column] !== lower) continue;
|
|
9957
|
+
rows.push({
|
|
9958
|
+
table: "DocumentPermission",
|
|
9959
|
+
column,
|
|
9960
|
+
documentId: row.documentId,
|
|
9961
|
+
detail: { permission: row.permission }
|
|
9962
|
+
});
|
|
9963
|
+
}
|
|
9964
|
+
for (const row of found.operationPermissions) for (const column of ["userAddress", "grantedBy"]) {
|
|
9965
|
+
if (row[column] !== lower) continue;
|
|
9966
|
+
rows.push({
|
|
9967
|
+
table: "OperationUserPermission",
|
|
9968
|
+
column,
|
|
9969
|
+
documentId: row.documentId,
|
|
9970
|
+
detail: { operationType: row.operationType }
|
|
9971
|
+
});
|
|
9972
|
+
}
|
|
9973
|
+
for (const documentId of found.ownedDocumentIds) rows.push({
|
|
9974
|
+
table: "DocumentProtection",
|
|
9975
|
+
column: "ownerAddress",
|
|
9976
|
+
documentId
|
|
9977
|
+
});
|
|
9978
|
+
return rows;
|
|
9979
|
+
}
|
|
9980
|
+
async erasePermissions(documentId) {
|
|
9981
|
+
const permissions = await this.service.deleteAllDocumentPermissions(documentId);
|
|
9982
|
+
const protection = await this.service.deleteDocumentProtection(documentId);
|
|
9983
|
+
return {
|
|
9984
|
+
...permissions,
|
|
9985
|
+
DocumentProtection: protection
|
|
9986
|
+
};
|
|
9987
|
+
}
|
|
9988
|
+
};
|
|
9989
|
+
//#endregion
|
|
8198
9990
|
//#region src/services/package-storage.ts
|
|
8199
9991
|
var InMemoryPackageStorage = class {
|
|
8200
9992
|
packages = /* @__PURE__ */ new Map();
|
|
@@ -8298,7 +10090,7 @@ var PackageManagementService = class {
|
|
|
8298
10090
|
}
|
|
8299
10091
|
};
|
|
8300
10092
|
//#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 };
|
|
10093
|
+
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
10094
|
|
|
8303
10095
|
//# sourceMappingURL=index.mjs.map
|
|
8304
|
-
//# debugId=
|
|
10096
|
+
//# debugId=65e47e18-15f4-5a67-b94b-f6e795c1eb9b
|